summaryrefslogtreecommitdiffstats
path: root/mobile/android/base
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2019-04-23 15:32:23 -0400
committerMatt A. Tobin <email@mattatobin.com>2019-04-23 15:32:23 -0400
commitabe80cc31d5a40ebed743085011fbcda0c1a9a10 (patch)
treefb3762f06b84745b182af281abb107b95a9fcf01 /mobile/android/base
parent63295d0087eb58a6eb34cad324c4c53d1b220491 (diff)
downloadUXP-abe80cc31d5a40ebed743085011fbcda0c1a9a10.tar
UXP-abe80cc31d5a40ebed743085011fbcda0c1a9a10.tar.gz
UXP-abe80cc31d5a40ebed743085011fbcda0c1a9a10.tar.lz
UXP-abe80cc31d5a40ebed743085011fbcda0c1a9a10.tar.xz
UXP-abe80cc31d5a40ebed743085011fbcda0c1a9a10.zip
Issue #1053 - Drop support Android and remove Fennec - Part 1a: Remove mobile/android
Diffstat (limited to 'mobile/android/base')
-rw-r--r--mobile/android/base/AdjustConstants.java.in32
-rw-r--r--mobile/android/base/AndroidManifest.xml.in433
-rw-r--r--mobile/android/base/AppConstants.java.in342
-rw-r--r--mobile/android/base/FennecManifest_permissions.xml.in65
-rw-r--r--mobile/android/base/GcmAndroidManifest_permissions.xml.in4
-rw-r--r--mobile/android/base/GcmAndroidManifest_services.xml.in29
-rw-r--r--mobile/android/base/Makefile.in594
-rw-r--r--mobile/android/base/adjust-sdk-sandbox.token1
-rw-r--r--mobile/android/base/adjust_sdk_app_token.in3
-rw-r--r--mobile/android/base/aidl/org/mozilla/gecko/media/FormatParam.aidl7
-rw-r--r--mobile/android/base/aidl/org/mozilla/gecko/media/ICodec.aidl26
-rw-r--r--mobile/android/base/aidl/org/mozilla/gecko/media/ICodecCallbacks.aidl16
-rw-r--r--mobile/android/base/aidl/org/mozilla/gecko/media/IMediaDrmBridge.aidl25
-rw-r--r--mobile/android/base/aidl/org/mozilla/gecko/media/IMediaDrmBridgeCallbacks.aidl31
-rw-r--r--mobile/android/base/aidl/org/mozilla/gecko/media/IMediaManager.aidl18
-rw-r--r--mobile/android/base/aidl/org/mozilla/gecko/media/Sample.aidl7
-rw-r--r--mobile/android/base/aidl/org/mozilla/gecko/media/SessionKeyInfo.aidl7
-rw-r--r--mobile/android/base/android-services.mozbuild1023
-rw-r--r--mobile/android/base/crashreporter/res/drawable-mdpi/crash_reporter.pngbin2833 -> 0 bytes
-rw-r--r--mobile/android/base/crashreporter/res/drawable/textbox_bg.xml22
-rw-r--r--mobile/android/base/crashreporter/res/layout/crash_reporter.xml126
-rw-r--r--mobile/android/base/crashreporter/res/values/colors.xml13
-rw-r--r--mobile/android/base/crashreporter/res/values/styles.xml15
-rw-r--r--mobile/android/base/geckoview.ddf75
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/ANRReporter.java596
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/AboutPages.java117
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/AccountsHelper.java318
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/ActionBarTextSelection.java256
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/ActionModeCompat.java135
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/ActionModeCompatView.java202
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/ActivityHandlerHelper.java61
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/BootReceiver.java27
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/BrowserApp.java4261
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/BrowserLocaleManager.java439
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/ChromeCastDisplay.java112
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/ChromeCastPlayer.java509
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/CrashReporter.java480
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/CustomEditText.java89
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/DataReportingNotification.java133
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/DevToolsAuthHelper.java52
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/DoorHangerPopup.java361
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/DownloadsIntegration.java235
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/DynamicToolbar.java218
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/EditBookmarkDialog.java252
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/Experiments.java119
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/FilePicker.java227
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/FilePickerResultHandler.java282
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/FindInPageBar.java256
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/FormAssistPopup.java459
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/GeckoActivity.java100
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/GeckoActivityStatus.java10
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/GeckoApp.java2878
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java314
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/GeckoJavaSampler.java211
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/GeckoMediaPlayer.java27
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/GeckoMessageReceiver.java19
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/GeckoPresentationDisplay.java22
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/GeckoProfilesProvider.java149
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/GeckoService.java236
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/GeckoUpdateReceiver.java25
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/GlobalHistory.java178
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/GlobalPageMetadata.java182
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/GuestSession.java51
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/IntentHelper.java599
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/LauncherActivity.java110
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/LocaleManager.java42
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/Locales.java136
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/MediaCastingBar.java131
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/MediaPlayerManager.java323
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/MemoryMonitor.java279
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/MotionEventInterceptor.java13
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/PackageReplacedReceiver.java38
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/PresentationMediaPlayerManager.java149
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/PresentationView.java27
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/PrintHelper.java124
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/PrivateTab.java28
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/RemoteClientsDialogFragment.java133
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/RemotePresentationService.java150
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/Restarter.java50
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/ScreenManagerHelper.java43
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/ScreenshotObserver.java146
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/SessionParser.java140
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/SharedPreferencesHelper.java311
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/SiteIdentity.java249
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/SnackbarBuilder.java257
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/SuggestClient.java142
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/Tab.java843
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/Tabs.java1021
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/Telemetry.java246
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/TelemetryContract.java307
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/ThumbnailHelper.java246
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/ZoomedView.java838
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/activitystream/ActivityStream.java149
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/adjust/AdjustBrowserAppDelegate.java52
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/adjust/AdjustHelper.java75
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/adjust/AdjustHelperInterface.java22
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/adjust/AttributionHelperListener.java17
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/adjust/StubAdjustHelper.java31
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/animation/AnimationUtils.java21
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/animation/HeightChangeAnimation.java27
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/animation/PropertyAnimator.java342
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/animation/Rotate3DAnimation.java97
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/animation/ViewHelper.java109
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/cleanup/FileCleanupController.java81
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/cleanup/FileCleanupService.java80
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java177
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/customtabs/GeckoCustomTabsService.java65
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/AbstractPerProfileDatabaseProvider.java79
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/AbstractTransactionalProvider.java328
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/BaseTable.java64
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java785
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/BrowserDB.java205
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java2237
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java2340
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/DBUtils.java450
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/FormHistoryProvider.java166
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/HomeProvider.java194
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java1938
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/LocalSearches.java28
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/LocalTabsAccessor.java320
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/LocalURLMetadata.java240
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/LocalUrlAnnotations.java253
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/LoginsProvider.java520
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/PasswordsProvider.java348
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/PerProfileDatabaseProvider.java55
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/PerProfileDatabases.java94
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/RemoteClient.java69
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/RemoteTab.java90
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/SQLiteBridgeContentProvider.java471
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/SearchHistoryProvider.java127
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/Searches.java12
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/SharedBrowserDatabaseProvider.java128
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/SuggestedSites.java629
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/Table.java47
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/TabsAccessor.java28
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/TabsProvider.java361
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/URLMetadata.java25
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/URLMetadataTable.java92
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/db/UrlAnnotations.java51
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/delegates/BookmarkStateChangeDelegate.java237
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/delegates/BrowserAppDelegate.java78
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/delegates/BrowserAppDelegateWithReference.java29
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/delegates/OfflineTabStatusDelegate.java119
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/delegates/ScreenshotDelegate.java80
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/delegates/TabsTrayVisibilityAwareDelegate.java38
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/distribution/Distribution.java1046
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/distribution/DistributionStoreCallback.java61
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/distribution/PartnerBookmarksProviderProxy.java322
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/distribution/PartnerBrowserCustomizationsClient.java43
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/distribution/ReferrerDescriptor.java64
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/distribution/ReferrerReceiver.java107
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/dlc/BaseAction.java166
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/dlc/CleanupAction.java49
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/dlc/DownloadAction.java325
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/dlc/DownloadContentService.java144
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/dlc/StudyAction.java81
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/dlc/SyncAction.java263
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/dlc/VerifyAction.java63
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContent.java189
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContentBootstrap.java161
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContentBuilder.java238
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContentCatalog.java303
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/ContentNotificationsDelegate.java89
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/FeedAlarmReceiver.java31
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/FeedFetcher.java110
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/FeedService.java168
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/action/CheckForUpdatesAction.java281
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/action/EnrollSubscriptionsAction.java101
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/action/FeedAction.java58
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/action/SetupAlarmsAction.java146
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/action/SubscribeToFeedAction.java79
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/action/WithdrawSubscriptionsAction.java109
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSite.java38
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteBlogger.java29
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteMedium.java29
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteTumblr.java33
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteWordpress.java26
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/parser/Feed.java70
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/parser/Item.java49
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/parser/SimpleFeedParser.java367
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/feeds/subscriptions/FeedSubscription.java130
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/firstrun/DataPanel.java47
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunAnimationContainer.java94
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPager.java174
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPagerConfig.java107
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPanel.java80
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/firstrun/RestrictedWelcomePanel.java61
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/firstrun/SyncPanel.java61
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/firstrun/TabQueuePanel.java92
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/gcm/GcmInstanceIDListenerService.java35
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/gcm/GcmMessageListenerService.java38
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/gcm/GcmTokenClient.java131
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/health/HealthRecorder.java40
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/health/SessionInformation.java138
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/health/StubbedHealthRecorder.java53
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/BookmarkFolderView.java147
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/BookmarkScreenshotRow.java67
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/BookmarksListAdapter.java352
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/BookmarksListView.java218
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java316
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java1316
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/ClientsAdapter.java373
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java433
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryItem.java127
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java697
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryRecyclerView.java145
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/DynamicPanel.java393
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/FramePanelLayout.java52
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HistorySectionsHelper.java80
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeAdapter.java224
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java315
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeConfig.java1694
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeConfigLoader.java83
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeConfigPrefsBackend.java663
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeContextMenuInfo.java82
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeExpandableListView.java68
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java498
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeListView.java138
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomePager.java564
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomePanelsManager.java368
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/HomeScreen.java57
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/ImageLoader.java164
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/MultiTypeCursorAdapter.java100
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelAuthCache.java82
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelAuthLayout.java63
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelBackItemView.java48
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelHeaderView.java28
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelInfoManager.java162
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelItemView.java136
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelLayout.java747
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelListView.java83
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelRecyclerView.java178
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelRecyclerViewAdapter.java137
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelRefreshLayout.java90
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelViewAdapter.java113
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PanelViewItemHandler.java59
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/PinSiteDialog.java256
-rwxr-xr-xmobile/android/base/java/org/mozilla/gecko/home/RecentTabsAdapter.java454
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsExpandableListState.java163
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/SearchEngine.java102
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/SearchEngineAdapter.java122
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/SearchEngineBar.java148
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java494
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/SearchLoader.java114
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/SimpleCursorLoader.java147
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/SpacingDecoration.java20
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/TabMenuStrip.java127
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/TabMenuStripLayout.java246
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/TopSitesGridItemView.java312
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/TopSitesGridView.java169
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/TopSitesPanel.java968
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/TopSitesThumbnailView.java102
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java324
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStream.java145
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStreamHomeFragment.java39
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStreamHomeScreen.java73
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java196
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamRecyclerAdapter.java135
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/ActivityStreamContextMenu.java239
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/BottomSheetContextMenu.java102
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/PopupContextMenu.java76
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/CirclePageIndicator.java568
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesCard.java105
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPage.java38
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPageAdapter.java117
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPagerAdapter.java124
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/IconCallback.java13
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/IconDescriptor.java96
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/IconDescriptorComparator.java67
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/IconRequest.java181
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/IconRequestBuilder.java143
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/IconRequestExecutor.java152
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/IconResponse.java167
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/IconTask.java222
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/Icons.java35
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/IconsHelper.java140
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/decoders/FaviconDecoder.java197
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/decoders/ICODecoder.java396
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/decoders/IconDirectoryEntry.java212
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/decoders/LoadFaviconResult.java133
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/loader/ContentProviderLoader.java96
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/loader/DataUriLoader.java36
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/loader/DiskLoader.java27
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/loader/IconDownloader.java219
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/loader/IconGenerator.java168
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/loader/IconLoader.java23
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/loader/JarLoader.java45
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/loader/LegacyLoader.java74
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/loader/MemoryLoader.java31
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/preparation/AboutPagesPreparer.java39
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/preparation/AddDefaultIconUrl.java39
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/preparation/FilterKnownFailureUrls.java29
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/preparation/FilterMimeTypes.java39
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/preparation/FilterPrivilegedUrls.java30
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/preparation/LookupIconUrl.java56
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/preparation/Preparer.java19
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/processing/ColorProcessor.java61
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/processing/DiskProcessor.java36
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/processing/MemoryProcessor.java38
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/processing/Processor.java21
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/processing/ResizingProcessor.java68
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/storage/DiskStorage.java293
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/storage/FailureCache.java70
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/icons/storage/MemoryStorage.java112
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/javaaddons/JavaAddonManager.java195
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/javaaddons/JavaAddonManagerV1.java260
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/lwt/LightweightTheme.java455
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/lwt/LightweightThemeDrawable.java133
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/mdns/MulticastDNSManager.java535
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/AsyncCodec.java34
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/AsyncCodecFactory.java14
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/AudioFocusAgent.java135
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/Codec.java366
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/CodecProxy.java191
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/FormatParam.java133
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrm.java35
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java627
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV23.java44
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/JellyBeanAsyncCodec.java405
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/LocalMediaDrmBridge.java162
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java431
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/MediaDrmProxy.java307
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/MediaManager.java44
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/RemoteManager.java224
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridge.java152
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridgeStub.java247
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/Sample.java264
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/SamplePool.java115
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/SessionKeyInfo.java51
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/media/VideoPlayer.java204
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/menu/GeckoMenu.java928
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/menu/GeckoMenuInflater.java163
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/menu/GeckoMenuItem.java472
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/menu/GeckoSubMenu.java81
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/menu/MenuItemActionBar.java64
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/menu/MenuItemDefault.java152
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/menu/MenuItemSwitcherLayout.java188
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/menu/MenuPanel.java36
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/menu/MenuPopup.java76
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/mozglue/SharedMemBuffer.java81
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/mozglue/SharedMemory.java171
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/notifications/NotificationClient.java324
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/notifications/NotificationHelper.java366
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/notifications/NotificationReceiver.java106
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/notifications/NotificationService.java37
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/notifications/WhatsNewReceiver.java99
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/overlays/OverlayConstants.java68
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/overlays/service/OverlayActionService.java126
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/overlays/service/ShareData.java48
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/overlays/service/sharemethods/AddBookmark.java30
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/overlays/service/sharemethods/SendTab.java296
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/overlays/service/sharemethods/ShareMethod.java82
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/overlays/ui/OverlayDialogButton.java128
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/overlays/ui/SendTabDeviceListArrayAdapter.java185
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/overlays/ui/SendTabList.java150
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/overlays/ui/SendTabTargetSelectedListener.java25
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/overlays/ui/ShareDialog.java493
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/AlignRightLinkPreference.java24
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/AndroidImport.java230
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/AndroidImportPreference.java112
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/AppCompatPreferenceActivity.java115
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/ClearOnShutdownPref.java37
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/CustomCheckBoxPreference.java44
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/CustomListCategory.java72
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/CustomListPreference.java182
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/DistroSharedPrefsImport.java61
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/FontSizePreference.java192
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferenceFragment.java296
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java1514
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/LinkPreference.java35
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/ListCheckboxPreference.java58
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/LocaleListPreference.java316
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/ModifiableHintPreference.java67
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/MultiChoicePreference.java271
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/MultiPrefMultiChoicePreference.java116
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/PanelsPreference.java255
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/PanelsPreferenceCategory.java261
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/PrivateDataPreference.java67
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/SearchEnginePreference.java183
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/SearchPreferenceCategory.java145
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/SetHomepagePreference.java124
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/preferences/SyncPreference.java103
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java237
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/promotion/HomeScreenPrompt.java237
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/promotion/ReaderViewBookmarkPromotion.java103
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/promotion/SimpleHelperUI.java194
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/prompts/ColorPickerInput.java59
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/prompts/IconGridInput.java171
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/prompts/IntentChooserPrompt.java158
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/prompts/IntentHandler.java12
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/prompts/Prompt.java586
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/prompts/PromptInput.java398
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/prompts/PromptListAdapter.java281
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/prompts/PromptListItem.java128
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/prompts/PromptService.java72
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/prompts/TabInput.java107
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/push/Fetched.java71
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/push/PushClient.java110
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/push/PushManager.java354
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/push/PushRegistration.java126
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/push/PushService.java440
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/push/PushState.java137
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/push/PushSubscription.java81
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/reader/ReaderModeUtils.java72
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/reader/ReadingListHelper.java154
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/reader/SavedReaderViewHelper.java247
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/restrictions/DefaultConfiguration.java34
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/restrictions/GuestProfileConfiguration.java83
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/restrictions/Restrictable.java112
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/restrictions/RestrictedProfileConfiguration.java129
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/restrictions/RestrictionCache.java99
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/restrictions/RestrictionConfiguration.java31
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/restrictions/RestrictionProvider.java84
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/restrictions/Restrictions.java127
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/search/SearchEngine.java304
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/search/SearchEngineManager.java764
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueHelper.java357
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueuePrompt.java215
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueService.java342
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabqueue/TabReceivedService.java130
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/PrivateTabsPanel.java63
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabCurve.java70
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryController.java87
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryFragment.java172
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryItemRow.java69
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryPage.java60
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabPanelBackButton.java55
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabStrip.java170
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabStripAdapter.java98
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabStripItemView.java254
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabStripView.java449
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabsGridLayout.java712
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayout.java216
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutAdapter.java100
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutItemView.java172
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutRecyclerAdapter.java124
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabsListLayout.java118
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabsListLayoutAnimator.java65
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabsPanel.java456
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabsPanelThumbnailView.java52
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/tabs/TabsTouchHelperCallback.java69
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryConstants.java16
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryCorePingDelegate.java188
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryDispatcher.java118
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryPing.java34
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryPreferences.java73
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryUploadService.java347
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/telemetry/measurements/CampaignIdMeasurements.java37
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/telemetry/measurements/SearchCountMeasurements.java100
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/telemetry/measurements/SessionMeasurements.java99
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/telemetry/pingbuilders/TelemetryCorePingBuilder.java247
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/telemetry/pingbuilders/TelemetryPingBuilder.java87
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/telemetry/schedulers/TelemetryUploadAllPingsImmediatelyScheduler.java32
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/telemetry/schedulers/TelemetryUploadScheduler.java26
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/telemetry/stores/TelemetryJSONFilePingStore.java301
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/telemetry/stores/TelemetryPingStore.java66
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/text/FloatingActionModeCallback.java69
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java206
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/text/TextAction.java68
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/text/TextSelection.java13
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/AutocompleteHandler.java10
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/BackButton.java26
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java960
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbarPhone.java128
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbarPhoneBase.java219
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbarTablet.java211
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbarTabletBase.java182
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/CanvasDelegate.java62
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/ForwardButton.java23
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/NavButton.java85
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/PageActionLayout.java371
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/PhoneTabsButton.java29
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/ShapedButton.java109
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/ShapedButtonFrameLayout.java74
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/SiteIdentityPopup.java571
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/TabCounter.java154
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java530
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarEditLayout.java348
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarEditText.java630
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarPrefs.java78
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarProgressView.java195
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/trackingprotection/TrackingProtectionPrompt.java131
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/updater/PostUpdateHandler.java120
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/updater/UpdateService.java795
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/updater/UpdateServiceHelper.java213
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/util/ColorUtil.java44
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/util/DrawableUtil.java66
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/util/ResourceDrawableUtils.java136
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/util/TouchTargetUtil.java48
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/util/UnusedResourcesUtil.java128
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/util/ViewUtil.java33
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/ActivityChooserModel.java1359
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/AllCapsTextView.java21
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/AnchoredPopup.java130
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/AnimatedHeightLayout.java77
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/BasicColorPicker.java140
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/CheckableLinearLayout.java52
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/ClickableWhenDisabledEditText.java25
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/ContentSecurityDoorHanger.java127
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/CropImageView.java143
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/DateTimePicker.java665
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/DefaultDoorHanger.java190
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/DefaultItemAnimatorBase.java685
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/DoorHanger.java220
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/DoorhangerConfig.java127
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/EllipsisTextView.java65
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/ExternalIntentDuringPrivateBrowsingPromptFragment.java106
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/FadedMultiColorTextView.java108
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/FadedSingleColorTextView.java74
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/FadedTextView.java48
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/FaviconView.java268
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/FilledCardView.java39
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/FlowLayout.java91
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/GeckoActionProvider.java360
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/GeckoPopupMenu.java189
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/HistoryDividerItemDecoration.java66
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/IconTabWidget.java111
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/LoginDoorHanger.java228
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/RecyclerViewClickSupport.java105
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/ResizablePathDrawable.java117
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/RoundedCornerLayout.java79
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/SiteLogins.java16
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/SquaredImageView.java21
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/SquaredRelativeLayout.java33
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/SwipeDismissListViewTouchListener.java356
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/TabThumbnailWrapper.java38
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/ThumbnailView.java86
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/TouchDelegateWithReset.java134
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/TwoWayView.java7191
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedEditText.java172
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedFrameLayout.java172
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedImageButton.java200
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedImageView.java199
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedLinearLayout.java167
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedRelativeLayout.java172
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedTextSwitcher.java167
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedTextView.java172
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedView.java172
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedView.java.frag211
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/widget/themed/generate_themed_views.py72
-rw-r--r--mobile/android/base/locales/Makefile.in114
-rw-r--r--mobile/android/base/locales/en-US/android_strings.dtd848
-rw-r--r--mobile/android/base/locales/en-US/search_strings.dtd28
-rw-r--r--mobile/android/base/locales/en-US/sync_strings.dtd126
-rw-r--r--mobile/android/base/locales/moz.build8
-rw-r--r--mobile/android/base/moz.build1143
-rw-r--r--mobile/android/base/package-name.txt.in2
-rw-r--r--mobile/android/base/resources/anim/grow_fade_in.xml21
-rw-r--r--mobile/android/base/resources/anim/overlay_check_entry.xml10
-rw-r--r--mobile/android/base/resources/anim/overlay_check_exit.xml11
-rw-r--r--mobile/android/base/resources/anim/overlay_pop.xml25
-rw-r--r--mobile/android/base/resources/anim/overlay_slide_down.xml10
-rw-r--r--mobile/android/base/resources/anim/overlay_slide_up.xml9
-rw-r--r--mobile/android/base/resources/anim/popup_hide.xml16
-rw-r--r--mobile/android/base/resources/anim/popup_show.xml16
-rw-r--r--mobile/android/base/resources/color/action_bar_menu_item_colors.xml26
-rw-r--r--mobile/android/base/resources/color/action_bar_secondary_menu_item_colors.xml15
-rw-r--r--mobile/android/base/resources/color/facet_button_text_color.xml9
-rw-r--r--mobile/android/base/resources/color/pressed_about_page_header_grey.xml12
-rw-r--r--mobile/android/base/resources/color/primary_text.xml7
-rw-r--r--mobile/android/base/resources/color/primary_text_selector.xml7
-rw-r--r--mobile/android/base/resources/color/recyclerview_selector.xml12
-rw-r--r--mobile/android/base/resources/color/secondary_text.xml7
-rw-r--r--mobile/android/base/resources/color/select_item_multichoice.xml7
-rw-r--r--mobile/android/base/resources/color/state_pressed_toolbar_grey_pressed.xml12
-rw-r--r--mobile/android/base/resources/color/tab_item_title.xml12
-rw-r--r--mobile/android/base/resources/color/tab_new_tab_strip_colors.xml14
-rw-r--r--mobile/android/base/resources/color/tab_strip_item_bg.xml48
-rw-r--r--mobile/android/base/resources/color/tab_strip_item_title.xml18
-rw-r--r--mobile/android/base/resources/color/tab_text_color.xml7
-rw-r--r--mobile/android/base/resources/color/tabs_counter_text_color.xml14
-rw-r--r--mobile/android/base/resources/color/tertiary_text.xml7
-rw-r--r--mobile/android/base/resources/color/toolbar_display_layout_bg.xml11
-rw-r--r--mobile/android/base/resources/color/top_sites_grid_item_title.xml14
-rw-r--r--mobile/android/base/resources/color/url_bar_title.xml21
-rw-r--r--mobile/android/base/resources/color/url_bar_title_hint.xml15
-rw-r--r--mobile/android/base/resources/drawable-hdpi-v11/alert_camera.pngbin181 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi-v11/alert_download.pngbin273 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi-v11/alert_guest.pngbin761 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi-v11/alert_mic.pngbin327 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi-v11/alert_mic_camera.pngbin242 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi-v11/ic_menu_back.pngbin292 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi-v11/ic_menu_bookmark_add.pngbin1405 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi-v11/ic_menu_forward.pngbin291 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi-v11/ic_menu_reload.pngbin663 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi-v11/ic_status_logo.pngbin528 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi-v11/star_blue.pngbin1050 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/ab_add_search_engine.pngbin593 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/ab_copy.pngbin148 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/ab_cut.pngbin508 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/ab_done.pngbin379 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/ab_mic.pngbin363 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/ab_paste.pngbin253 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/ab_qrcode.pngbin169 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/ab_search.pngbin799 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/ab_select_all.pngbin143 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/alert_camera.pngbin206 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/alert_download.pngbin218 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/alert_download_animation_1.pngbin266 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/alert_download_animation_2.pngbin267 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/alert_download_animation_3.pngbin268 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/alert_download_animation_4.pngbin267 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/alert_download_animation_5.pngbin270 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/alert_download_animation_6.pngbin270 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/alert_guest.pngbin878 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/alert_mic.pngbin278 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/alert_mic_camera.pngbin247 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/arrow_up.pngbin276 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/blank.pngbin82 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/casting.pngbin244 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/casting_active.pngbin302 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/close.pngbin296 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/close_edit_mode_dark.pngbin234 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/close_edit_mode_light.pngbin235 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/color_picker_row_bg.9.pngbin121 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/device_desktop.pngbin217 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/device_mobile.pngbin165 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/dropshadow.9.pngbin367 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/favicon_globe.pngbin1227 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/find_close.pngbin234 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/find_next.pngbin176 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/find_prev.pngbin167 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/firefox_settings_alert.pngbin412 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/flat_icon.pngbin620 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/folder_closed.pngbin339 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/globe_light.pngbin2014 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/grid_icon_bg_activated.9.pngbin105 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/grid_icon_bg_focused.9.pngbin139 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/handle_end.pngbin570 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/handle_middle.pngbin692 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/handle_start.pngbin581 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/helper_readerview_bookmark.webpbin1710 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/home_bg.pngbin1645 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/home_group_collapsed.pngbin228 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/home_star.pngbin3167 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/home_tab_menu_strip.9.pngbin88 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/homepage_banner_firstrun.pngbin530 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/ic_action_settings.pngbin446 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/ic_media_pause.pngbin107 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/ic_media_play.pngbin230 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/ic_menu_share.pngbin494 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/ic_status_logo.pngbin537 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/ic_url_bar_tab.pngbin195 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/ic_widget_new_tab.pngbin173 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/ic_widget_search.pngbin396 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/icon_bookmarks_empty.pngbin926 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/icon_home_empty_firefox.pngbin2579 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/icon_key.pngbin922 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/icon_most_recent_empty.pngbin1508 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/icon_openinapp.pngbin307 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/icon_pageaction.pngbin152 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/icon_remote_tabs_empty.pngbin742 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/icon_search_empty_firefox.pngbin1741 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/icon_shareplane.pngbin1449 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/img_check.pngbin669 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/location.pngbin851 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/lock_disabled.pngbin790 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/lock_inactive.pngbin535 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/lock_secure.pngbin535 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/media_bar_pause.pngbin166 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/media_bar_play.pngbin336 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/media_bar_stop.pngbin401 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/menu.pngbin138 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/menu_item_check.pngbin502 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/menu_item_more.pngbin96 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/menu_item_uncheck.pngbin254 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/network_error.pngbin1160 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/notification_media.webpbin484 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/open_in_browser.pngbin194 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/overlay_bookmark_icon.pngbin1181 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/overlay_bookmarked_already_icon.pngbin578 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/overlay_check.pngbin1194 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/pause.pngbin231 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/phone.pngbin528 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/pin.pngbin201 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/play.pngbin300 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/private_masq.pngbin1660 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/progress.9.pngbin343 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/reader.pngbin315 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/reader_active.pngbin316 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/reading_list_folder.pngbin424 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/search_clear.pngbin238 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/search_history.pngbin419 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/search_icon_active.pngbin450 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/search_icon_inactive.pngbin456 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/search_launcher.pngbin3473 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/search_plus.pngbin213 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/settings_notifications.pngbin385 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/shareplane.pngbin947 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/shield_disabled.pngbin1683 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/shield_enabled.pngbin1508 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/status_icon_readercache.pngbin445 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/suggestedsites_amazon.pngbin4253 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/suggestedsites_facebook.pngbin603 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/suggestedsites_twitter.pngbin1304 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/suggestedsites_wikipedia.pngbin2037 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/suggestedsites_youtube.pngbin2773 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/switch_button_icon.pngbin186 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/tab_audio_playing.pngbin290 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/tab_close.pngbin178 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/tab_close_active.pngbin186 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/tab_indicator_background.9.pngbin107 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/tab_indicator_divider.9.pngbin75 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/tab_indicator_selected.9.pngbin88 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/tab_indicator_selected_focused.9.pngbin93 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/tab_new.pngbin219 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/tab_preview_masq.pngbin1131 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/tabs_count.pngbin145 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/tabs_count_foreground.pngbin146 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/tabs_normal.pngbin216 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/tabs_panel_nav_back.pngbin453 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/tabs_private.pngbin429 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/tip_addsearch.pngbin2606 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/top_site_add.pngbin113 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/tracking_protection_toolbar_illustration.pngbin2474 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/undo_button_icon.pngbin436 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/url_bar_entry_default.9.pngbin217 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/url_bar_entry_default_pb.9.pngbin252 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/url_bar_entry_pressed.9.pngbin252 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/url_bar_entry_pressed_pb.9.pngbin259 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/urlbar_stop.pngbin276 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/validation_arrow.pngbin221 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/validation_arrow_inverted.pngbin240 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/validation_bg.9.pngbin409 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/warning_major.pngbin283 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/warning_minor.pngbin283 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-hdpi/widget_bg.9.pngbin354 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-hdpi-v11/ic_menu_back.pngbin471 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-hdpi-v11/ic_menu_forward.pngbin360 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-hdpi-v11/ic_menu_reload.pngbin779 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-hdpi-v11/tabs_count.pngbin171 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-hdpi-v11/tabs_count_foreground.pngbin144 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-hdpi-v11/toolbar_favicon_default.pngbin134 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-hdpi-v11/url_bar_entry_default.9.pngbin261 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-hdpi-v11/url_bar_entry_default_pb.9.pngbin281 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-hdpi-v11/url_bar_entry_pressed.9.pngbin281 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-hdpi-v11/url_bar_entry_pressed_pb.9.pngbin281 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-v11/browser_toolbar_action_bar_button.xml77
-rw-r--r--mobile/android/base/resources/drawable-large-v11/url_bar_nav_button.xml36
-rw-r--r--mobile/android/base/resources/drawable-large-xhdpi-v11/ic_menu_back.pngbin598 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xhdpi-v11/ic_menu_forward.pngbin393 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xhdpi-v11/ic_menu_reload.pngbin998 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xhdpi-v11/tabs_count.pngbin196 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xhdpi-v11/tabs_count_foreground.pngbin163 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xhdpi-v11/toolbar_favicon_default.pngbin153 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xhdpi-v11/url_bar_entry_default.9.pngbin351 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xhdpi-v11/url_bar_entry_default_pb.9.pngbin367 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xhdpi-v11/url_bar_entry_pressed.9.pngbin373 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xhdpi-v11/url_bar_entry_pressed_pb.9.pngbin367 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xxhdpi-v11/ic_menu_back.pngbin845 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xxhdpi-v11/ic_menu_forward.pngbin544 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xxhdpi-v11/ic_menu_reload.pngbin1324 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xxhdpi-v11/tabs_count.pngbin232 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xxhdpi-v11/tabs_count_foreground.pngbin196 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xxhdpi-v11/toolbar_favicon_default.pngbin193 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xxhdpi-v11/url_bar_entry_default.9.pngbin497 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xxhdpi-v11/url_bar_entry_default_pb.9.pngbin499 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xxhdpi-v11/url_bar_entry_pressed.9.pngbin497 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-large-xxhdpi-v11/url_bar_entry_pressed_pb.9.pngbin500 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-nodpi/cloud.pngbin2188 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-nodpi/firstrun_account.pngbin8733 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-nodpi/firstrun_bookmarks.pngbin19817 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-nodpi/firstrun_data_off.pngbin18905 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-nodpi/firstrun_data_on.pngbin16918 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-nodpi/firstrun_readerview.pngbin9895 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-nodpi/firstrun_signin.pngbin39900 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-nodpi/firstrun_sync.pngbin16191 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-nodpi/firstrun_tabqueue_off.pngbin8779 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-nodpi/firstrun_tabqueue_on.pngbin9444 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-nodpi/firstrun_urlbar.pngbin12926 -> 0 bytes
-rwxr-xr-xmobile/android/base/resources/drawable-nodpi/icon_recent.pngbin774 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-v12/toast_button_background.xml27
-rw-r--r--mobile/android/base/resources/drawable-v21/logo.xml15
-rw-r--r--mobile/android/base/resources/drawable-xhdpi-v11/alert_camera.pngbin204 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi-v11/alert_download.pngbin273 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi-v11/alert_guest.pngbin1016 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi-v11/alert_mic.pngbin345 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi-v11/alert_mic_camera.pngbin268 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi-v11/ic_menu_back.pngbin361 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi-v11/ic_menu_bookmark_add.pngbin1910 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi-v11/ic_menu_forward.pngbin368 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi-v11/ic_menu_reload.pngbin777 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi-v11/ic_status_logo.pngbin728 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi-v11/star_blue.pngbin1418 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/ab_add_search_engine.pngbin672 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/ab_copy.pngbin174 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/ab_cut.pngbin602 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/ab_done.pngbin501 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/ab_mic.pngbin412 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/ab_paste.pngbin300 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/ab_qrcode.pngbin177 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/ab_search.pngbin989 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/ab_select_all.pngbin138 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/alert_camera.pngbin269 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/alert_download.pngbin294 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/alert_download_animation_1.pngbin266 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/alert_download_animation_2.pngbin267 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/alert_download_animation_3.pngbin268 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/alert_download_animation_4.pngbin267 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/alert_download_animation_5.pngbin270 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/alert_download_animation_6.pngbin270 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/alert_guest.pngbin289 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/alert_mic.pngbin404 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/alert_mic_camera.pngbin337 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/arrow_up.pngbin247 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/blank.pngbin81 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/casting.pngbin370 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/casting_active.pngbin442 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/close.pngbin373 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/close_edit_mode_dark.pngbin283 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/close_edit_mode_light.pngbin288 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/color_picker_row_bg.9.pngbin123 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/device_desktop.pngbin246 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/device_mobile.pngbin215 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/dropshadow.9.pngbin463 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/favicon_globe.pngbin1617 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/find_close.pngbin293 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/find_next.pngbin216 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/find_prev.pngbin212 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/firefox_settings_alert.pngbin622 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/flat_icon.pngbin810 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/folder_closed.pngbin445 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/globe_light.pngbin2597 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/grid_icon_bg_activated.9.pngbin116 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/grid_icon_bg_focused.9.pngbin165 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/handle_end.pngbin707 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/handle_middle.pngbin890 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/handle_start.pngbin725 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/helper_readerview_bookmark.webpbin2042 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/home_group_collapsed.pngbin204 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/home_tab_menu_strip.9.pngbin96 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/homepage_banner_firstrun.pngbin707 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/ic_action_settings.pngbin629 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/ic_media_pause.pngbin113 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/ic_media_play.pngbin282 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/ic_menu_share.pngbin713 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/ic_status_logo.pngbin691 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/ic_url_bar_tab.pngbin243 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/ic_widget_new_tab.pngbin184 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/ic_widget_search.pngbin516 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/icon_bookmarks_empty.pngbin1131 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/icon_home_empty_firefox.pngbin3271 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/icon_key.pngbin1212 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/icon_most_recent_empty.pngbin1927 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/icon_openinapp.pngbin420 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/icon_pageaction.pngbin244 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/icon_remote_tabs_empty.pngbin841 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/icon_search_empty_firefox.pngbin2323 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/icon_shareplane.pngbin1823 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/img_check.pngbin907 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/location.pngbin1019 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/lock_disabled.pngbin821 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/lock_inactive.pngbin465 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/lock_secure.pngbin465 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/media_bar_pause.pngbin161 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/media_bar_play.pngbin390 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/media_bar_stop.pngbin509 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/menu.pngbin162 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/menu_item_check.pngbin555 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/menu_item_more.pngbin111 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/menu_item_uncheck.pngbin315 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/network_error.pngbin1677 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/notification_media.webpbin616 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/open_in_browser.pngbin212 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/overlay_bookmark_icon.pngbin1513 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/overlay_bookmarked_already_icon.pngbin755 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/overlay_check.pngbin1586 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/pause.pngbin220 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/phone.pngbin689 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/pin.pngbin227 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/play.pngbin244 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/private_masq.pngbin2007 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/progress.9.pngbin425 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/push_notification.pngbin446 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/reader.pngbin355 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/reader_active.pngbin360 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/reading_list_folder.pngbin542 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/search_clear.pngbin292 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/search_history.pngbin561 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/search_icon_active.pngbin575 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/search_icon_inactive.pngbin593 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/search_launcher.pngbin4931 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/search_plus.pngbin231 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/settings_notifications.pngbin493 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/shareplane.pngbin1215 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/shield_disabled.pngbin2222 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/shield_enabled.pngbin2097 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/status_icon_readercache.pngbin531 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/suggestedsites_amazon.pngbin5924 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/suggestedsites_facebook.pngbin775 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/suggestedsites_restricted_fxsupport.pngbin2269 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/suggestedsites_restricted_mozilla.pngbin2324 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/suggestedsites_twitter.pngbin1769 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/suggestedsites_webmaker.pngbin4712 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/suggestedsites_wikipedia.pngbin2874 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/suggestedsites_youtube.pngbin3686 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/switch_button_icon.pngbin222 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/tab_audio_playing.pngbin341 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/tab_close.pngbin190 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/tab_close_active.pngbin204 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/tab_indicator_background.9.pngbin110 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/tab_indicator_divider.9.pngbin75 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/tab_indicator_selected.9.pngbin97 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/tab_indicator_selected_focused.9.pngbin103 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/tab_new.pngbin293 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/tab_preview_masq.pngbin1460 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/tabs_count.pngbin165 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/tabs_count_foreground.pngbin172 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/tabs_normal.pngbin248 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/tabs_panel_nav_back.pngbin589 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/tabs_private.pngbin554 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/tip_addsearch.pngbin3120 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/top_site_add.pngbin118 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/tracking_protection_toolbar_illustration.pngbin3450 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/undo_button_icon.pngbin552 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/url_bar_entry_default.9.pngbin285 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/url_bar_entry_default_pb.9.pngbin318 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/url_bar_entry_pressed.9.pngbin309 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/url_bar_entry_pressed_pb.9.pngbin310 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/urlbar_stop.pngbin341 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/validation_arrow.pngbin262 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/validation_arrow_inverted.pngbin272 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/validation_bg.9.pngbin487 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/warning_major.pngbin507 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/warning_minor.pngbin516 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xhdpi/widget_bg.9.pngbin1224 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xlarge-hdpi-v11/ic_menu_bookmark_add.pngbin765 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xlarge-hdpi-v11/star_blue.pngbin685 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xlarge-xhdpi-v11/ic_menu_bookmark_add.pngbin958 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xlarge-xhdpi-v11/star_blue.pngbin794 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xlarge-xxhdpi-v11/ic_menu_bookmark_add.pngbin1419 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xlarge-xxhdpi-v11/star_blue.pngbin1193 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi-v11/ic_status_logo.pngbin1343 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/ab_mic.pngbin587 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/ab_qrcode.pngbin230 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/arrow_up.pngbin372 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/close_edit_mode_dark.pngbin377 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/close_edit_mode_light.pngbin377 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/device_desktop.pngbin298 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/device_mobile.pngbin238 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/dropshadow.9.pngbin672 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/exit_fullscreen.pngbin842 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/flat_icon.pngbin1240 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/folder_closed.pngbin635 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/fullscreen.pngbin842 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/globe_light.pngbin3642 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/helper_readerview_bookmark.webpbin3048 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/home_group_collapsed.pngbin293 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/homepage_banner_firstrun.pngbin985 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/ic_action_settings.pngbin818 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/ic_media_pause.pngbin135 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/ic_media_play.pngbin374 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/ic_menu_share.pngbin832 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/ic_widget_new_tab.pngbin238 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/ic_widget_search.pngbin717 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/icon_key.pngbin1789 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/icon_search_empty_firefox.pngbin3465 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/icon_shareplane.pngbin2610 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/img_check.pngbin1276 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/location.pngbin1533 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/lock_disabled.pngbin1215 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/lock_inactive.pngbin777 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/lock_secure.pngbin777 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/menu.pngbin217 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/menu_item_check.pngbin688 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/menu_item_uncheck.pngbin391 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/network_error.pngbin1944 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/notification_media.webpbin974 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/overlay_bookmark_icon.pngbin2143 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/overlay_bookmarked_already_icon.pngbin1113 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/overlay_check.pngbin2144 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/private_masq.pngbin2872 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/push_notification.pngbin611 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/reading_list_folder.pngbin726 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/search_clear.pngbin370 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/search_history.pngbin781 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/search_icon_active.pngbin856 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/search_icon_inactive.pngbin858 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/search_launcher.pngbin8383 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/search_plus.pngbin273 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/shareplane.pngbin1774 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/shield_disabled.pngbin2926 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/shield_enabled.pngbin2789 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/status_icon_readercache.pngbin712 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/suggestedsites_amazon.pngbin9225 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/suggestedsites_facebook.pngbin1152 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/suggestedsites_twitter.pngbin2932 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/suggestedsites_wikipedia.pngbin4456 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/suggestedsites_youtube.pngbin5537 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/tab_close.pngbin278 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/tab_close_active.pngbin292 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/tab_new.pngbin348 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/tab_preview_masq.pngbin2075 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/tabs_panel_nav_back.pngbin816 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/tracking_protection_toolbar_illustration.pngbin5660 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/url_bar_entry_default.9.pngbin406 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/url_bar_entry_default_pb.9.pngbin418 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/url_bar_entry_pressed.9.pngbin425 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/url_bar_entry_pressed_pb.9.pngbin423 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/urlbar_stop.pngbin318 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/warning_major.pngbin408 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxhdpi/warning_minor.pngbin407 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable-xxxhdpi/search_launcher.pngbin12725 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable/action_bar_button.xml24
-rw-r--r--mobile/android/base/resources/drawable/action_bar_button_inverse.xml23
-rw-r--r--mobile/android/base/resources/drawable/action_bar_button_negative.xml20
-rw-r--r--mobile/android/base/resources/drawable/action_bar_button_positive.xml20
-rw-r--r--mobile/android/base/resources/drawable/alert_download_animation.xml16
-rw-r--r--mobile/android/base/resources/drawable/arrow_down.xml10
-rw-r--r--mobile/android/base/resources/drawable/as_bin.xml9
-rw-r--r--mobile/android/base/resources/drawable/as_bookmark.xml9
-rw-r--r--mobile/android/base/resources/drawable/as_contextmenu_divider.xml9
-rw-r--r--mobile/android/base/resources/drawable/as_copy.xml9
-rw-r--r--mobile/android/base/resources/drawable/as_dimiss.xml9
-rw-r--r--mobile/android/base/resources/drawable/as_home.xml9
-rw-r--r--mobile/android/base/resources/drawable/as_private.xml9
-rw-r--r--mobile/android/base/resources/drawable/as_share.xml9
-rw-r--r--mobile/android/base/resources/drawable/as_tab.xml9
-rw-r--r--mobile/android/base/resources/drawable/autocomplete_list_bg.xml14
-rw-r--r--mobile/android/base/resources/drawable/bookmark_folder_arrow_up.xml14
-rw-r--r--mobile/android/base/resources/drawable/button_background_action_blue_round.xml11
-rw-r--r--mobile/android/base/resources/drawable/button_background_action_orange_round.xml11
-rw-r--r--mobile/android/base/resources/drawable/button_enabled_action_blue_round.xml11
-rw-r--r--mobile/android/base/resources/drawable/button_enabled_action_orange_round.xml11
-rw-r--r--mobile/android/base/resources/drawable/button_pressed_action_blue_round.xml11
-rw-r--r--mobile/android/base/resources/drawable/button_pressed_action_orange_round.xml11
-rw-r--r--mobile/android/base/resources/drawable/close_edit_mode_selector.xml17
-rw-r--r--mobile/android/base/resources/drawable/color_picker_checkmark.xml12
-rw-r--r--mobile/android/base/resources/drawable/divider_vertical.xml12
-rw-r--r--mobile/android/base/resources/drawable/edit_text_default.xml24
-rw-r--r--mobile/android/base/resources/drawable/edit_text_focused.xml25
-rw-r--r--mobile/android/base/resources/drawable/facet_button_background.xml15
-rw-r--r--mobile/android/base/resources/drawable/facet_button_background_default.xml8
-rw-r--r--mobile/android/base/resources/drawable/facet_button_background_pressed.xml8
-rw-r--r--mobile/android/base/resources/drawable/home_banner.xml38
-rw-r--r--mobile/android/base/resources/drawable/home_history_clear_button_bg.xml23
-rw-r--r--mobile/android/base/resources/drawable/home_pager_empty_state.xml16
-rw-r--r--mobile/android/base/resources/drawable/ic_as_bookmarked.xml9
-rw-r--r--mobile/android/base/resources/drawable/ic_as_visited.xml9
-rw-r--r--mobile/android/base/resources/drawable/icon_grid_item_bg.xml28
-rw-r--r--mobile/android/base/resources/drawable/logo.xml9
-rw-r--r--mobile/android/base/resources/drawable/menu_item_action_bar_bg.xml24
-rw-r--r--mobile/android/base/resources/drawable/menu_item_state.xml24
-rw-r--r--mobile/android/base/resources/drawable/overlay_share_bookmark_button.xml12
-rw-r--r--mobile/android/base/resources/drawable/overlay_share_button_background.xml15
-rw-r--r--mobile/android/base/resources/drawable/overlay_share_button_background_first.xml29
-rw-r--r--mobile/android/base/resources/drawable/panel_auth_button.xml38
-rw-r--r--mobile/android/base/resources/drawable/progressbar.xml9
-rw-r--r--mobile/android/base/resources/drawable/push_notification.pngbin391 -> 0 bytes
-rw-r--r--mobile/android/base/resources/drawable/remote_tabs_setup_button_background.xml20
-rw-r--r--mobile/android/base/resources/drawable/search_row_background.xml10
-rw-r--r--mobile/android/base/resources/drawable/search_suggestion_button.xml19
-rw-r--r--mobile/android/base/resources/drawable/search_suggestion_prompt_no.xml20
-rw-r--r--mobile/android/base/resources/drawable/search_suggestion_prompt_yes.xml20
-rw-r--r--mobile/android/base/resources/drawable/shaped_button.xml19
-rw-r--r--mobile/android/base/resources/drawable/site_security_level.xml18
-rw-r--r--mobile/android/base/resources/drawable/site_security_unknown.xml12
-rw-r--r--mobile/android/base/resources/drawable/tab_history_bg.xml27
-rw-r--r--mobile/android/base/resources/drawable/tab_history_icon_state.xml24
-rw-r--r--mobile/android/base/resources/drawable/tab_item_close_button.xml18
-rw-r--r--mobile/android/base/resources/drawable/tab_panel_tab_background.xml38
-rw-r--r--mobile/android/base/resources/drawable/tab_queue_dismiss_button_foreground.xml14
-rw-r--r--mobile/android/base/resources/drawable/tab_row.xml16
-rw-r--r--mobile/android/base/resources/drawable/tab_strip_button.xml45
-rw-r--r--mobile/android/base/resources/drawable/tab_strip_divider.xml18
-rw-r--r--mobile/android/base/resources/drawable/tab_thumbnail.xml87
-rw-r--r--mobile/android/base/resources/drawable/tabs_panel_indicator.xml55
-rw-r--r--mobile/android/base/resources/drawable/tabs_panel_indicator_selected.xml9
-rw-r--r--mobile/android/base/resources/drawable/tabs_panel_indicator_selected_private.xml9
-rw-r--r--mobile/android/base/resources/drawable/tabs_strip_indicator.xml48
-rw-r--r--mobile/android/base/resources/drawable/toast_background.xml10
-rw-r--r--mobile/android/base/resources/drawable/toast_button_background.xml30
-rw-r--r--mobile/android/base/resources/drawable/toolbar_favicon_default.xml7
-rw-r--r--mobile/android/base/resources/drawable/toolbar_grey_round.xml10
-rw-r--r--mobile/android/base/resources/drawable/top_sites_thumbnail_bg.xml12
-rw-r--r--mobile/android/base/resources/drawable/url_bar_bg.xml15
-rw-r--r--mobile/android/base/resources/drawable/url_bar_entry.xml37
-rw-r--r--mobile/android/base/resources/drawable/url_bar_nav_button.xml9
-rw-r--r--mobile/android/base/resources/drawable/url_bar_translating_edge.xml9
-rw-r--r--mobile/android/base/resources/drawable/widget_button_left.xml14
-rw-r--r--mobile/android/base/resources/drawable/widget_button_left_default.xml19
-rw-r--r--mobile/android/base/resources/drawable/widget_button_left_pressed.xml19
-rw-r--r--mobile/android/base/resources/drawable/widget_button_middle.xml13
-rw-r--r--mobile/android/base/resources/drawable/widget_button_middle_pressed.xml15
-rw-r--r--mobile/android/base/resources/drawable/widget_button_right.xml13
-rw-r--r--mobile/android/base/resources/drawable/widget_button_right_pressed.xml19
-rw-r--r--mobile/android/base/resources/layout-large-v11/browser_toolbar.xml153
-rw-r--r--mobile/android/base/resources/layout-large-v11/tabs_counter.xml16
-rw-r--r--mobile/android/base/resources/layout-xlarge-v11/font_size_preference.xml52
-rw-r--r--mobile/android/base/resources/layout/actionbar.xml31
-rw-r--r--mobile/android/base/resources/layout/activity_stream.xml6
-rw-r--r--mobile/android/base/resources/layout/activity_stream_card_history_item.xml123
-rw-r--r--mobile/android/base/resources/layout/activity_stream_contextmenu_bottomsheet.xml78
-rw-r--r--mobile/android/base/resources/layout/activity_stream_contextmenu_popupmenu.xml21
-rw-r--r--mobile/android/base/resources/layout/activity_stream_main_highlightstitle.xml28
-rw-r--r--mobile/android/base/resources/layout/activity_stream_main_toppanel.xml26
-rw-r--r--mobile/android/base/resources/layout/activity_stream_topsites_card.xml61
-rw-r--r--mobile/android/base/resources/layout/activity_stream_topsites_page.xml6
-rw-r--r--mobile/android/base/resources/layout/anchored_popup.xml25
-rw-r--r--mobile/android/base/resources/layout/as_content.xml8
-rw-r--r--mobile/android/base/resources/layout/autocomplete_list.xml11
-rw-r--r--mobile/android/base/resources/layout/autocomplete_list_item.xml14
-rw-r--r--mobile/android/base/resources/layout/basic_color_picker_dialog.xml21
-rw-r--r--mobile/android/base/resources/layout/bookmark_edit.xml51
-rw-r--r--mobile/android/base/resources/layout/bookmark_folder_row.xml13
-rw-r--r--mobile/android/base/resources/layout/bookmark_item_row.xml10
-rw-r--r--mobile/android/base/resources/layout/bookmark_screenshot_row.xml35
-rw-r--r--mobile/android/base/resources/layout/browser_search.xml35
-rw-r--r--mobile/android/base/resources/layout/browser_toolbar.xml112
-rw-r--r--mobile/android/base/resources/layout/button_toast.xml19
-rw-r--r--mobile/android/base/resources/layout/color_picker_row.xml13
-rw-r--r--mobile/android/base/resources/layout/customtabs_activity.xml50
-rw-r--r--mobile/android/base/resources/layout/datetime_picker.xml138
-rw-r--r--mobile/android/base/resources/layout/default_doorhanger.xml34
-rw-r--r--mobile/android/base/resources/layout/doorhanger.xml76
-rw-r--r--mobile/android/base/resources/layout/doorhanger_security.xml31
-rw-r--r--mobile/android/base/resources/layout/find_in_page_content.xml49
-rw-r--r--mobile/android/base/resources/layout/firstrun_animation_container.xml30
-rw-r--r--mobile/android/base/resources/layout/firstrun_basepanel_checkable_fragment.xml58
-rw-r--r--mobile/android/base/resources/layout/firstrun_sync_fragment.xml54
-rw-r--r--mobile/android/base/resources/layout/font_size_preference.xml54
-rw-r--r--mobile/android/base/resources/layout/gecko_app.xml177
-rw-r--r--mobile/android/base/resources/layout/history_sync_setup.xml35
-rw-r--r--mobile/android/base/resources/layout/home_banner.xml15
-rw-r--r--mobile/android/base/resources/layout/home_banner_content.xml36
-rw-r--r--mobile/android/base/resources/layout/home_bookmarks_panel.xml20
-rw-r--r--mobile/android/base/resources/layout/home_combined_back_item.xml13
-rw-r--r--mobile/android/base/resources/layout/home_combined_history_panel.xml52
-rw-r--r--mobile/android/base/resources/layout/home_empty_panel.xml45
-rw-r--r--mobile/android/base/resources/layout/home_header_row.xml7
-rw-r--r--mobile/android/base/resources/layout/home_item_row.xml10
-rw-r--r--mobile/android/base/resources/layout/home_pager.xml25
-rw-r--r--mobile/android/base/resources/layout/home_remote_tabs_group.xml60
-rw-r--r--mobile/android/base/resources/layout/home_remote_tabs_hidden_devices.xml17
-rw-r--r--mobile/android/base/resources/layout/home_search_item_row.xml12
-rw-r--r--mobile/android/base/resources/layout/home_smartfolder.xml50
-rw-r--r--mobile/android/base/resources/layout/home_suggestion_prompt.xml56
-rw-r--r--mobile/android/base/resources/layout/home_top_sites_panel.xml10
-rw-r--r--mobile/android/base/resources/layout/icon_grid.xml10
-rw-r--r--mobile/android/base/resources/layout/icon_grid_item.xml55
-rw-r--r--mobile/android/base/resources/layout/list_item_header.xml14
-rw-r--r--mobile/android/base/resources/layout/login_doorhanger.xml17
-rw-r--r--mobile/android/base/resources/layout/login_edit_dialog.xml31
-rw-r--r--mobile/android/base/resources/layout/media_casting.xml41
-rw-r--r--mobile/android/base/resources/layout/menu_action_bar.xml15
-rw-r--r--mobile/android/base/resources/layout/menu_item_switcher_layout.xml32
-rw-r--r--mobile/android/base/resources/layout/menu_popup.xml18
-rw-r--r--mobile/android/base/resources/layout/menu_secondary_action_bar.xml14
-rw-r--r--mobile/android/base/resources/layout/overlay_share_button.xml24
-rw-r--r--mobile/android/base/resources/layout/overlay_share_dialog.xml83
-rw-r--r--mobile/android/base/resources/layout/overlay_share_send_tab_item.xml10
-rw-r--r--mobile/android/base/resources/layout/panel_article_item.xml35
-rw-r--r--mobile/android/base/resources/layout/panel_auth_layout.xml39
-rw-r--r--mobile/android/base/resources/layout/panel_back_item.xml26
-rw-r--r--mobile/android/base/resources/layout/panel_icon_item.xml36
-rw-r--r--mobile/android/base/resources/layout/panel_image_item.xml44
-rw-r--r--mobile/android/base/resources/layout/panel_item_container.xml8
-rw-r--r--mobile/android/base/resources/layout/pin_site_dialog.xml43
-rw-r--r--mobile/android/base/resources/layout/preference_checkbox.xml24
-rw-r--r--mobile/android/base/resources/layout/preference_panels.xml30
-rw-r--r--mobile/android/base/resources/layout/preference_rightalign_icon.xml43
-rw-r--r--mobile/android/base/resources/layout/preference_search_engine.xml48
-rw-r--r--mobile/android/base/resources/layout/preference_search_tip.xml34
-rw-r--r--mobile/android/base/resources/layout/preference_set_homepage.xml38
-rw-r--r--mobile/android/base/resources/layout/private_tabs_panel.xml25
-rw-r--r--mobile/android/base/resources/layout/restricted_firstrun_welcome_fragment.xml73
-rw-r--r--mobile/android/base/resources/layout/search_activity_main.xml65
-rw-r--r--mobile/android/base/resources/layout/search_bar.xml43
-rw-r--r--mobile/android/base/resources/layout/search_empty.xml51
-rw-r--r--mobile/android/base/resources/layout/search_engine_bar_item.xml32
-rw-r--r--mobile/android/base/resources/layout/search_engine_bar_label.xml23
-rw-r--r--mobile/android/base/resources/layout/search_engine_row.xml30
-rw-r--r--mobile/android/base/resources/layout/search_fragment_post_search.xml30
-rw-r--r--mobile/android/base/resources/layout/search_fragment_pre_search.xml27
-rw-r--r--mobile/android/base/resources/layout/search_history_row.xml14
-rw-r--r--mobile/android/base/resources/layout/search_sugestions.xml12
-rw-r--r--mobile/android/base/resources/layout/search_suggestions_row.xml31
-rw-r--r--mobile/android/base/resources/layout/search_widget.xml67
-rw-r--r--mobile/android/base/resources/layout/select_dialog_list.xml13
-rw-r--r--mobile/android/base/resources/layout/select_dialog_multichoice.xml22
-rw-r--r--mobile/android/base/resources/layout/select_dialog_singlechoice.xml24
-rw-r--r--mobile/android/base/resources/layout/site_identity.xml101
-rw-r--r--mobile/android/base/resources/layout/site_setting_item.xml53
-rw-r--r--mobile/android/base/resources/layout/suggestion_item.xml31
-rw-r--r--mobile/android/base/resources/layout/tab_history_item_row.xml58
-rw-r--r--mobile/android/base/resources/layout/tab_history_layout.xml17
-rw-r--r--mobile/android/base/resources/layout/tab_menu_strip.xml15
-rw-r--r--mobile/android/base/resources/layout/tab_prompt_input.xml32
-rw-r--r--mobile/android/base/resources/layout/tab_queue_prompt.xml142
-rw-r--r--mobile/android/base/resources/layout/tab_queue_toast.xml38
-rw-r--r--mobile/android/base/resources/layout/tab_strip.xml10
-rw-r--r--mobile/android/base/resources/layout/tab_strip_inner.xml28
-rw-r--r--mobile/android/base/resources/layout/tab_strip_item.xml13
-rw-r--r--mobile/android/base/resources/layout/tab_strip_item_view.xml42
-rw-r--r--mobile/android/base/resources/layout/tabs_counter.xml17
-rw-r--r--mobile/android/base/resources/layout/tabs_layout_item_view.xml71
-rw-r--r--mobile/android/base/resources/layout/tabs_list_item_view.xml68
-rw-r--r--mobile/android/base/resources/layout/tabs_panel_default.xml100
-rw-r--r--mobile/android/base/resources/layout/tabs_panel_indicator.xml13
-rw-r--r--mobile/android/base/resources/layout/tabs_panel_view.xml14
-rw-r--r--mobile/android/base/resources/layout/toolbar_display_layout.xml52
-rw-r--r--mobile/android/base/resources/layout/toolbar_edit_layout.xml49
-rw-r--r--mobile/android/base/resources/layout/top_sites_grid_item_view.xml25
-rw-r--r--mobile/android/base/resources/layout/tracking_protection_prompt.xml106
-rw-r--r--mobile/android/base/resources/layout/two_line_folder_row.xml43
-rw-r--r--mobile/android/base/resources/layout/two_line_page_row.xml51
-rw-r--r--mobile/android/base/resources/layout/validation_message.xml42
-rw-r--r--mobile/android/base/resources/layout/zoomed_view.xml51
-rw-r--r--mobile/android/base/resources/menu-large/browser_app_menu.xml116
-rw-r--r--mobile/android/base/resources/menu-v11/preferences_search_menu.xml11
-rw-r--r--mobile/android/base/resources/menu-v11/tabs_menu.xml20
-rw-r--r--mobile/android/base/resources/menu-v11/titlebar_contextmenu.xml20
-rw-r--r--mobile/android/base/resources/menu-xlarge/browser_app_menu.xml117
-rw-r--r--mobile/android/base/resources/menu/activitystream_contextmenu.xml47
-rw-r--r--mobile/android/base/resources/menu/browser_app_menu.xml116
-rw-r--r--mobile/android/base/resources/menu/browsersearch_contextmenu.xml11
-rw-r--r--mobile/android/base/resources/menu/gecko_app_menu.xml10
-rw-r--r--mobile/android/base/resources/menu/home_contextmenu.xml38
-rw-r--r--mobile/android/base/resources/menu/home_remote_tabs_client_contextmenu.xml11
-rw-r--r--mobile/android/base/resources/menu/preferences_search_menu.xml10
-rw-r--r--mobile/android/base/resources/menu/tabs_menu.xml20
-rw-r--r--mobile/android/base/resources/menu/titlebar_contextmenu.xml26
-rw-r--r--mobile/android/base/resources/raw/bookmarkdefaults_favicon_addons.pngbin1815 -> 0 bytes
-rw-r--r--mobile/android/base/resources/raw/bookmarkdefaults_favicon_restricted_support.pngbin20144 -> 0 bytes
-rw-r--r--mobile/android/base/resources/raw/bookmarkdefaults_favicon_restricted_webmaker.pngbin5455 -> 0 bytes
-rw-r--r--mobile/android/base/resources/raw/bookmarkdefaults_favicon_support.pngbin19290 -> 0 bytes
-rw-r--r--mobile/android/base/resources/raw/fake_home_items.json15
-rw-r--r--mobile/android/base/resources/raw/topdomains.txt455
-rw-r--r--mobile/android/base/resources/values-land/dimens.xml11
-rw-r--r--mobile/android/base/resources/values-land/integers.xml11
-rw-r--r--mobile/android/base/resources/values-land/styles.xml32
-rw-r--r--mobile/android/base/resources/values-large-land-v11/dimens.xml12
-rw-r--r--mobile/android/base/resources/values-large-land-v11/styles.xml34
-rw-r--r--mobile/android/base/resources/values-large-v16/dimens.xml8
-rw-r--r--mobile/android/base/resources/values-large-v16/styles.xml13
-rw-r--r--mobile/android/base/resources/values-large/bool.xml11
-rw-r--r--mobile/android/base/resources/values-large/dimens.xml37
-rw-r--r--mobile/android/base/resources/values-large/integers.xml12
-rw-r--r--mobile/android/base/resources/values-large/styles.xml89
-rw-r--r--mobile/android/base/resources/values-sw240dp/dimens.xml10
-rw-r--r--mobile/android/base/resources/values-sw360dp/dimens.xml13
-rw-r--r--mobile/android/base/resources/values-sw400dp/dimens.xml10
-rw-r--r--mobile/android/base/resources/values-v11/dimens.xml13
-rw-r--r--mobile/android/base/resources/values-v11/styles.xml112
-rw-r--r--mobile/android/base/resources/values-v11/themes.xml45
-rw-r--r--mobile/android/base/resources/values-v13/search_styles.xml20
-rw-r--r--mobile/android/base/resources/values-v13/styles.xml15
-rw-r--r--mobile/android/base/resources/values-v14/themes.xml27
-rw-r--r--mobile/android/base/resources/values-v16/search_styles.xml19
-rw-r--r--mobile/android/base/resources/values-v16/styles.xml29
-rw-r--r--mobile/android/base/resources/values-v19/dimens.xml8
-rw-r--r--mobile/android/base/resources/values-v19/styles.xml41
-rw-r--r--mobile/android/base/resources/values-v21/dimens.xml8
-rw-r--r--mobile/android/base/resources/values-v21/integers.xml10
-rw-r--r--mobile/android/base/resources/values-v21/styles.xml12
-rw-r--r--mobile/android/base/resources/values-v21/themes.xml35
-rw-r--r--mobile/android/base/resources/values-w400dp/styles.xml12
-rw-r--r--mobile/android/base/resources/values-xlarge-land-v11/dimens.xml10
-rw-r--r--mobile/android/base/resources/values-xlarge-land-v11/styles.xml23
-rw-r--r--mobile/android/base/resources/values-xlarge-v11/dimens.xml11
-rw-r--r--mobile/android/base/resources/values-xlarge-v11/integers.xml13
-rw-r--r--mobile/android/base/resources/values-xlarge-v11/styles.xml27
-rw-r--r--mobile/android/base/resources/values/arrays.xml176
-rw-r--r--mobile/android/base/resources/values/attrs.xml188
-rw-r--r--mobile/android/base/resources/values/bool.xml18
-rw-r--r--mobile/android/base/resources/values/colors.xml148
-rw-r--r--mobile/android/base/resources/values/dimens.xml227
-rw-r--r--mobile/android/base/resources/values/ids.xml23
-rw-r--r--mobile/android/base/resources/values/integers.xml17
-rw-r--r--mobile/android/base/resources/values/search_attrs.xml12
-rw-r--r--mobile/android/base/resources/values/search_colors.xml24
-rw-r--r--mobile/android/base/resources/values/search_dimens.xml30
-rw-r--r--mobile/android/base/resources/values/search_styles.xml40
-rw-r--r--mobile/android/base/resources/values/styles.xml832
-rw-r--r--mobile/android/base/resources/values/themes.xml123
-rw-r--r--mobile/android/base/resources/values/vpi__attrs.xml59
-rw-r--r--mobile/android/base/resources/values/vpi__defaults.xml26
-rw-r--r--mobile/android/base/resources/xml-v11/preference_headers.xml74
-rw-r--r--mobile/android/base/resources/xml-v11/preferences_default_browser_tablet.xml11
-rw-r--r--mobile/android/base/resources/xml-v11/preferences_privacy_clear_tablet.xml16
-rw-r--r--mobile/android/base/resources/xml-v11/preferences_search.xml33
-rw-r--r--mobile/android/base/resources/xml/preference_headers.xml25
-rw-r--r--mobile/android/base/resources/xml/preferences.xml85
-rw-r--r--mobile/android/base/resources/xml/preferences_accessibility.xml35
-rw-r--r--mobile/android/base/resources/xml/preferences_advanced.xml100
-rw-r--r--mobile/android/base/resources/xml/preferences_general.xml37
-rw-r--r--mobile/android/base/resources/xml/preferences_general_tablet.xml43
-rw-r--r--mobile/android/base/resources/xml/preferences_home.xml34
-rw-r--r--mobile/android/base/resources/xml/preferences_locale.xml19
-rw-r--r--mobile/android/base/resources/xml/preferences_notifications.xml16
-rw-r--r--mobile/android/base/resources/xml/preferences_privacy.xml113
-rw-r--r--mobile/android/base/resources/xml/preferences_search.xml40
-rw-r--r--mobile/android/base/resources/xml/preferences_vendor.xml24
-rw-r--r--mobile/android/base/resources/xml/search_preferences.xml9
-rw-r--r--mobile/android/base/resources/xml/search_widget_info.xml12
-rw-r--r--mobile/android/base/resources/xml/searchable.xml7
-rw-r--r--mobile/android/base/strings.xml.in639
1297 files changed, 0 insertions, 133292 deletions
diff --git a/mobile/android/base/AdjustConstants.java.in b/mobile/android/base/AdjustConstants.java.in
deleted file mode 100644
index 6388cbbf9..000000000
--- a/mobile/android/base/AdjustConstants.java.in
+++ /dev/null
@@ -1,32 +0,0 @@
-//#filter substitution
-//#include @OBJDIR@/adjust_sdk_app_token
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.adjust.AdjustHelperInterface;
-//#ifdef MOZ_INSTALL_TRACKING
-import org.mozilla.gecko.adjust.AdjustHelper;
-//#else
-import org.mozilla.gecko.adjust.StubAdjustHelper;
-//#endif
-
-public class AdjustConstants {
- public static final String MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN =
-//#ifdef MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN
- "@MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN@";
-//#else
- null;
-//#endif
-
- public static AdjustHelperInterface getAdjustHelper() {
-//#ifdef MOZ_INSTALL_TRACKING
- return new AdjustHelper();
-//#else
- return new StubAdjustHelper();
-//#endif
- }
-}
diff --git a/mobile/android/base/AndroidManifest.xml.in b/mobile/android/base/AndroidManifest.xml.in
deleted file mode 100644
index 0352c1ab6..000000000
--- a/mobile/android/base/AndroidManifest.xml.in
+++ /dev/null
@@ -1,433 +0,0 @@
-#filter substitution
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="@ANDROID_PACKAGE_NAME@"
- android:installLocation="auto"
- android:versionCode="@ANDROID_VERSION_CODE@"
- android:versionName="@MOZ_APP_VERSION@"
-#ifdef MOZ_ANDROID_SHARED_ID
- android:sharedUserId="@MOZ_ANDROID_SHARED_ID@"
-#endif
- >
- <uses-sdk android:minSdkVersion="@MOZ_ANDROID_MIN_SDK_VERSION@"
-#ifdef MOZ_ANDROID_MAX_SDK_VERSION
- android:maxSdkVersion="@MOZ_ANDROID_MAX_SDK_VERSION@"
-#endif
- android:targetSdkVersion="23"/>
-
-<!-- The bouncer APK and the main APK should define the same set of
- <permission>, <uses-permission>, and <uses-feature> elements. This reduces
- the likelihood of permission-related surprises when installing the main APK
- on top of a pre-installed bouncer APK. Add such shared elements in the
- fileincluded here, so that they can be referenced by both APKs. -->
-#include FennecManifest_permissions.xml.in
-
- <application android:label="@string/moz_app_displayname"
- android:icon="@drawable/icon"
- android:logo="@drawable/logo"
- android:name="@MOZ_ANDROID_APPLICATION_CLASS@"
- android:hardwareAccelerated="true"
- android:allowBackup="false"
-# The preprocessor does not yet support arbitrary parentheses, so this cannot
-# be parenthesized thus to clarify that the logical AND operator has precedence:
-# !defined(MOZILLA_OFFICIAL) || (defined(NIGHTLY_BUILD) && defined(MOZ_DEBUG))
-#if !defined(MOZILLA_OFFICIAL) || defined(NIGHTLY_BUILD) && defined(MOZ_DEBUG)
- android:debuggable="true">
-#else
- android:debuggable="false">
-#endif
-
- <meta-data android:name="com.sec.android.support.multiwindow" android:value="true"/>
-
-#ifdef MOZ_NATIVE_DEVICES
- <!-- This resources comes from Google Play Services. Required for casting support. -->
- <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
- <service android:name="org.mozilla.gecko.RemotePresentationService" android:exported="false"/>
-
-#endif
-
- <!-- This activity handles all incoming Intents and dispatches them to other activities. -->
- <activity android:name="org.mozilla.gecko.LauncherActivity"
- android:theme="@android:style/Theme.Translucent.NoTitleBar"
- android:relinquishTaskIdentity="true"
- android:taskAffinity=""
- android:excludeFromRecents="true" />
-
- <!-- Fennec is shipped as the Android package named
- org.mozilla.{fennec,firefox,firefox_beta}. The internal Java
- package hierarchy inside the Android package used to have an
- org.mozilla.{fennec,firefox,firefox_beta} subtree *and* an
- org.mozilla.gecko subtree; it now only has org.mozilla.gecko. -->
- <activity android:name="@MOZ_ANDROID_BROWSER_INTENT_CLASS@"
- android:label="@string/moz_app_displayname"
- android:taskAffinity="@ANDROID_PACKAGE_NAME@.BROWSER"
- android:alwaysRetainTaskState="true"
- android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|locale|layoutDirection|smallestScreenSize|screenLayout"
- android:windowSoftInputMode="stateUnspecified|adjustResize"
- android:launchMode="singleTask"
- android:exported="true"
- android:theme="@style/Gecko.App" />
-
- <!-- Bug 1256615 / Bug 1268455: We published an .App alias and we need to maintain it
- forever. If we don't, home screen shortcuts will disappear because the intent
- filter details change. -->
- <activity-alias android:name=".App"
- android:label="@MOZ_APP_DISPLAYNAME@"
- android:targetActivity="org.mozilla.gecko.LauncherActivity">
-
- <!-- android:priority ranges between -1000 and 1000. We never want
- another activity to usurp the MAIN action, so we ratchet our
- priority up. -->
- <intent-filter android:priority="999">
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- <category android:name="android.intent.category.MULTIWINDOW_LAUNCHER"/>
- <category android:name="android.intent.category.APP_BROWSER" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
-
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
- <data android:scheme="http" />
- <data android:scheme="https" />
- <data android:scheme="about" />
- <data android:scheme="javascript" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
- <category android:name="android.intent.category.BROWSABLE" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:scheme="file" />
- <data android:scheme="http" />
- <data android:scheme="https" />
- <data android:mimeType="text/html"/>
- <data android:mimeType="text/plain"/>
- <data android:mimeType="application/xhtml+xml"/>
- </intent-filter>
-
- <meta-data android:name="com.sec.minimode.icon.portrait.normal"
- android:resource="@drawable/icon"/>
-
- <meta-data android:name="com.sec.minimode.icon.landscape.normal"
- android:resource="@drawable/icon" />
-
- <intent-filter>
- <action android:name="org.mozilla.gecko.ACTION_ALERT_CALLBACK" />
- </intent-filter>
-
- <intent-filter>
- <action android:name="org.mozilla.gecko.GUEST_SESSION_INPROGRESS" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
-
- <intent-filter>
- <action android:name="org.mozilla.gecko.UPDATE"/>
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
-
- <intent-filter>
- <action android:name="android.intent.action.WEB_SEARCH" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
- <data android:scheme="" />
- <data android:scheme="http" />
- <data android:scheme="https" />
- </intent-filter>
-
- <!-- For XPI installs from websites and the download manager. -->
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:scheme="file" />
- <data android:scheme="http" />
- <data android:scheme="https" />
- <data android:mimeType="application/x-xpinstall" />
- </intent-filter>
-
- <!-- For XPI installs from file: URLs. -->
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:host="" />
- <data android:scheme="file" />
- <data android:pathPattern=".*\\.xpi" />
- </intent-filter>
-
-#ifdef MOZ_ANDROID_BEAM
- <intent-filter>
- <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
- <category android:name="android.intent.category.DEFAULT" />
- <data android:scheme="http" />
- <data android:scheme="https" />
- </intent-filter>
-#endif
-
- <!-- For debugging -->
- <intent-filter>
- <action android:name="org.mozilla.gecko.DEBUG" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity-alias>
-
- <service android:name="org.mozilla.gecko.GeckoService" />
-
- <activity android:name="org.mozilla.gecko.trackingprotection.TrackingProtectionPrompt"
- android:launchMode="singleTop"
- android:theme="@style/OverlayActivity" />
-
- <activity android:name="org.mozilla.gecko.promotion.SimpleHelperUI"
- android:launchMode="singleTop"
- android:theme="@style/OverlayActivity" />
-
- <activity android:name="org.mozilla.gecko.promotion.HomeScreenPrompt"
- android:launchMode="singleTop"
- android:theme="@style/OverlayActivity" />
-
- <!-- The main reason for the Tab Queue build flag is to not mess with the VIEW intent filter
- before the rest of the plumbing is in place -->
-
- <service android:name="org.mozilla.gecko.tabqueue.TabQueueService" />
-
- <activity android:name="org.mozilla.gecko.tabqueue.TabQueuePrompt"
- android:launchMode="singleTop"
- android:theme="@style/OverlayActivity" />
-
- <receiver android:name="org.mozilla.gecko.restrictions.RestrictionProvider">
- <intent-filter>
- <action android:name="android.intent.action.GET_RESTRICTION_ENTRIES" />
- </intent-filter>
- </receiver>
-
- <!-- Masquerade as the Resolver so that we can be opened from the Marketplace. -->
- <activity-alias
- android:name="com.android.internal.app.ResolverActivity"
- android:targetActivity="@MOZ_ANDROID_BROWSER_INTENT_CLASS@"
- android:exported="true" />
-
- <receiver android:name="org.mozilla.gecko.GeckoUpdateReceiver">
- <intent-filter>
- <action android:name="@ANDROID_PACKAGE_NAME@.CHECK_UPDATE_RESULT" />
- </intent-filter>
- </receiver>
-
- <receiver android:name="org.mozilla.gecko.GeckoMessageReceiver"
- android:exported="false">
- </receiver>
-
- <!-- Catch install referrer so we can do post-install work. -->
- <receiver android:name="org.mozilla.gecko.distribution.ReferrerReceiver"
- android:exported="true">
- <intent-filter>
- <action android:name="com.android.vending.INSTALL_REFERRER" />
- </intent-filter>
- </receiver>
-
- <service android:name="org.mozilla.gecko.Restarter"
- android:exported="false"
- android:process="@MANGLED_ANDROID_PACKAGE_NAME@.Restarter">
- </service>
-
- <service android:name="org.mozilla.gecko.media.MediaControlService"
- android:exported="false">
- </service>
-
- <receiver android:name="org.mozilla.gecko.AlarmReceiver" >
- </receiver>
-
- <receiver
- android:name="org.mozilla.gecko.notifications.WhatsNewReceiver"
- android:exported="false">
- <intent-filter>
- <action android:name="android.intent.action.PACKAGE_REPLACED" />
- <data android:scheme="package" android:path="org.mozilla.gecko" />
- </intent-filter>
- </receiver>
-
- <receiver
- android:name="org.mozilla.gecko.notifications.NotificationReceiver"
- android:exported="false">
- <!-- Notification API V2 -->
- <intent-filter>
- <action android:name="@ANDROID_PACKAGE_NAME@.helperBroadcastAction" />
- <action android:name="@ANDROID_PACKAGE_NAME@.NOTIFICATION_CLICK" />
- <action android:name="@ANDROID_PACKAGE_NAME@.NOTIFICATION_CLOSE" />
- <data android:scheme="moz-notification" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </receiver>
-
-#include ../services/manifests/FxAccountAndroidManifest_activities.xml.in
-#ifdef MOZ_ANDROID_SEARCH_ACTIVITY
-#include ../search/manifests/SearchAndroidManifest_activities.xml.in
-#endif
-
- <activity android:name="org.mozilla.gecko.preferences.GeckoPreferences"
- android:theme="@style/Gecko.Preferences"
- android:configChanges="orientation|screenSize|locale|layoutDirection"
- android:excludeFromRecents="true"/>
-
- <provider android:name="org.mozilla.gecko.db.BrowserProvider"
- android:authorities="@ANDROID_PACKAGE_NAME@.db.browser"
- android:exported="false"/>
-
- <provider android:name="org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy"
- android:authorities="@ANDROID_PACKAGE_NAME@.partnerbookmarks"
- android:exported="false"/>
-
- <!-- Share overlay activity
-
- Setting launchMode="singleTop" ensures onNewIntent is called when the Activity is
- reused. Ideally we create a new instance but Android L breaks this (bug 1137928). -->
- <activity android:name="org.mozilla.gecko.overlays.ui.ShareDialog"
- android:label="@string/overlay_share_label"
- android:theme="@style/OverlayActivity"
- android:configChanges="keyboard|keyboardHidden|mcc|mnc|locale|layoutDirection"
- android:launchMode="singleTop"
- android:windowSoftInputMode="stateAlwaysHidden|adjustResize">
-
- <intent-filter>
- <action android:name="android.intent.action.SEND" />
- <category android:name="android.intent.category.DEFAULT" />
- <data android:mimeType="text/plain" />
- </intent-filter>
-
- </activity>
-
-#ifdef MOZ_ANDROID_CUSTOM_TABS
- <activity android:name="org.mozilla.gecko.customtabs.CustomTabsActivity"
- android:theme="@style/Theme.AppCompat.NoActionBar" />
-#endif
-
- <!-- Service to handle requests from overlays. -->
- <service android:name="org.mozilla.gecko.overlays.service.OverlayActionService" />
-
- <!--
- Ensure that passwords provider runs in its own process. (Bug 718760.)
- Process name is per-application to avoid loading CPs from multiple
- Fennec versions into the same process. (Bug 749727.)
- Process name is a mangled version to avoid a Talos bug. (Bug 750548.)
- -->
- <provider android:name="org.mozilla.gecko.db.PasswordsProvider"
- android:label="@string/sync_configure_engines_title_passwords"
- android:authorities="@ANDROID_PACKAGE_NAME@.db.passwords"
- android:exported="false"
- android:process="@MANGLED_ANDROID_PACKAGE_NAME@.PasswordsProvider"/>
-
- <provider android:name="org.mozilla.gecko.db.LoginsProvider"
- android:label="@string/sync_configure_engines_title_passwords"
- android:authorities="@ANDROID_PACKAGE_NAME@.db.logins"
- android:exported="false"/>
-
- <provider android:name="org.mozilla.gecko.db.FormHistoryProvider"
- android:label="@string/sync_configure_engines_title_history"
- android:authorities="@ANDROID_PACKAGE_NAME@.db.formhistory"
- android:exported="false"/>
-
- <provider android:name="org.mozilla.gecko.GeckoProfilesProvider"
- android:authorities="@ANDROID_PACKAGE_NAME@.profiles"
- android:exported="false"/>
-
- <provider android:name="org.mozilla.gecko.db.TabsProvider"
- android:label="@string/sync_configure_engines_title_tabs"
- android:authorities="@ANDROID_PACKAGE_NAME@.db.tabs"
- android:exported="false"/>
-
- <provider android:name="org.mozilla.gecko.db.HomeProvider"
- android:authorities="@ANDROID_PACKAGE_NAME@.db.home"
- android:exported="false"/>
-
- <provider android:name="org.mozilla.gecko.db.SearchHistoryProvider"
- android:authorities="@ANDROID_PACKAGE_NAME@.db.searchhistory"
- android:exported="false"/>
-
- <service
- android:exported="false"
- android:name="org.mozilla.gecko.updater.UpdateService"
- android:process="@MANGLED_ANDROID_PACKAGE_NAME@.UpdateService">
- </service>
-
- <service
- android:exported="false"
- android:name="org.mozilla.gecko.notifications.NotificationService">
- </service>
-
- <service
- android:exported="false"
- android:name="org.mozilla.gecko.dlc.DownloadContentService">
- </service>
-
- <service
- android:exported="false"
- android:name="org.mozilla.gecko.feeds.FeedService">
- </service>
-
- <!-- DON'T EXPORT THIS, please! An attacker could delete arbitrary files. -->
- <service
- android:exported="false"
- android:name="org.mozilla.gecko.cleanup.FileCleanupService">
- </service>
-
- <receiver
- android:name="org.mozilla.gecko.feeds.FeedAlarmReceiver"
- android:exported="false" />
-
- <receiver
- android:name="org.mozilla.gecko.BootReceiver"
- android:exported="false">
- <intent-filter>
- <action android:name="android.intent.action.BOOT_COMPLETED"></action>
- </intent-filter>
- </receiver>
-
- <receiver
- android:name="org.mozilla.gecko.PackageReplacedReceiver"
- android:exported="false">
- <intent-filter>
- <action android:name="android.intent.action.MY_PACKAGE_REPLACED"></action>
- </intent-filter>
- </receiver>
-
- <service
- android:name="org.mozilla.gecko.telemetry.TelemetryUploadService"
- android:exported="false"/>
-
-#ifdef MOZ_ANDROID_CUSTOM_TABS
- <service
- android:name="org.mozilla.gecko.customtabs.GeckoCustomTabsService"
- android:exported="true">
- <intent-filter>
- <action android:name="android.support.customtabs.action.CustomTabsService" />
- </intent-filter>
- </service>
-#endif
-
-#include ../services/manifests/FxAccountAndroidManifest_services.xml.in
-
- <service
- android:name="org.mozilla.gecko.tabqueue.TabReceivedService"
- android:exported="false" />
-
-
-#ifdef MOZ_ANDROID_SEARCH_ACTIVITY
-#include ../search/manifests/SearchAndroidManifest_services.xml.in
-#endif
-#ifdef MOZ_ANDROID_MLS_STUMBLER
-#include ../stumbler/manifests/StumblerManifest_services.xml.in
-#endif
-
-#ifdef MOZ_ANDROID_GCM
-#include GcmAndroidManifest_services.xml.in
-#endif
-
- <service
- android:name="org.mozilla.gecko.media.MediaManager"
- android:enabled="true"
- android:exported="false"
- android:process=":media"
- android:isolatedProcess="false">
- </service>
-
- </application>
-</manifest>
diff --git a/mobile/android/base/AppConstants.java.in b/mobile/android/base/AppConstants.java.in
deleted file mode 100644
index 21748e73b..000000000
--- a/mobile/android/base/AppConstants.java.in
+++ /dev/null
@@ -1,342 +0,0 @@
-//#filter substitution
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.content.Context;
-import android.os.Build;
-//#ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
-import android.support.multidex.MultiDex;
-//#endif
-
-/**
- * A collection of constants that pertain to the build and runtime state of the
- * application. Typically these are sourced from build-time definitions (see
- * Makefile.in). This is a Java-side substitute for nsIXULAppInfo, amongst
- * other things.
- *
- * See also SysInfo.java, which includes some of the values available from
- * nsSystemInfo inside Gecko.
- */
-// Normally, we'd annotate with @RobocopTarget. Since AppConstants is compiled
-// before RobocopTarget, we instead add o.m.g.AppConstants directly to the
-// Proguard configuration.
-public class AppConstants {
- public static final String ANDROID_PACKAGE_NAME = "@ANDROID_PACKAGE_NAME@";
- public static final String MANGLED_ANDROID_PACKAGE_NAME = "@MANGLED_ANDROID_PACKAGE_NAME@";
-
- public static final String MOZ_ANDROID_SHARED_FXACCOUNT_TYPE = "@ANDROID_PACKAGE_NAME@_fxaccount";
-
- /**
- * Encapsulates access to compile-time version definitions, allowing
- * for dead code removal for particular APKs.
- */
- public static final class Versions {
- public static final int MIN_SDK_VERSION = @MOZ_ANDROID_MIN_SDK_VERSION@;
- public static final int MAX_SDK_VERSION =
-//#ifdef MOZ_ANDROID_MAX_SDK_VERSION
- @MOZ_ANDROID_MAX_SDK_VERSION@;
-//#else
- 999;
-//#endif
-
- /*
- * The SDK_INT >= N check can only pass if our MAX_SDK_VERSION is
- * _greater than or equal_ to that number, because otherwise we
- * won't be installed on the device.
- *
- * If MIN_SDK_VERSION is greater than or equal to the number, there
- * is no need to do the runtime check.
- */
- public static final boolean feature16Plus = MIN_SDK_VERSION >= 16 || (MAX_SDK_VERSION >= 16 && Build.VERSION.SDK_INT >= 16);
- public static final boolean feature17Plus = MIN_SDK_VERSION >= 17 || (MAX_SDK_VERSION >= 17 && Build.VERSION.SDK_INT >= 17);
- public static final boolean feature19Plus = MIN_SDK_VERSION >= 19 || (MAX_SDK_VERSION >= 19 && Build.VERSION.SDK_INT >= 19);
- public static final boolean feature20Plus = MIN_SDK_VERSION >= 20 || (MAX_SDK_VERSION >= 20 && Build.VERSION.SDK_INT >= 20);
- public static final boolean feature21Plus = MIN_SDK_VERSION >= 21 || (MAX_SDK_VERSION >= 21 && Build.VERSION.SDK_INT >= 21);
-
- /*
- * If our MIN_SDK_VERSION is 14 or higher, we must be an ICS device.
- * If our MAX_SDK_VERSION is lower than ICS, we must not be an ICS device.
- * Otherwise, we need a range check.
- */
- public static final boolean preMarshmallow = MAX_SDK_VERSION < 23 || (MIN_SDK_VERSION < 23 && Build.VERSION.SDK_INT < 23);
- public static final boolean preLollipop = MAX_SDK_VERSION < 21 || (MIN_SDK_VERSION < 21 && Build.VERSION.SDK_INT < 21);
- public static final boolean preJBMR2 = MAX_SDK_VERSION < 18 || (MIN_SDK_VERSION < 18 && Build.VERSION.SDK_INT < 18);
- public static final boolean preJBMR1 = MAX_SDK_VERSION < 17 || (MIN_SDK_VERSION < 17 && Build.VERSION.SDK_INT < 17);
- public static final boolean preJB = MAX_SDK_VERSION < 16 || (MIN_SDK_VERSION < 16 && Build.VERSION.SDK_INT < 16);
- public static final boolean preN = MAX_SDK_VERSION < 24 || (MIN_SDK_VERSION < 24 && Build.VERSION.SDK_INT < 24);
- }
-
- /**
- * The name of the Java class that represents the android application.
- */
- public static final String MOZ_ANDROID_APPLICATION_CLASS = "@MOZ_ANDROID_APPLICATION_CLASS@";
- /**
- * The name of the Java class that launches the browser activity.
- */
- public static final String MOZ_ANDROID_BROWSER_INTENT_CLASS = "@MOZ_ANDROID_BROWSER_INTENT_CLASS@";
- /**
- * The name of the Java class that launches the search activity.
- */
- public static final String MOZ_ANDROID_SEARCH_INTENT_CLASS = "@MOZ_ANDROID_SEARCH_INTENT_CLASS@";
-
- public static final String GRE_MILESTONE = "@GRE_MILESTONE@";
-
- public static final String MOZ_APP_ABI = "@MOZ_APP_ABI@";
- public static final String MOZ_APP_BASENAME = "@MOZ_APP_BASENAME@";
-
- // For the benefit of future archaeologists:
- // GRE_BUILDID is exactly the same as MOZ_APP_BUILDID unless you're running
- // on XULRunner, which is never the case on Android.
- public static final String MOZ_APP_BUILDID = "@MOZ_BUILDID@";
- public static final String MOZ_APP_ID = "@MOZ_APP_ID@";
- public static final String MOZ_APP_NAME = "@MOZ_APP_NAME@";
- public static final String MOZ_APP_VENDOR = "@MOZ_APP_VENDOR@";
- public static final String MOZ_APP_VERSION = "@MOZ_APP_VERSION@";
- public static final String MOZ_APP_DISPLAYNAME = "@MOZ_APP_DISPLAYNAME@";
- // MOZ_APP_UA_NAME is already quoted when it gets substituted, like MOZILLA_VERSION.
- public static final String MOZ_APP_UA_NAME = @MOZ_APP_UA_NAME@;
-
- // MOZILLA_VERSION is already quoted when it gets substituted in. If we
- // add additional quotes we end up with ""x.y"", which is a syntax error.
- public static final String MOZILLA_VERSION = @MOZILLA_VERSION@;
-
- public static final String MOZ_MOZILLA_API_KEY = "@MOZ_MOZILLA_API_KEY@";
- public static final boolean MOZ_STUMBLER_BUILD_TIME_ENABLED =
-//#ifdef MOZ_ANDROID_MLS_STUMBLER
- true;
-//#else
- false;
-//#endif
-
- public static final boolean MOZ_ANDROID_GCM =
-//#ifdef MOZ_ANDROID_GCM
- true;
-//#else
- false;
-//#endif
-
- public static final String MOZ_ANDROID_GCM_SENDERID =
-//#ifdef MOZ_ANDROID_GCM_SENDERID
- "@MOZ_ANDROID_GCM_SENDERID@";
-//#else
- null;
-//#endif
-
- public static final String MOZ_CHILD_PROCESS_NAME = "@MOZ_CHILD_PROCESS_NAME@";
- public static final String MOZ_UPDATE_CHANNEL = "@MOZ_UPDATE_CHANNEL@";
- public static final String OMNIJAR_NAME = "@OMNIJAR_NAME@";
- public static final String OS_TARGET = "@OS_TARGET@";
- public static final String TARGET_XPCOM_ABI = @TARGET_XPCOM_ABI@;
-
- public static final String USER_AGENT_BOT_LIKE = "Redirector/" + AppConstants.MOZ_APP_VERSION +
- " (Android; rv:" + AppConstants.MOZ_APP_VERSION + ")";
-
- public static final String USER_AGENT_FENNEC_MOBILE = "Mozilla/5.0 (Android " +
- Build.VERSION.RELEASE + "; Mobile; rv:" +
- AppConstants.MOZ_APP_VERSION + ") Gecko/" +
- AppConstants.MOZ_APP_VERSION + " Firefox/" +
- AppConstants.MOZ_APP_VERSION;
-
- public static final String USER_AGENT_FENNEC_TABLET = "Mozilla/5.0 (Android " +
- Build.VERSION.RELEASE + "; Tablet; rv:" +
- AppConstants.MOZ_APP_VERSION + ") Gecko/" +
- AppConstants.MOZ_APP_VERSION + " Firefox/" +
- AppConstants.MOZ_APP_VERSION;
-
- public static final int MOZ_MIN_CPU_VERSION = @MOZ_MIN_CPU_VERSION@;
-
- public static final boolean MOZ_ANDROID_ANR_REPORTER =
-//#ifdef MOZ_ANDROID_ANR_REPORTER
- true;
-//#else
- false;
-//#endif
-
- public static final String MOZ_PKG_SPECIAL =
-//#ifdef MOZ_PKG_SPECIAL
- "@MOZ_PKG_SPECIAL@";
-//#else
- null;
-//#endif
-
- public static final boolean MOZ_EXCLUDE_HYPHENATION_DICTIONARIES =
-//#ifdef MOZ_EXCLUDE_HYPHENATION_DICTIONARIES
- true;
-//#else
- false;
-//#endif
-
- public static final boolean MOZ_SERVICES_HEALTHREPORT =
-//#ifdef MOZ_SERVICES_HEALTHREPORT
- true;
-//#else
- false;
-//#endif
-
- public static final boolean MOZ_TELEMETRY_ON_BY_DEFAULT =
-//#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
- true;
-//#else
- false;
-//#endif
-
- public static final String TELEMETRY_PREF_NAME =
- "toolkit.telemetry.enabled";
-
- public static final boolean MOZ_TELEMETRY_REPORTING =
-//#ifdef MOZ_TELEMETRY_REPORTING
- true;
-//#else
- false;
-//#endif
-
- public static final boolean MOZ_DATA_REPORTING =
-//#ifdef MOZ_DATA_REPORTING
- true;
-//#else
- false;
-//#endif
-
- public static final boolean MOZ_LOCALE_SWITCHER =
-//#ifdef MOZ_LOCALE_SWITCHER
- true;
-//#else
- false;
-//#endif
-
- public static final boolean MOZ_UPDATER =
-//#ifdef MOZ_UPDATER
- true;
-//#else
- false;
-//#endif
-
- // Android Beam is only supported on API14+, so we don't even bother building
- // it if this APK doesn't include API14 support.
- public static final boolean MOZ_ANDROID_BEAM =
-//#ifdef MOZ_ANDROID_BEAM
- true;
-//#else
- false;
-//#endif
-
- // See this wiki page for more details about channel specific build defines:
- // https://wiki.mozilla.org/Platform/Channel-specific_build_defines
- public static final boolean RELEASE_OR_BETA =
-//#ifdef RELEASE_OR_BETA
- true;
-//#else
- false;
-//#endif
-
- public static final boolean NIGHTLY_BUILD =
-//#ifdef NIGHTLY_BUILD
- true;
-//#else
- false;
-//#endif
-
- public static final boolean DEBUG_BUILD =
-//#ifdef MOZ_DEBUG
- true;
-//#else
- false;
-//#endif
-
- public static final boolean MOZ_MEDIA_PLAYER =
-//#ifdef MOZ_NATIVE_DEVICES
- true;
-//#else
- false;
-//#endif
-
- // Official corresponds, roughly, to whether this build is performed on
- // Mozilla's continuous integration infrastructure. You should disable
- // developer-only functionality when this flag is set.
- public static final boolean MOZILLA_OFFICIAL =
-//#ifdef MOZILLA_OFFICIAL
- true;
-//#else
- false;
-//#endif
-
- public static final boolean ANDROID_DOWNLOADS_INTEGRATION =
-//#ifdef MOZ_ANDROID_DOWNLOADS_INTEGRATION
- true;
-//#else
- false;
-//#endif
-
- public static final boolean MOZ_DRAGGABLE_URLBAR = false;
-
- public static final boolean MOZ_INSTALL_TRACKING =
-//#ifdef MOZ_INSTALL_TRACKING
- true;
-//#else
- false;
-//#endif
-
- public static final boolean MOZ_SWITCHBOARD =
-//#ifdef MOZ_SWITCHBOARD
- true;
-//#else
- false;
-//#endif
-
- /**
- * Target CPU architecture: "armeabi-v7a", "x86, "mips", ..
- */
- public static final String ANDROID_CPU_ARCH = "@ANDROID_CPU_ARCH@";
-
- public static final boolean MOZ_ANDROID_EXCLUDE_FONTS =
-//#ifdef MOZ_ANDROID_EXCLUDE_FONTS
- true;
-//#else
- false;
-//#endif
-
- public static final boolean MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE =
-//#ifdef MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE
- true;
-//#else
- false;
-//#endif
-
- public static final boolean MOZ_ANDROID_CUSTOM_TABS =
-//#ifdef MOZ_ANDROID_CUSTOM_TABS
- true;
-//#else
- false;
-//#endif
-
- // (bug 1266820) Temporarily disabled since no one is working on it.
- public static final boolean SCREENSHOTS_IN_BOOKMARKS_ENABLED = false;
-
- /**
- * Enables multidex depending on build flags. For more information,
- * see `multiDexEnabled true` in mobile/android/app/build.gradle.
- *
- * As a method, this shouldn't be in AppConstants, but it's
- * the only semi-relevant Java file that we pre-process.
- */
- public static void maybeInstallMultiDex(final Context context) {
-//#ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
- if (BuildConfig.FLAVOR.equals("automation")) {
- MultiDex.install(context);
- }
-//#else
- // Do nothing.
-//#endif
- }
-
- public static final boolean MOZ_ANDROID_ACTIVITY_STREAM =
-//#ifdef MOZ_ANDROID_ACTIVITY_STREAM
- true;
-//#else
- false;
-//#endif
-}
diff --git a/mobile/android/base/FennecManifest_permissions.xml.in b/mobile/android/base/FennecManifest_permissions.xml.in
deleted file mode 100644
index cf3365582..000000000
--- a/mobile/android/base/FennecManifest_permissions.xml.in
+++ /dev/null
@@ -1,65 +0,0 @@
-<!-- The bouncer APK and the Fennec APK should define the same set of
- <permission>, <uses-permission>, and <uses-feature> elements. This reduces
- the likelihood of permission-related surprises when installing the main APK
- on top of a pre-installed bouncer APK. Add such elements here, so that
- they can be easily shared between the two APKs. -->
-
-#include ../services/manifests/FxAccountAndroidManifest_permissions.xml.in
-
-#ifdef MOZ_ANDROID_SEARCH_ACTIVITY
-#include ../search/manifests/SearchAndroidManifest_permissions.xml.in
-#endif
-
-<!-- Bug 1261302: we have two new permissions to request,
- RECEIVE_BOOT_COMPLETED and the permission for push. We want to ask for
- them during the same release, which should be Fennec 48. Therefore we
- decouple the push permission from MOZ_ANDROID_GCM to let it ride ahead
- (potentially) of the push feature. -->
-#include GcmAndroidManifest_permissions.xml.in
-
- <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
- <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
- <uses-permission android:name="android.permission.INTERNET"/>
- <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
- <!-- READ_EXTERNAL_STORAGE was added in API 16, and is only enforced in API
- 19+. We declare it so that the bouncer APK and the main APK have the
- same set of permissions. -->
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
- <uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"/>
- <uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS"/>
-
- <uses-permission android:name="android.permission.WAKE_LOCK"/>
- <uses-permission android:name="android.permission.VIBRATE"/>
-#ifdef MOZ_ANDROID_DOWNLOADS_INTEGRATION
- <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
-#endif
-
- <uses-feature android:name="android.hardware.location" android:required="false"/>
- <uses-feature android:name="android.hardware.location.gps" android:required="false"/>
- <uses-feature android:name="android.hardware.touchscreen"/>
-
- <!-- Tab Queue -->
- <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
-
-#ifdef MOZ_ANDROID_BEAM
- <!-- Android Beam support -->
- <uses-permission android:name="android.permission.NFC"/>
- <uses-feature android:name="android.hardware.nfc" android:required="false"/>
-#endif
-
-#ifdef MOZ_WEBRTC
- <uses-permission android:name="android.permission.RECORD_AUDIO"/>
- <uses-feature android:name="android.hardware.audio.low_latency" android:required="false"/>
- <uses-feature android:name="android.hardware.camera.any" android:required="false"/>
- <uses-feature android:name="android.hardware.microphone" android:required="false"/>
-#endif
- <uses-permission android:name="android.permission.CAMERA" />
- <uses-feature android:name="android.hardware.camera" android:required="false"/>
- <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
-
- <!-- App requires OpenGL ES 2.0 -->
- <uses-feature android:glEsVersion="0x00020000" android:required="true" />
diff --git a/mobile/android/base/GcmAndroidManifest_permissions.xml.in b/mobile/android/base/GcmAndroidManifest_permissions.xml.in
deleted file mode 100644
index b6fe5fffa..000000000
--- a/mobile/android/base/GcmAndroidManifest_permissions.xml.in
+++ /dev/null
@@ -1,4 +0,0 @@
- <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
- <!-- Avoid a linter warning by not double-including WAKE_LOCK.
- <uses-permission android:name="android.permission.WAKE_LOCK" />
- -->
diff --git a/mobile/android/base/GcmAndroidManifest_services.xml.in b/mobile/android/base/GcmAndroidManifest_services.xml.in
deleted file mode 100644
index de1c77b3b..000000000
--- a/mobile/android/base/GcmAndroidManifest_services.xml.in
+++ /dev/null
@@ -1,29 +0,0 @@
- <!-- Handle GCM registration updates from on-device Google Play Services. -->
- <service
- android:name="org.mozilla.gecko.gcm.GcmInstanceIDListenerService"
- android:exported="false">
- <intent-filter>
- <action android:name="com.google.android.gms.iid.InstanceID"/>
- </intent-filter>
- </service>
-
- <!-- Provided by on-device Google Play Services. Directs inbound messages to internal listener service. -->
- <receiver
- android:name="com.google.android.gms.gcm.GcmReceiver"
- android:exported="true"
- android:permission="com.google.android.c2dm.permission.SEND">
- <intent-filter>
- <action android:name="com.google.android.c2dm.intent.RECEIVE" />
- <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
- <category android:name="@ANDROID_PACKAGE_NAME@" />
- </intent-filter>
- </receiver>
-
- <!-- Handle messages directed by the GCM receiver. -->
- <service
- android:name="org.mozilla.gecko.gcm.GcmMessageListenerService"
- android:exported="false">
- <intent-filter>
- <action android:name="com.google.android.c2dm.intent.RECEIVE" />
- </intent-filter>
- </service>
diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in
deleted file mode 100644
index fe2f9d3ec..000000000
--- a/mobile/android/base/Makefile.in
+++ /dev/null
@@ -1,594 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-# We call mach -> Make -> gradle -> mach, which races to find and
-# create .mozconfig files and to generate targets.
-ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
-.NOTPARALLEL:
-endif
-
-MOZ_BUILDID := $(shell awk '{print $$3}' $(DEPTH)/buildid.h)
-
-# Set the appropriate version code, based on the existance of the
-# MOZ_APP_ANDROID_VERSION_CODE variable.
-ifdef MOZ_APP_ANDROID_VERSION_CODE
- ANDROID_VERSION_CODE:=$(MOZ_APP_ANDROID_VERSION_CODE)
-else
- ANDROID_VERSION_CODE:=$(shell $(PYTHON) \
- $(topsrcdir)/python/mozbuild/mozbuild/android_version_code.py \
- --verbose \
- --with-android-cpu-arch=$(ANDROID_CPU_ARCH) \
- $(if $(MOZ_ANDROID_MIN_SDK_VERSION),--with-android-min-sdk=$(MOZ_ANDROID_MIN_SDK_VERSION)) \
- $(if $(MOZ_ANDROID_MAX_SDK_VERSION),--with-android-max-sdk=$(MOZ_ANDROID_MAX_SDK_VERSION)) \
- $(MOZ_BUILDID))
-endif
-
-DEFINES += \
- -DANDROID_VERSION_CODE=$(ANDROID_VERSION_CODE) \
- -DMOZ_ANDROID_SHARED_ID="$(MOZ_ANDROID_SHARED_ID)" \
- -DMOZ_BUILDID=$(MOZ_BUILDID) \
- $(NULL)
-
-GARBAGE += \
- classes.dex \
- gecko.ap_ \
- res/values/strings.xml \
- res/raw/browsersearch.json \
- res/raw/suggestedsites.json \
- .aapt.deps \
- GeneratedJNINatives.h \
- GeneratedJNIWrappers.cpp \
- GeneratedJNIWrappers.h \
- FennecJNINatives.h \
- FennecJNIWrappers.cpp \
- FennecJNIWrappers.h \
- $(NULL)
-
-GARBAGE_DIRS += classes db jars res sync services generated
-
-# The bootclasspath is functionally identical to the classpath, but allows the
-# classes given to redefine classes in core packages, such as java.lang.
-# android.jar is here as it provides Android's definition of the Java Standard
-# Library. The compatability lib here tweaks a few of the core classes to paint
-# over changes in behaviour between versions.
-JAVA_BOOTCLASSPATH := \
- $(ANDROID_SDK)/android.jar \
- $(NULL)
-
-JAVA_BOOTCLASSPATH := $(subst $(NULL) ,:,$(strip $(JAVA_BOOTCLASSPATH)))
-
-JAVA_CLASSPATH += \
- $(ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB) \
- $(ANDROID_SUPPORT_V4_AAR_LIB) \
- $(ANDROID_SUPPORT_V4_AAR_INTERNAL_LIB) \
- $(ANDROID_APPCOMPAT_V7_AAR_LIB) \
- $(ANDROID_SUPPORT_VECTOR_DRAWABLE_AAR_LIB) \
- $(ANDROID_ANIMATED_VECTOR_DRAWABLE_AAR_LIB) \
- $(ANDROID_CARDVIEW_V7_AAR_LIB) \
- $(ANDROID_DESIGN_AAR_LIB) \
- $(ANDROID_RECYCLERVIEW_V7_AAR_LIB) \
- $(ANDROID_CUSTOMTABS_AAR_LIB) \
- $(ANDROID_PALETTE_V7_AAR_LIB) \
- $(NULL)
-
-# If native devices are enabled, add Google Play Services and some of the v7
-# compat libraries.
-ifdef MOZ_NATIVE_DEVICES
- JAVA_CLASSPATH += \
- $(ANDROID_PLAY_SERVICES_BASE_AAR_LIB) \
- $(ANDROID_PLAY_SERVICES_BASEMENT_AAR_LIB) \
- $(ANDROID_PLAY_SERVICES_CAST_AAR_LIB) \
- $(ANDROID_MEDIAROUTER_V7_AAR_LIB) \
- $(ANDROID_MEDIAROUTER_V7_AAR_INTERNAL_LIB) \
- $(NULL)
-endif
-
-ifdef MOZ_ANDROID_GCM
- JAVA_CLASSPATH += \
- $(ANDROID_PLAY_SERVICES_BASE_AAR_LIB) \
- $(ANDROID_PLAY_SERVICES_BASEMENT_AAR_LIB) \
- $(ANDROID_PLAY_SERVICES_GCM_AAR_LIB) \
- $(ANDROID_PLAY_SERVICES_MEASUREMENT_AAR_LIB) \
- $(NULL)
-endif
-
-ifdef MOZ_INSTALL_TRACKING
- JAVA_CLASSPATH += \
- $(ANDROID_PLAY_SERVICES_ADS_AAR_LIB) \
- $(ANDROID_PLAY_SERVICES_BASEMENT_AAR_LIB) \
- $(NULL)
-endif
-
-JAVA_CLASSPATH := $(subst $(NULL) ,:,$(strip $(JAVA_CLASSPATH)))
-
-# Library jars that we're bundling: these are subject to Proguard before inclusion
-# into classes.dex.
-java_bundled_libs := \
- $(ANDROID_SUPPORT_V4_AAR_LIB) \
- $(ANDROID_SUPPORT_V4_AAR_INTERNAL_LIB) \
- $(ANDROID_APPCOMPAT_V7_AAR_LIB) \
- $(ANDROID_SUPPORT_VECTOR_DRAWABLE_AAR_LIB) \
- $(ANDROID_ANIMATED_VECTOR_DRAWABLE_AAR_LIB) \
- $(ANDROID_CARDVIEW_V7_AAR_LIB) \
- $(ANDROID_DESIGN_AAR_LIB) \
- $(ANDROID_RECYCLERVIEW_V7_AAR_LIB) \
- $(ANDROID_CUSTOMTABS_AAR_LIB) \
- $(ANDROID_PALETTE_V7_AAR_LIB) \
- $(NULL)
-
-ifdef MOZ_NATIVE_DEVICES
- java_bundled_libs += \
- $(ANDROID_PLAY_SERVICES_BASE_AAR_LIB) \
- $(ANDROID_PLAY_SERVICES_BASEMENT_AAR_LIB) \
- $(ANDROID_PLAY_SERVICES_CAST_AAR_LIB) \
- $(ANDROID_MEDIAROUTER_V7_AAR_LIB) \
- $(ANDROID_MEDIAROUTER_V7_AAR_INTERNAL_LIB) \
- $(NULL)
-endif
-
-ifdef MOZ_ANDROID_GCM
- java_bundled_libs += \
- $(ANDROID_PLAY_SERVICES_BASE_AAR_LIB) \
- $(ANDROID_PLAY_SERVICES_BASEMENT_AAR_LIB) \
- $(ANDROID_PLAY_SERVICES_GCM_AAR_LIB) \
- $(ANDROID_PLAY_SERVICES_MEASUREMENT_AAR_LIB) \
- $(NULL)
-endif
-
-ifdef MOZ_INSTALL_TRACKING
- java_bundled_libs += \
- $(ANDROID_PLAY_SERVICES_ADS_AAR_LIB) \
- $(ANDROID_PLAY_SERVICES_BASEMENT_AAR_LIB) \
- $(NULL)
-endif
-
-# uniq purloined from http://stackoverflow.com/a/16151140.
-uniq = $(if $1,$(firstword $1) $(call uniq,$(filter-out $(firstword $1),$1)))
-
-java_bundled_libs := $(call uniq,$(java_bundled_libs))
-java_bundled_libs := $(subst $(NULL) ,:,$(strip $(java_bundled_libs)))
-
-GECKOVIEW_JARS = \
- constants.jar \
- gecko-R.jar \
- gecko-mozglue.jar \
- gecko-util.jar \
- gecko-view.jar \
- sync-thirdparty.jar \
- $(NULL)
-
-ifdef MOZ_INSTALL_TRACKING
-GECKOVIEW_JARS += gecko-thirdparty-adjust_sdk.jar
-endif
-
-geckoview_jars_classpath := $(subst $(NULL) ,:,$(strip $(GECKOVIEW_JARS)))
-
-FENNEC_JARS = \
- gecko-browser.jar \
- gecko-thirdparty.jar \
- services.jar \
- ../javaaddons/javaaddons-1.0.jar \
- $(NULL)
-
-ifdef MOZ_WEBRTC
-FENNEC_JARS += webrtc.jar
-endif
-
-ifdef MOZ_ANDROID_SEARCH_ACTIVITY
-FENNEC_JARS += search-activity.jar
-endif
-
-ifdef MOZ_ANDROID_MLS_STUMBLER
-FENNEC_JARS += ../stumbler/stumbler.jar
-endif
-
-# All the jars we're compiling from source. (not to be confused with
-# java_bundled_libs, which holds the jars which we're including as binaries).
-ALL_JARS = \
- $(GECKOVIEW_JARS) \
- $(FENNEC_JARS) \
- $(NULL)
-
-# The list of jars in Java classpath notation (colon-separated).
-all_jars_classpath := $(subst $(NULL) ,:,$(strip $(ALL_JARS)))
-
-include $(topsrcdir)/config/config.mk
-
-library_jars := \
- $(ANDROID_SDK)/android.jar \
- $(NULL)
-
-# Android 23 (Marshmallow) deprecated a part of the Android platform, namely the
-# org.apache.http package. Fennec removed all code that referenced said package
-# in order to easily ship to Android 23 devices (and, by extension, all devices
-# before Android 23).
-#
-# Google did not remove all code that referenced said package in their own
-# com.google.android.gms namespace! It turns out that the org.apache.http
-# package is not removed, only deprecated and hidden by default. Google added a
-# a `useLibrary` Gradle directive that allows legacy code to reference the
-# package, which is still (hidden) in the Android 23 platform.
-#
-# Fennec code doesn't need to compile against the deprecated package, since our
-# code doesn't reference the package anymore. However, we do need to Proguard
-# against the deprecated package. If we don't, Proguard -- which is a global
-# optimization -- sees Google libraries referencing "non-existent" libraries and
-# complains. The solution is to mimic the `useLibraries` directive by declaring
-# the legacy package as a provided library jar.
-#
-# See https://bugzilla.mozilla.org/show_bug.cgi?id=1233238#c19 for symptoms and
-# more discussion.
-ifdef MOZ_INSTALL_TRACKING
-library_jars += $(ANDROID_SDK)/optional/org.apache.http.legacy.jar
-endif # MOZ_INSTALL_TRACKING
-
-library_jars := $(subst $(NULL) ,:,$(strip $(library_jars)))
-
-gradle_dir := $(topobjdir)/gradle/build/mobile/android
-
-ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
-.gradle.deps: .aapt.deps FORCE
- @$(TOUCH) $@
- $(topsrcdir)/mach gradle \
- app:assembleAutomationDebug app:assembleAutomationDebugAndroidTest -x lint
-
-classes.dex: .gradle.deps
- $(REPORT_BUILD)
- cp $(gradle_dir)/app/intermediates/transforms/dex/automation/debug/folders/1000/1f/main/classes.dex $@
-else
-classes.dex: .proguard.deps
- $(REPORT_BUILD)
- $(DX) --dex --output=classes.dex jars-proguarded
-endif
-
-ifdef MOZ_DISABLE_PROGUARD
- PROGUARD_PASSES=0
-else
- ifdef MOZ_DEBUG
- PROGUARD_PASSES=1
- else
- ifndef MOZILLA_OFFICIAL
- PROGUARD_PASSES=1
- else
- PROGUARD_PASSES=6
- endif
- endif
-endif
-
-proguard_config_dir=$(topsrcdir)/mobile/android/config/proguard
-
-# This stanza ensures that the set of GeckoView classes does not depend on too
-# much of Fennec, where "too much" is defined as the set of potentially
-# non-GeckoView classes that GeckoView already depended on at a certain point in
-# time. The idea is to set a high-water mark that is not to be crossed.
-classycle_jar := $(topsrcdir)/mobile/android/build/classycle/classycle-1.4.1.jar
-.geckoview.deps: geckoview.ddf $(classycle_jar) $(ALL_JARS)
- $(JAVA) -cp $(classycle_jar) \
- classycle.dependency.DependencyChecker \
- -mergeInnerClasses \
- -dependencies=@$< \
- $(ALL_JARS)
- @$(TOUCH) $@
-
-# First, we delete debugging information from libraries. Having line-number
-# information for libraries for which we lack the source isn't useful, so this
-# saves us a bit of space. Importantly, Proguard has a bug causing it to
-# sometimes corrupt this information if present (which it does for some of the
-# included libraries). This corruption prevents dex from completing, so we need
-# to get rid of it. This prevents us from seeing line numbers in stack traces
-# for stack frames inside libraries.
-#
-# This step can occur much earlier than the main Proguard pass: it needs only
-# gecko-R.jar to have been compiled (as that's where the library R.java files
-# end up), but it does block the main Proguard pass.
-.bundled.proguard.deps: gecko-R.jar $(proguard_config_dir)/strip-libs.cfg
- $(REPORT_BUILD)
- @$(TOUCH) $@
- $(JAVA) \
- -Xmx512m -Xms128m \
- -jar $(ANDROID_SDK_ROOT)/tools/proguard/lib/proguard.jar \
- @$(proguard_config_dir)/strip-libs.cfg \
- -injars $(subst ::,:,$(java_bundled_libs))\
- -outjars bundled-jars-nodebug \
- -libraryjars $(library_jars):gecko-R.jar
-
-# We touch the target file before invoking Proguard so that Proguard's
-# outputs are fresher than the target, preventing a subsequent
-# invocation from thinking Proguard's outputs are stale. This is safe
-# because Make removes the target file if any recipe command fails.
-.proguard.deps: .geckoview.deps .bundled.proguard.deps $(ALL_JARS) $(proguard_config_dir)/proguard.cfg
- $(REPORT_BUILD)
- @$(TOUCH) $@
- $(JAVA) \
- -Xmx512m -Xms128m \
- -jar $(ANDROID_SDK_ROOT)/tools/proguard/lib/proguard.jar \
- @$(proguard_config_dir)/proguard.cfg \
- -optimizationpasses $(PROGUARD_PASSES) \
- -injars $(subst ::,:,$(all_jars_classpath)):bundled-jars-nodebug \
- -outjars jars-proguarded \
- -libraryjars $(library_jars)
-
-ANNOTATION_PROCESSOR_JAR_FILES := $(DEPTH)/build/annotationProcessors/annotationProcessors.jar
-
-# This annotation processing step also generates
-# GeneratedJNIWrappers.h and GeneratedJNINatives.h
-GeneratedJNIWrappers.cpp: $(ANNOTATION_PROCESSOR_JAR_FILES) $(GECKOVIEW_JARS)
- $(JAVA) -classpath $(geckoview_jars_classpath):$(JAVA_BOOTCLASSPATH):$(JAVA_CLASSPATH):$(ANNOTATION_PROCESSOR_JAR_FILES) \
- org.mozilla.gecko.annotationProcessors.AnnotationProcessor \
- Generated $(GECKOVIEW_JARS)
-
-# This annotation processing step also generates
-# FennecJNIWrappers.h and FennecJNINatives.h
-FennecJNIWrappers.cpp: $(ANNOTATION_PROCESSOR_JAR_FILES) $(FENNEC_JARS)
- $(JAVA) -classpath $(all_jars_classpath):$(JAVA_BOOTCLASSPATH):$(JAVA_CLASSPATH):$(ANNOTATION_PROCESSOR_JAR_FILES) \
- org.mozilla.gecko.annotationProcessors.AnnotationProcessor \
- Fennec $(FENNEC_JARS)
-
-# Certain source files need to be preprocessed. This special rule
-# generates these files into generated/org/mozilla/gecko for
-# consumption by the build system and IDEs.
-
-# The list in moz.build looks like
-# 'preprocessed/org/mozilla/gecko/AppConstants.java'. The list in
-# constants_PP_JAVAFILES looks like
-# 'generated/preprocessed/org/mozilla/gecko/AppConstants.java'. We
-# need to write AppConstants.java.in to
-# generated/preprocessed/org/mozilla/gecko.
-preprocessed := $(addsuffix .in,$(subst generated/preprocessed/org/mozilla/gecko/,,$(filter generated/preprocessed/org/mozilla/gecko/%,$(constants_PP_JAVAFILES))))
-
-preprocessed_PATH := generated/preprocessed/org/mozilla/gecko
-preprocessed_KEEP_PATH := 1
-preprocessed_FLAGS := --marker='//\\\#'
-
-PP_TARGETS += preprocessed
-
-include $(topsrcdir)/config/rules.mk
-
-not_android_res_files := \
- *.mkdir.done* \
- *.DS_Store* \
- *\#* \
- *.rej \
- *.orig \
- $(NULL)
-
-# This uses the fact that Android resource directories list all
-# resource files one subdirectory below the parent resource directory.
-android_res_files := $(filter-out $(not_android_res_files),$(wildcard $(addsuffix /*,$(wildcard $(addsuffix /*,$(ANDROID_RES_DIRS))))))
-
-$(ANDROID_GENERATED_RESFILES): $(call mkdir_deps,$(sort $(dir $(ANDROID_GENERATED_RESFILES))))
-
-# [Comment 1/3] We don't have correct dependencies for strings.xml at
-# this point, so we always recursively invoke the submake to check the
-# dependencies. Sigh. And, with multilocale builds, there will be
-# multiple strings.xml files, and we need to rebuild gecko.ap_ if any
-# of them change. But! mobile/android/base/locales does not have
-# enough information to actually build res/values/strings.xml during a
-# language repack. So rather than adding rules into the main
-# makefile, and trying to work around the lack of information, we
-# force a rebuild of gecko.ap_ during packaging. See below.
-
-# Since the sub-Make is forced, it doesn't matter that we touch the
-# target file before the command. If in the future we stop forcing
-# the sub-Make, touching the target file first is better, because the
-# sub-Make outputs will be fresher than the target, and not require
-# rebuilding. This is all safe because Make removes the target file
-# if any recipe command fails. It is crucial that the sub-Make touch
-# the target files (those depending on .locales.deps) only when there
-# contents have changed; otherwise, this will force rebuild them as
-# part of every build.
-.locales.deps: FORCE
- $(TOUCH) $@
- $(MAKE) -C locales
-
-
-# This .deps pattern saves an invocation of the sub-Make: the single
-# invocation generates strings.xml, browsersearch.json, and
-# suggestedsites.json. The trailing semi-colon defines an empty
-# recipe: defining no recipe at all causes Make to treat the target
-# differently, in a way that defeats our dependencies.
-res/values/strings.xml: .locales.deps ;
-res/raw/browsersearch.json: .locales.deps ;
-res/raw/suggestedsites.json: .locales.deps ;
-
-all_resources = \
- $(DEPTH)/mobile/android/base/AndroidManifest.xml \
- $(android_res_files) \
- $(ANDROID_GENERATED_RESFILES) \
- $(NULL)
-
-# All of generated/org/mozilla/gecko/R.java, gecko.ap_, and R.txt are
-# produced by aapt; this saves aapt invocations. The trailing
-# semi-colon defines an empty recipe; defining no recipe at all causes
-# Make to treat the target differently, in a way that defeats our
-# dependencies.
-
-generated/org/mozilla/gecko/R.java: .aapt.deps ;
-
-# Only add libraries that contain resources here. We (unecessarily) generate an identical R.java which
-# is copied into each of these locations, and each of these files contains thousands of fields.
-# Each unnecessary copy therefore wastes unnecessary fields in the output dex file.
-# Note: usually proguard will help clean this up after the fact, but having too many fields will cause
-# dexing to fail, regardless of any later optimisations proguard could later make to bring us back
-# under the limit.
-# Ideally we would fix our aapt invocations to correctly generate minimal copies of R.java for each
-# package, but that seems redundant since gradle builds are able to correctly generate these files.
-
-# If native devices are enabled, add Google Play Services, build their resources
-# (no resources) generated/android/support/v4/R.java: .aapt.deps ;
-generated/android/support/v7/appcompat/R.java: .aapt.deps ;
-# (no resources) generated/android/support/graphics/drawable/animated/R.java: .aapt.deps ;
-# (no resources) generated/android/support/graphics/drawable/R.java: .aapt.deps ;
-generated/android/support/v7/cardview/R.java: .aapt.deps ;
-generated/android/support/design/R.java: .aapt.deps ;
-generated/android/support/v7/mediarouter/R.java: .aapt.deps ;
-generated/android/support/v7/recyclerview/R.java: .aapt.deps ;
-# (no resources) generated/android/support/customtabs/R.java: .aapt.deps ;
-# (no resources) generated/android/support/v7/palette/R.java: .aapt.deps ;
-generated/com/google/android/gms/R.java: .aapt.deps ;
-generated/com/google/android/gms/ads/R.java: .aapt.deps ;
-generated/com/google/android/gms/base/R.java: .aapt.deps ;
-generated/com/google/android/gms/cast/R.java: .aapt.deps ;
-# (no resources) generated/com/google/android/gms/gcm/R.java: .aapt.deps ;
-# (no resources) generated/com/google/android/gms/measurement/R.java: .aapt.deps ;
-
-gecko.ap_: .aapt.deps ;
-R.txt: .aapt.deps ;
-
-# [Comment 2/3] This tom-foolery provides a target that forces a
-# rebuild of gecko.ap_. This is used during packaging to ensure that
-# resources are fresh. The alternative would be complicated; see
-# [Comment 1/3].
-
-gecko-nodeps/R.java: .aapt.nodeps ;
-gecko-nodeps.ap_: .aapt.nodeps ;
-gecko-nodeps/R.txt: .aapt.nodeps ;
-
-# This ignores the default set of resources ignored by aapt, plus
-# files starting with '#'. (Emacs produces temp files named #temp#.)
-# This doesn't actually set the environment variable; it's used as a
-# parameter in the aapt invocation below. Consider updating
-# not_android_res_files as well.
-
-ANDROID_AAPT_IGNORE := !.svn:!.git:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*.scc:*~:\#*:*.rej:*.orig
-
-# 1: target file.
-# 2: dependencies.
-# 3: name of ap_ file to write.
-# 4: directory to write R.java into.
-# 5: directory to write R.txt into.
-# We touch the target file before invoking aapt so that aapt's outputs
-# are fresher than the target, preventing a subsequent invocation from
-# thinking aapt's outputs are stale. This is safe because Make
-# removes the target file if any recipe command fails.
-
-define aapt_command
-$(1): $$(call mkdir_deps,$(filter-out ./,$(dir $(3) $(4) $(5)))) $(2)
- @$$(TOUCH) $$@
- $$(AAPT) package -f -m \
- -M AndroidManifest.xml \
- -I $(ANDROID_SDK)/android.jar \
- $(if $(MOZ_ANDROID_MAX_SDK_VERSION),--max-res-version $(MOZ_ANDROID_MAX_SDK_VERSION),) \
- --auto-add-overlay \
- $$(addprefix -S ,$$(ANDROID_RES_DIRS)) \
- $$(addprefix -A ,$$(ANDROID_ASSETS_DIRS)) \
- $(if $(ANDROID_EXTRA_PACKAGES),--extra-packages $$(subst $$(NULL) ,:,$$(strip $$(ANDROID_EXTRA_PACKAGES)))) \
- $(if $(ANDROID_EXTRA_RES_DIRS),$$(addprefix -S ,$$(ANDROID_EXTRA_RES_DIRS))) \
- --custom-package org.mozilla.gecko \
- --no-version-vectors \
- -F $(3) \
- -J $(4) \
- --output-text-symbols $(5) \
- --ignore-assets "$$(ANDROID_AAPT_IGNORE)"
-endef
-
-# [Comment 3/3] The first of these rules is used during regular
-# builds. The second writes an ap_ file that is only used during
-# packaging. It doesn't write the normal ap_, or R.java, since we
-# don't want the packaging step to write anything that would make a
-# further no-op build do work. See also
-# toolkit/mozapps/installer/packager.mk.
-
-# .aapt.deps: $(all_resources)
-$(eval $(call aapt_command,.aapt.deps,$(all_resources),gecko.ap_,generated/,./))
-
-ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
-.aapt.nodeps: FORCE
- cp $(gradle_dir)/app/intermediates/res/resources-automation-debug.ap_ gecko-nodeps.ap_
-else
-# .aapt.nodeps: $(DEPTH)/mobile/android/base/AndroidManifest.xml FORCE
-$(eval $(call aapt_command,.aapt.nodeps,$(DEPTH)/mobile/android/base/AndroidManifest.xml FORCE,gecko-nodeps.ap_,gecko-nodeps/,gecko-nodeps/))
-endif
-
-# Override the Java settings with some specific android settings
-include $(topsrcdir)/config/android-common.mk
-
-update-generated-wrappers:
- @cp $(CURDIR)/GeneratedJNIWrappers.cpp \
- $(CURDIR)/GeneratedJNIWrappers.h \
- $(CURDIR)/GeneratedJNINatives.h $(topsrcdir)/widget/android
- @echo Updated generated JNI code
-
-update-fennec-wrappers:
- @cp $(CURDIR)/FennecJNIWrappers.cpp \
- $(CURDIR)/FennecJNIWrappers.h \
- $(CURDIR)/FennecJNINatives.h $(topsrcdir)/widget/android/fennec
- @echo Updated Fennec JNI code
-
-.PHONY: update-generated-wrappers
-
-# This target is only used by IDE integrations. It rebuilds resources
-# that end up in omni.ja using the equivalent of |mach build faster|,
-# does most of the packaging step, and then updates omni.ja in
-# place. If you're not using an IDE, you should be using |mach build
-# mobile/android && mach package|.
-$(ABS_DIST)/fennec/$(OMNIJAR_NAME): FORCE
- $(REPORT_BUILD)
- $(MAKE) -C ../../../faster
- $(MAKE) -C ../installer stage-package
- $(MKDIR) -p $(@D)
- rsync --update $(DIST)/fennec/$(notdir $(OMNIJAR_NAME)) $@
- $(RM) $(DIST)/fennec/$(notdir $(OMNIJAR_NAME))
-
-# Targets built very early during a Gradle build.
-gradle-targets: $(foreach f,$(constants_PP_JAVAFILES),$(f))
-gradle-targets: $(DEPTH)/mobile/android/base/AndroidManifest.xml
-gradle-targets: $(ANDROID_GENERATED_RESFILES)
-
-ifndef MOZILLA_OFFICIAL
-# Local developers update omni.ja during their builds. There's a
-# chicken-and-egg problem here.
-gradle-omnijar: $(abspath $(DIST)/fennec/$(OMNIJAR_NAME))
-else
-# In automation, omni.ja is built only during packaging.
-gradle-omnijar:
-endif
-
-.PHONY: gradle-targets gradle-omnijar
-
-# GeneratedJNIWrappers.cpp target also generates
-# GeneratedJNIWrappers.h and GeneratedJNINatives.h
-# FennecJNIWrappers.cpp target also generates
-# FennecJNIWrappers.h and FennecJNINatives.h
-ifndef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
-libs:: GeneratedJNIWrappers.cpp
- @(diff GeneratedJNIWrappers.cpp $(topsrcdir)/widget/android/GeneratedJNIWrappers.cpp >/dev/null && \
- diff GeneratedJNIWrappers.h $(topsrcdir)/widget/android/GeneratedJNIWrappers.h >/dev/null && \
- diff GeneratedJNINatives.h $(topsrcdir)/widget/android/GeneratedJNINatives.h >/dev/null) || \
- (echo '*****************************************************' && \
- echo '*** Error: The generated JNI code has changed ***' && \
- echo '* To update generated code in the tree, please run *' && \
- echo && \
- echo ' make -C $(CURDIR) update-generated-wrappers' && \
- echo && \
- echo '* Repeat the build, and check in any changes. *' && \
- echo '*****************************************************' && \
- exit 1)
-
-libs:: FennecJNIWrappers.cpp
- @(diff FennecJNIWrappers.cpp $(topsrcdir)/widget/android/fennec/FennecJNIWrappers.cpp >/dev/null && \
- diff FennecJNIWrappers.h $(topsrcdir)/widget/android/fennec/FennecJNIWrappers.h >/dev/null && \
- diff FennecJNINatives.h $(topsrcdir)/widget/android/fennec/FennecJNINatives.h >/dev/null) || \
- (echo '*****************************************************' && \
- echo '*** Error: The Fennec JNI code has changed ***' && \
- echo '* To update generated code in the tree, please run *' && \
- echo && \
- echo ' make -C $(CURDIR) update-fennec-wrappers' && \
- echo && \
- echo '* Repeat the build, and check in any changes. *' && \
- echo '*****************************************************' && \
- exit 1)
-endif
-
-libs:: classes.dex
- $(INSTALL) classes.dex $(FINAL_TARGET)
-
-# Generate Java binder interfaces from AIDL files.
-aidl_src_path := $(srcdir)/aidl
-aidl_target_path := generated
-media_pkg := org/mozilla/gecko/media
-
-$(aidl_target_path)/$(media_pkg)/%.java:$(aidl_src_path)/$(media_pkg)/%.aidl
- @echo "Processing AIDL: $< => $@"
- $(AIDL) -p$(ANDROID_SDK)/framework.aidl -I$(aidl_src_path) -o$(aidl_target_path) $<
diff --git a/mobile/android/base/adjust-sdk-sandbox.token b/mobile/android/base/adjust-sdk-sandbox.token
deleted file mode 100644
index bb5bd5094..000000000
--- a/mobile/android/base/adjust-sdk-sandbox.token
+++ /dev/null
@@ -1 +0,0 @@
-ABCDEFGHIJKL
diff --git a/mobile/android/base/adjust_sdk_app_token.in b/mobile/android/base/adjust_sdk_app_token.in
deleted file mode 100644
index 07ac44d3f..000000000
--- a/mobile/android/base/adjust_sdk_app_token.in
+++ /dev/null
@@ -1,3 +0,0 @@
-//#ifdef MOZ_INSTALL_TRACKING
-//#define MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN @MOZ_ADJUST_SDK_KEY@
-//#endif
diff --git a/mobile/android/base/aidl/org/mozilla/gecko/media/FormatParam.aidl b/mobile/android/base/aidl/org/mozilla/gecko/media/FormatParam.aidl
deleted file mode 100644
index 91ce56d46..000000000
--- a/mobile/android/base/aidl/org/mozilla/gecko/media/FormatParam.aidl
+++ /dev/null
@@ -1,7 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-parcelable FormatParam; \ No newline at end of file
diff --git a/mobile/android/base/aidl/org/mozilla/gecko/media/ICodec.aidl b/mobile/android/base/aidl/org/mozilla/gecko/media/ICodec.aidl
deleted file mode 100644
index 7b434a5b6..000000000
--- a/mobile/android/base/aidl/org/mozilla/gecko/media/ICodec.aidl
+++ /dev/null
@@ -1,26 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-// Non-default types used in interface.
-import android.os.Bundle;
-import android.view.Surface;
-import org.mozilla.gecko.media.FormatParam;
-import org.mozilla.gecko.media.ICodecCallbacks;
-import org.mozilla.gecko.media.Sample;
-
-interface ICodec {
- void setCallbacks(in ICodecCallbacks callbacks);
- boolean configure(in FormatParam format, inout Surface surface, int flags);
- oneway void start();
- oneway void stop();
- oneway void flush();
- oneway void release();
-
- Sample dequeueInput(int size);
- oneway void queueInput(in Sample sample);
-
- oneway void releaseOutput(in Sample sample);
-}
diff --git a/mobile/android/base/aidl/org/mozilla/gecko/media/ICodecCallbacks.aidl b/mobile/android/base/aidl/org/mozilla/gecko/media/ICodecCallbacks.aidl
deleted file mode 100644
index 59e637f55..000000000
--- a/mobile/android/base/aidl/org/mozilla/gecko/media/ICodecCallbacks.aidl
+++ /dev/null
@@ -1,16 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-// Non-default types used in interface.
-import org.mozilla.gecko.media.FormatParam;
-import org.mozilla.gecko.media.Sample;
-
-interface ICodecCallbacks {
- oneway void onInputExhausted();
- oneway void onOutputFormatChanged(in FormatParam format);
- oneway void onOutput(in Sample sample);
- oneway void onError(boolean fatal);
-} \ No newline at end of file
diff --git a/mobile/android/base/aidl/org/mozilla/gecko/media/IMediaDrmBridge.aidl b/mobile/android/base/aidl/org/mozilla/gecko/media/IMediaDrmBridge.aidl
deleted file mode 100644
index 515e4b7d0..000000000
--- a/mobile/android/base/aidl/org/mozilla/gecko/media/IMediaDrmBridge.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-// Non-default types used in interface.
-import org.mozilla.gecko.media.IMediaDrmBridgeCallbacks;
-
-interface IMediaDrmBridge {
- void setCallbacks(in IMediaDrmBridgeCallbacks callbacks);
-
- oneway void createSession(int createSessionToken,
- int promiseId,
- String initDataType,
- in byte[] initData);
-
- oneway void updateSession(int promiseId,
- String sessionId,
- in byte[] response);
-
- oneway void closeSession(int promiseId, String sessionId);
-
- oneway void release();
-}
diff --git a/mobile/android/base/aidl/org/mozilla/gecko/media/IMediaDrmBridgeCallbacks.aidl b/mobile/android/base/aidl/org/mozilla/gecko/media/IMediaDrmBridgeCallbacks.aidl
deleted file mode 100644
index b3918417e..000000000
--- a/mobile/android/base/aidl/org/mozilla/gecko/media/IMediaDrmBridgeCallbacks.aidl
+++ /dev/null
@@ -1,31 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-// Non-default types used in interface.
-import org.mozilla.gecko.media.SessionKeyInfo;
-
-interface IMediaDrmBridgeCallbacks {
-
- oneway void onSessionCreated(int createSessionToken,
- int promiseId,
- in byte[] sessionId,
- in byte[] request);
-
- oneway void onSessionUpdated(int promiseId, in byte[] sessionId);
-
- oneway void onSessionClosed(int promiseId, in byte[] sessionId);
-
- oneway void onSessionMessage(in byte[] sessionId,
- int sessionMessageType,
- in byte[] request);
-
- oneway void onSessionError(in byte[] sessionId, String message);
-
- oneway void onSessionBatchedKeyChanged(in byte[] sessionId,
- in SessionKeyInfo[] keyInfos);
-
- oneway void onRejectPromise(int promiseId, String message);
-}
diff --git a/mobile/android/base/aidl/org/mozilla/gecko/media/IMediaManager.aidl b/mobile/android/base/aidl/org/mozilla/gecko/media/IMediaManager.aidl
deleted file mode 100644
index 023733d9d..000000000
--- a/mobile/android/base/aidl/org/mozilla/gecko/media/IMediaManager.aidl
+++ /dev/null
@@ -1,18 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-// Non-default types used in interface.
-import org.mozilla.gecko.media.ICodec;
-import org.mozilla.gecko.media.IMediaDrmBridge;
-
-interface IMediaManager {
- /** Creates a remote ICodec object. */
- ICodec createCodec();
-
- /** Creates a remote IMediaDrmBridge object. */
- IMediaDrmBridge createRemoteMediaDrmBridge(in String keySystem,
- in String stubId);
-}
diff --git a/mobile/android/base/aidl/org/mozilla/gecko/media/Sample.aidl b/mobile/android/base/aidl/org/mozilla/gecko/media/Sample.aidl
deleted file mode 100644
index 0d55c76fc..000000000
--- a/mobile/android/base/aidl/org/mozilla/gecko/media/Sample.aidl
+++ /dev/null
@@ -1,7 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-parcelable Sample; \ No newline at end of file
diff --git a/mobile/android/base/aidl/org/mozilla/gecko/media/SessionKeyInfo.aidl b/mobile/android/base/aidl/org/mozilla/gecko/media/SessionKeyInfo.aidl
deleted file mode 100644
index 1ec8f63c7..000000000
--- a/mobile/android/base/aidl/org/mozilla/gecko/media/SessionKeyInfo.aidl
+++ /dev/null
@@ -1,7 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-parcelable SessionKeyInfo; \ No newline at end of file
diff --git a/mobile/android/base/android-services.mozbuild b/mobile/android/base/android-services.mozbuild
deleted file mode 100644
index ca266542a..000000000
--- a/mobile/android/base/android-services.mozbuild
+++ /dev/null
@@ -1,1023 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-sync_thirdparty_java_files = [
- 'ch/boye/httpclientandroidlib/androidextra/Base64.java',
- 'ch/boye/httpclientandroidlib/androidextra/HttpClientAndroidLog.java',
- 'ch/boye/httpclientandroidlib/annotation/GuardedBy.java',
- 'ch/boye/httpclientandroidlib/annotation/Immutable.java',
- 'ch/boye/httpclientandroidlib/annotation/NotThreadSafe.java',
- 'ch/boye/httpclientandroidlib/annotation/package-info.java',
- 'ch/boye/httpclientandroidlib/annotation/ThreadSafe.java',
- 'ch/boye/httpclientandroidlib/auth/AUTH.java',
- 'ch/boye/httpclientandroidlib/auth/AuthenticationException.java',
- 'ch/boye/httpclientandroidlib/auth/AuthOption.java',
- 'ch/boye/httpclientandroidlib/auth/AuthProtocolState.java',
- 'ch/boye/httpclientandroidlib/auth/AuthScheme.java',
- 'ch/boye/httpclientandroidlib/auth/AuthSchemeFactory.java',
- 'ch/boye/httpclientandroidlib/auth/AuthSchemeProvider.java',
- 'ch/boye/httpclientandroidlib/auth/AuthSchemeRegistry.java',
- 'ch/boye/httpclientandroidlib/auth/AuthScope.java',
- 'ch/boye/httpclientandroidlib/auth/AuthState.java',
- 'ch/boye/httpclientandroidlib/auth/BasicUserPrincipal.java',
- 'ch/boye/httpclientandroidlib/auth/ChallengeState.java',
- 'ch/boye/httpclientandroidlib/auth/ContextAwareAuthScheme.java',
- 'ch/boye/httpclientandroidlib/auth/Credentials.java',
- 'ch/boye/httpclientandroidlib/auth/InvalidCredentialsException.java',
- 'ch/boye/httpclientandroidlib/auth/MalformedChallengeException.java',
- 'ch/boye/httpclientandroidlib/auth/NTCredentials.java',
- 'ch/boye/httpclientandroidlib/auth/NTUserPrincipal.java',
- 'ch/boye/httpclientandroidlib/auth/package-info.java',
- 'ch/boye/httpclientandroidlib/auth/params/AuthParamBean.java',
- 'ch/boye/httpclientandroidlib/auth/params/AuthParams.java',
- 'ch/boye/httpclientandroidlib/auth/params/AuthPNames.java',
- 'ch/boye/httpclientandroidlib/auth/params/package-info.java',
- 'ch/boye/httpclientandroidlib/auth/UsernamePasswordCredentials.java',
- 'ch/boye/httpclientandroidlib/client/AuthCache.java',
- 'ch/boye/httpclientandroidlib/client/AuthenticationHandler.java',
- 'ch/boye/httpclientandroidlib/client/AuthenticationStrategy.java',
- 'ch/boye/httpclientandroidlib/client/BackoffManager.java',
- 'ch/boye/httpclientandroidlib/client/cache/CacheResponseStatus.java',
- 'ch/boye/httpclientandroidlib/client/cache/HeaderConstants.java',
- 'ch/boye/httpclientandroidlib/client/cache/HttpCacheContext.java',
- 'ch/boye/httpclientandroidlib/client/cache/HttpCacheEntry.java',
- 'ch/boye/httpclientandroidlib/client/cache/HttpCacheEntrySerializationException.java',
- 'ch/boye/httpclientandroidlib/client/cache/HttpCacheEntrySerializer.java',
- 'ch/boye/httpclientandroidlib/client/cache/HttpCacheInvalidator.java',
- 'ch/boye/httpclientandroidlib/client/cache/HttpCacheStorage.java',
- 'ch/boye/httpclientandroidlib/client/cache/HttpCacheUpdateCallback.java',
- 'ch/boye/httpclientandroidlib/client/cache/HttpCacheUpdateException.java',
- 'ch/boye/httpclientandroidlib/client/cache/InputLimit.java',
- 'ch/boye/httpclientandroidlib/client/cache/Resource.java',
- 'ch/boye/httpclientandroidlib/client/cache/ResourceFactory.java',
- 'ch/boye/httpclientandroidlib/client/CircularRedirectException.java',
- 'ch/boye/httpclientandroidlib/client/ClientProtocolException.java',
- 'ch/boye/httpclientandroidlib/client/config/AuthSchemes.java',
- 'ch/boye/httpclientandroidlib/client/config/CookieSpecs.java',
- 'ch/boye/httpclientandroidlib/client/config/package-info.java',
- 'ch/boye/httpclientandroidlib/client/config/RequestConfig.java',
- 'ch/boye/httpclientandroidlib/client/ConnectionBackoffStrategy.java',
- 'ch/boye/httpclientandroidlib/client/CookieStore.java',
- 'ch/boye/httpclientandroidlib/client/CredentialsProvider.java',
- 'ch/boye/httpclientandroidlib/client/entity/DecompressingEntity.java',
- 'ch/boye/httpclientandroidlib/client/entity/DeflateDecompressingEntity.java',
- 'ch/boye/httpclientandroidlib/client/entity/DeflateInputStream.java',
- 'ch/boye/httpclientandroidlib/client/entity/EntityBuilder.java',
- 'ch/boye/httpclientandroidlib/client/entity/GzipCompressingEntity.java',
- 'ch/boye/httpclientandroidlib/client/entity/GzipDecompressingEntity.java',
- 'ch/boye/httpclientandroidlib/client/entity/LazyDecompressingInputStream.java',
- 'ch/boye/httpclientandroidlib/client/entity/package-info.java',
- 'ch/boye/httpclientandroidlib/client/entity/UrlEncodedFormEntity.java',
- 'ch/boye/httpclientandroidlib/client/HttpClient.java',
- 'ch/boye/httpclientandroidlib/client/HttpRequestRetryHandler.java',
- 'ch/boye/httpclientandroidlib/client/HttpResponseException.java',
- 'ch/boye/httpclientandroidlib/client/methods/AbortableHttpRequest.java',
- 'ch/boye/httpclientandroidlib/client/methods/AbstractExecutionAwareRequest.java',
- 'ch/boye/httpclientandroidlib/client/methods/CloseableHttpResponse.java',
- 'ch/boye/httpclientandroidlib/client/methods/Configurable.java',
- 'ch/boye/httpclientandroidlib/client/methods/HttpDelete.java',
- 'ch/boye/httpclientandroidlib/client/methods/HttpEntityEnclosingRequestBase.java',
- 'ch/boye/httpclientandroidlib/client/methods/HttpExecutionAware.java',
- 'ch/boye/httpclientandroidlib/client/methods/HttpGet.java',
- 'ch/boye/httpclientandroidlib/client/methods/HttpHead.java',
- 'ch/boye/httpclientandroidlib/client/methods/HttpOptions.java',
- 'ch/boye/httpclientandroidlib/client/methods/HttpPatch.java',
- 'ch/boye/httpclientandroidlib/client/methods/HttpPost.java',
- 'ch/boye/httpclientandroidlib/client/methods/HttpPut.java',
- 'ch/boye/httpclientandroidlib/client/methods/HttpRequestBase.java',
- 'ch/boye/httpclientandroidlib/client/methods/HttpRequestWrapper.java',
- 'ch/boye/httpclientandroidlib/client/methods/HttpTrace.java',
- 'ch/boye/httpclientandroidlib/client/methods/HttpUriRequest.java',
- 'ch/boye/httpclientandroidlib/client/methods/package-info.java',
- 'ch/boye/httpclientandroidlib/client/methods/RequestBuilder.java',
- 'ch/boye/httpclientandroidlib/client/NonRepeatableRequestException.java',
- 'ch/boye/httpclientandroidlib/client/package-info.java',
- 'ch/boye/httpclientandroidlib/client/params/AllClientPNames.java',
- 'ch/boye/httpclientandroidlib/client/params/AuthPolicy.java',
- 'ch/boye/httpclientandroidlib/client/params/ClientParamBean.java',
- 'ch/boye/httpclientandroidlib/client/params/ClientPNames.java',
- 'ch/boye/httpclientandroidlib/client/params/CookiePolicy.java',
- 'ch/boye/httpclientandroidlib/client/params/HttpClientParamConfig.java',
- 'ch/boye/httpclientandroidlib/client/params/HttpClientParams.java',
- 'ch/boye/httpclientandroidlib/client/params/package-info.java',
- 'ch/boye/httpclientandroidlib/client/protocol/ClientContext.java',
- 'ch/boye/httpclientandroidlib/client/protocol/ClientContextConfigurer.java',
- 'ch/boye/httpclientandroidlib/client/protocol/HttpClientContext.java',
- 'ch/boye/httpclientandroidlib/client/protocol/package-info.java',
- 'ch/boye/httpclientandroidlib/client/protocol/RequestAcceptEncoding.java',
- 'ch/boye/httpclientandroidlib/client/protocol/RequestAddCookies.java',
- 'ch/boye/httpclientandroidlib/client/protocol/RequestAuthCache.java',
- 'ch/boye/httpclientandroidlib/client/protocol/RequestAuthenticationBase.java',
- 'ch/boye/httpclientandroidlib/client/protocol/RequestClientConnControl.java',
- 'ch/boye/httpclientandroidlib/client/protocol/RequestDefaultHeaders.java',
- 'ch/boye/httpclientandroidlib/client/protocol/RequestExpectContinue.java',
- 'ch/boye/httpclientandroidlib/client/protocol/RequestProxyAuthentication.java',
- 'ch/boye/httpclientandroidlib/client/protocol/RequestTargetAuthentication.java',
- 'ch/boye/httpclientandroidlib/client/protocol/ResponseAuthCache.java',
- 'ch/boye/httpclientandroidlib/client/protocol/ResponseContentEncoding.java',
- 'ch/boye/httpclientandroidlib/client/protocol/ResponseProcessCookies.java',
- 'ch/boye/httpclientandroidlib/client/RedirectException.java',
- 'ch/boye/httpclientandroidlib/client/RedirectHandler.java',
- 'ch/boye/httpclientandroidlib/client/RedirectStrategy.java',
- 'ch/boye/httpclientandroidlib/client/RequestDirector.java',
- 'ch/boye/httpclientandroidlib/client/ResponseHandler.java',
- 'ch/boye/httpclientandroidlib/client/ServiceUnavailableRetryStrategy.java',
- 'ch/boye/httpclientandroidlib/client/UserTokenHandler.java',
- 'ch/boye/httpclientandroidlib/client/utils/CloneUtils.java',
- 'ch/boye/httpclientandroidlib/client/utils/DateUtils.java',
- 'ch/boye/httpclientandroidlib/client/utils/HttpClientUtils.java',
- 'ch/boye/httpclientandroidlib/client/utils/Idn.java',
- 'ch/boye/httpclientandroidlib/client/utils/JdkIdn.java',
- 'ch/boye/httpclientandroidlib/client/utils/package-info.java',
- 'ch/boye/httpclientandroidlib/client/utils/Punycode.java',
- 'ch/boye/httpclientandroidlib/client/utils/Rfc3492Idn.java',
- 'ch/boye/httpclientandroidlib/client/utils/URIBuilder.java',
- 'ch/boye/httpclientandroidlib/client/utils/URIUtils.java',
- 'ch/boye/httpclientandroidlib/client/utils/URLEncodedUtils.java',
- 'ch/boye/httpclientandroidlib/concurrent/BasicFuture.java',
- 'ch/boye/httpclientandroidlib/concurrent/Cancellable.java',
- 'ch/boye/httpclientandroidlib/concurrent/FutureCallback.java',
- 'ch/boye/httpclientandroidlib/concurrent/package-info.java',
- 'ch/boye/httpclientandroidlib/config/ConnectionConfig.java',
- 'ch/boye/httpclientandroidlib/config/Lookup.java',
- 'ch/boye/httpclientandroidlib/config/MessageConstraints.java',
- 'ch/boye/httpclientandroidlib/config/package-info.java',
- 'ch/boye/httpclientandroidlib/config/Registry.java',
- 'ch/boye/httpclientandroidlib/config/RegistryBuilder.java',
- 'ch/boye/httpclientandroidlib/config/SocketConfig.java',
- 'ch/boye/httpclientandroidlib/conn/BasicEofSensorWatcher.java',
- 'ch/boye/httpclientandroidlib/conn/BasicManagedEntity.java',
- 'ch/boye/httpclientandroidlib/conn/ClientConnectionManager.java',
- 'ch/boye/httpclientandroidlib/conn/ClientConnectionManagerFactory.java',
- 'ch/boye/httpclientandroidlib/conn/ClientConnectionOperator.java',
- 'ch/boye/httpclientandroidlib/conn/ClientConnectionRequest.java',
- 'ch/boye/httpclientandroidlib/conn/ConnectionKeepAliveStrategy.java',
- 'ch/boye/httpclientandroidlib/conn/ConnectionPoolTimeoutException.java',
- 'ch/boye/httpclientandroidlib/conn/ConnectionReleaseTrigger.java',
- 'ch/boye/httpclientandroidlib/conn/ConnectionRequest.java',
- 'ch/boye/httpclientandroidlib/conn/ConnectTimeoutException.java',
- 'ch/boye/httpclientandroidlib/conn/DnsResolver.java',
- 'ch/boye/httpclientandroidlib/conn/EofSensorInputStream.java',
- 'ch/boye/httpclientandroidlib/conn/EofSensorWatcher.java',
- 'ch/boye/httpclientandroidlib/conn/HttpClientConnectionManager.java',
- 'ch/boye/httpclientandroidlib/conn/HttpConnectionFactory.java',
- 'ch/boye/httpclientandroidlib/conn/HttpHostConnectException.java',
- 'ch/boye/httpclientandroidlib/conn/HttpInetSocketAddress.java',
- 'ch/boye/httpclientandroidlib/conn/HttpRoutedConnection.java',
- 'ch/boye/httpclientandroidlib/conn/ManagedClientConnection.java',
- 'ch/boye/httpclientandroidlib/conn/ManagedHttpClientConnection.java',
- 'ch/boye/httpclientandroidlib/conn/MultihomePlainSocketFactory.java',
- 'ch/boye/httpclientandroidlib/conn/OperatedClientConnection.java',
- 'ch/boye/httpclientandroidlib/conn/package-info.java',
- 'ch/boye/httpclientandroidlib/conn/params/ConnConnectionParamBean.java',
- 'ch/boye/httpclientandroidlib/conn/params/ConnConnectionPNames.java',
- 'ch/boye/httpclientandroidlib/conn/params/ConnManagerParamBean.java',
- 'ch/boye/httpclientandroidlib/conn/params/ConnManagerParams.java',
- 'ch/boye/httpclientandroidlib/conn/params/ConnManagerPNames.java',
- 'ch/boye/httpclientandroidlib/conn/params/ConnPerRoute.java',
- 'ch/boye/httpclientandroidlib/conn/params/ConnPerRouteBean.java',
- 'ch/boye/httpclientandroidlib/conn/params/ConnRouteParamBean.java',
- 'ch/boye/httpclientandroidlib/conn/params/ConnRouteParams.java',
- 'ch/boye/httpclientandroidlib/conn/params/ConnRoutePNames.java',
- 'ch/boye/httpclientandroidlib/conn/params/package-info.java',
- 'ch/boye/httpclientandroidlib/conn/routing/BasicRouteDirector.java',
- 'ch/boye/httpclientandroidlib/conn/routing/HttpRoute.java',
- 'ch/boye/httpclientandroidlib/conn/routing/HttpRouteDirector.java',
- 'ch/boye/httpclientandroidlib/conn/routing/HttpRoutePlanner.java',
- 'ch/boye/httpclientandroidlib/conn/routing/package-info.java',
- 'ch/boye/httpclientandroidlib/conn/routing/RouteInfo.java',
- 'ch/boye/httpclientandroidlib/conn/routing/RouteTracker.java',
- 'ch/boye/httpclientandroidlib/conn/scheme/HostNameResolver.java',
- 'ch/boye/httpclientandroidlib/conn/scheme/LayeredSchemeSocketFactory.java',
- 'ch/boye/httpclientandroidlib/conn/scheme/LayeredSocketFactory.java',
- 'ch/boye/httpclientandroidlib/conn/scheme/LayeredSocketFactoryAdaptor.java',
- 'ch/boye/httpclientandroidlib/conn/scheme/package-info.java',
- 'ch/boye/httpclientandroidlib/conn/scheme/PlainSocketFactory.java',
- 'ch/boye/httpclientandroidlib/conn/scheme/Scheme.java',
- 'ch/boye/httpclientandroidlib/conn/scheme/SchemeLayeredSocketFactory.java',
- 'ch/boye/httpclientandroidlib/conn/scheme/SchemeLayeredSocketFactoryAdaptor.java',
- 'ch/boye/httpclientandroidlib/conn/scheme/SchemeLayeredSocketFactoryAdaptor2.java',
- 'ch/boye/httpclientandroidlib/conn/scheme/SchemeRegistry.java',
- 'ch/boye/httpclientandroidlib/conn/scheme/SchemeSocketFactory.java',
- 'ch/boye/httpclientandroidlib/conn/scheme/SchemeSocketFactoryAdaptor.java',
- 'ch/boye/httpclientandroidlib/conn/scheme/SocketFactory.java',
- 'ch/boye/httpclientandroidlib/conn/scheme/SocketFactoryAdaptor.java',
- 'ch/boye/httpclientandroidlib/conn/SchemePortResolver.java',
- 'ch/boye/httpclientandroidlib/conn/socket/ConnectionSocketFactory.java',
- 'ch/boye/httpclientandroidlib/conn/socket/LayeredConnectionSocketFactory.java',
- 'ch/boye/httpclientandroidlib/conn/socket/package-info.java',
- 'ch/boye/httpclientandroidlib/conn/socket/PlainConnectionSocketFactory.java',
- 'ch/boye/httpclientandroidlib/conn/ssl/AbstractVerifier.java',
- 'ch/boye/httpclientandroidlib/conn/ssl/AllowAllHostnameVerifier.java',
- 'ch/boye/httpclientandroidlib/conn/ssl/BrowserCompatHostnameVerifier.java',
- 'ch/boye/httpclientandroidlib/conn/ssl/DistinguishedNameParser.java',
- 'ch/boye/httpclientandroidlib/conn/ssl/package-info.java',
- 'ch/boye/httpclientandroidlib/conn/ssl/PrivateKeyDetails.java',
- 'ch/boye/httpclientandroidlib/conn/ssl/PrivateKeyStrategy.java',
- 'ch/boye/httpclientandroidlib/conn/ssl/SSLConnectionSocketFactory.java',
- 'ch/boye/httpclientandroidlib/conn/ssl/SSLContextBuilder.java',
- 'ch/boye/httpclientandroidlib/conn/ssl/SSLContexts.java',
- 'ch/boye/httpclientandroidlib/conn/ssl/SSLInitializationException.java',
- 'ch/boye/httpclientandroidlib/conn/ssl/SSLSocketFactory.java',
- 'ch/boye/httpclientandroidlib/conn/ssl/StrictHostnameVerifier.java',
- 'ch/boye/httpclientandroidlib/conn/ssl/TokenParser.java',
- 'ch/boye/httpclientandroidlib/conn/ssl/TrustSelfSignedStrategy.java',
- 'ch/boye/httpclientandroidlib/conn/ssl/TrustStrategy.java',
- 'ch/boye/httpclientandroidlib/conn/ssl/X509HostnameVerifier.java',
- 'ch/boye/httpclientandroidlib/conn/UnsupportedSchemeException.java',
- 'ch/boye/httpclientandroidlib/conn/util/InetAddressUtils.java',
- 'ch/boye/httpclientandroidlib/conn/util/package-info.java',
- 'ch/boye/httpclientandroidlib/ConnectionClosedException.java',
- 'ch/boye/httpclientandroidlib/ConnectionReuseStrategy.java',
- 'ch/boye/httpclientandroidlib/Consts.java',
- 'ch/boye/httpclientandroidlib/ContentTooLongException.java',
- 'ch/boye/httpclientandroidlib/cookie/ClientCookie.java',
- 'ch/boye/httpclientandroidlib/cookie/Cookie.java',
- 'ch/boye/httpclientandroidlib/cookie/CookieAttributeHandler.java',
- 'ch/boye/httpclientandroidlib/cookie/CookieIdentityComparator.java',
- 'ch/boye/httpclientandroidlib/cookie/CookieOrigin.java',
- 'ch/boye/httpclientandroidlib/cookie/CookiePathComparator.java',
- 'ch/boye/httpclientandroidlib/cookie/CookieRestrictionViolationException.java',
- 'ch/boye/httpclientandroidlib/cookie/CookieSpec.java',
- 'ch/boye/httpclientandroidlib/cookie/CookieSpecFactory.java',
- 'ch/boye/httpclientandroidlib/cookie/CookieSpecProvider.java',
- 'ch/boye/httpclientandroidlib/cookie/CookieSpecRegistry.java',
- 'ch/boye/httpclientandroidlib/cookie/MalformedCookieException.java',
- 'ch/boye/httpclientandroidlib/cookie/package-info.java',
- 'ch/boye/httpclientandroidlib/cookie/params/CookieSpecParamBean.java',
- 'ch/boye/httpclientandroidlib/cookie/params/CookieSpecPNames.java',
- 'ch/boye/httpclientandroidlib/cookie/params/package-info.java',
- 'ch/boye/httpclientandroidlib/cookie/SetCookie.java',
- 'ch/boye/httpclientandroidlib/cookie/SetCookie2.java',
- 'ch/boye/httpclientandroidlib/cookie/SM.java',
- 'ch/boye/httpclientandroidlib/entity/AbstractHttpEntity.java',
- 'ch/boye/httpclientandroidlib/entity/BasicHttpEntity.java',
- 'ch/boye/httpclientandroidlib/entity/BufferedHttpEntity.java',
- 'ch/boye/httpclientandroidlib/entity/ByteArrayEntity.java',
- 'ch/boye/httpclientandroidlib/entity/ContentLengthStrategy.java',
- 'ch/boye/httpclientandroidlib/entity/ContentProducer.java',
- 'ch/boye/httpclientandroidlib/entity/ContentType.java',
- 'ch/boye/httpclientandroidlib/entity/EntityTemplate.java',
- 'ch/boye/httpclientandroidlib/entity/FileEntity.java',
- 'ch/boye/httpclientandroidlib/entity/HttpEntityWrapper.java',
- 'ch/boye/httpclientandroidlib/entity/InputStreamEntity.java',
- 'ch/boye/httpclientandroidlib/entity/mime/AbstractMultipartForm.java',
- 'ch/boye/httpclientandroidlib/entity/mime/content/AbstractContentBody.java',
- 'ch/boye/httpclientandroidlib/entity/mime/content/ByteArrayBody.java',
- 'ch/boye/httpclientandroidlib/entity/mime/content/ContentBody.java',
- 'ch/boye/httpclientandroidlib/entity/mime/content/ContentDescriptor.java',
- 'ch/boye/httpclientandroidlib/entity/mime/content/FileBody.java',
- 'ch/boye/httpclientandroidlib/entity/mime/content/InputStreamBody.java',
- 'ch/boye/httpclientandroidlib/entity/mime/content/package-info.java',
- 'ch/boye/httpclientandroidlib/entity/mime/content/StringBody.java',
- 'ch/boye/httpclientandroidlib/entity/mime/FormBodyPart.java',
- 'ch/boye/httpclientandroidlib/entity/mime/Header.java',
- 'ch/boye/httpclientandroidlib/entity/mime/HttpBrowserCompatibleMultipart.java',
- 'ch/boye/httpclientandroidlib/entity/mime/HttpMultipartMode.java',
- 'ch/boye/httpclientandroidlib/entity/mime/HttpRFC6532Multipart.java',
- 'ch/boye/httpclientandroidlib/entity/mime/HttpStrictMultipart.java',
- 'ch/boye/httpclientandroidlib/entity/mime/MIME.java',
- 'ch/boye/httpclientandroidlib/entity/mime/MinimalField.java',
- 'ch/boye/httpclientandroidlib/entity/mime/MultipartEntityBuilder.java',
- 'ch/boye/httpclientandroidlib/entity/mime/MultipartFormEntity.java',
- 'ch/boye/httpclientandroidlib/entity/mime/package-info.java',
- 'ch/boye/httpclientandroidlib/entity/package-info.java',
- 'ch/boye/httpclientandroidlib/entity/SerializableEntity.java',
- 'ch/boye/httpclientandroidlib/entity/StringEntity.java',
- 'ch/boye/httpclientandroidlib/FormattedHeader.java',
- 'ch/boye/httpclientandroidlib/Header.java',
- 'ch/boye/httpclientandroidlib/HeaderElement.java',
- 'ch/boye/httpclientandroidlib/HeaderElementIterator.java',
- 'ch/boye/httpclientandroidlib/HeaderIterator.java',
- 'ch/boye/httpclientandroidlib/HttpClientConnection.java',
- 'ch/boye/httpclientandroidlib/HttpConnection.java',
- 'ch/boye/httpclientandroidlib/HttpConnectionFactory.java',
- 'ch/boye/httpclientandroidlib/HttpConnectionMetrics.java',
- 'ch/boye/httpclientandroidlib/HttpEntity.java',
- 'ch/boye/httpclientandroidlib/HttpEntityEnclosingRequest.java',
- 'ch/boye/httpclientandroidlib/HttpException.java',
- 'ch/boye/httpclientandroidlib/HttpHeaders.java',
- 'ch/boye/httpclientandroidlib/HttpHost.java',
- 'ch/boye/httpclientandroidlib/HttpInetConnection.java',
- 'ch/boye/httpclientandroidlib/HttpMessage.java',
- 'ch/boye/httpclientandroidlib/HttpRequest.java',
- 'ch/boye/httpclientandroidlib/HttpRequestFactory.java',
- 'ch/boye/httpclientandroidlib/HttpRequestInterceptor.java',
- 'ch/boye/httpclientandroidlib/HttpResponse.java',
- 'ch/boye/httpclientandroidlib/HttpResponseFactory.java',
- 'ch/boye/httpclientandroidlib/HttpResponseInterceptor.java',
- 'ch/boye/httpclientandroidlib/HttpServerConnection.java',
- 'ch/boye/httpclientandroidlib/HttpStatus.java',
- 'ch/boye/httpclientandroidlib/HttpVersion.java',
- 'ch/boye/httpclientandroidlib/impl/AbstractHttpClientConnection.java',
- 'ch/boye/httpclientandroidlib/impl/AbstractHttpServerConnection.java',
- 'ch/boye/httpclientandroidlib/impl/auth/AuthSchemeBase.java',
- 'ch/boye/httpclientandroidlib/impl/auth/BasicScheme.java',
- 'ch/boye/httpclientandroidlib/impl/auth/BasicSchemeFactory.java',
- 'ch/boye/httpclientandroidlib/impl/auth/DigestScheme.java',
- 'ch/boye/httpclientandroidlib/impl/auth/DigestSchemeFactory.java',
- 'ch/boye/httpclientandroidlib/impl/auth/HttpAuthenticator.java',
- 'ch/boye/httpclientandroidlib/impl/auth/HttpEntityDigester.java',
- 'ch/boye/httpclientandroidlib/impl/auth/NTLMEngine.java',
- 'ch/boye/httpclientandroidlib/impl/auth/NTLMEngineException.java',
- 'ch/boye/httpclientandroidlib/impl/auth/NTLMEngineImpl.java',
- 'ch/boye/httpclientandroidlib/impl/auth/NTLMScheme.java',
- 'ch/boye/httpclientandroidlib/impl/auth/NTLMSchemeFactory.java',
- 'ch/boye/httpclientandroidlib/impl/auth/package-info.java',
- 'ch/boye/httpclientandroidlib/impl/auth/RFC2617Scheme.java',
- 'ch/boye/httpclientandroidlib/impl/auth/SpnegoTokenGenerator.java',
- 'ch/boye/httpclientandroidlib/impl/auth/UnsupportedDigestAlgorithmException.java',
- 'ch/boye/httpclientandroidlib/impl/BHttpConnectionBase.java',
- 'ch/boye/httpclientandroidlib/impl/client/AbstractAuthenticationHandler.java',
- 'ch/boye/httpclientandroidlib/impl/client/AbstractHttpClient.java',
- 'ch/boye/httpclientandroidlib/impl/client/AIMDBackoffManager.java',
- 'ch/boye/httpclientandroidlib/impl/client/AuthenticationStrategyAdaptor.java',
- 'ch/boye/httpclientandroidlib/impl/client/AuthenticationStrategyImpl.java',
- 'ch/boye/httpclientandroidlib/impl/client/AutoRetryHttpClient.java',
- 'ch/boye/httpclientandroidlib/impl/client/BasicAuthCache.java',
- 'ch/boye/httpclientandroidlib/impl/client/BasicCookieStore.java',
- 'ch/boye/httpclientandroidlib/impl/client/BasicCredentialsProvider.java',
- 'ch/boye/httpclientandroidlib/impl/client/BasicResponseHandler.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/AsynchronousValidationRequest.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/AsynchronousValidator.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/BasicHttpCache.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/BasicHttpCacheStorage.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/BasicIdGenerator.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/CacheableRequestPolicy.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/CacheConfig.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/CachedHttpResponseGenerator.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/CachedResponseSuitabilityChecker.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/CacheEntity.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/CacheEntryUpdater.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/CacheInvalidator.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/CacheKeyGenerator.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/CacheMap.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/CacheValidityPolicy.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/CachingExec.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/CachingHttpClientBuilder.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/CachingHttpClients.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/CombinedEntity.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/ConditionalRequestBuilder.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/DefaultFailureCache.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/DefaultHttpCacheEntrySerializer.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/ExponentialBackOffSchedulingStrategy.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/FailureCache.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/FailureCacheValue.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/FileResource.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/FileResourceFactory.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/HeapResource.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/HeapResourceFactory.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/HttpCache.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/ImmediateSchedulingStrategy.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/IOUtils.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/ManagedHttpCacheStorage.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/OptionsHttp11Response.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/Proxies.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/RequestProtocolCompliance.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/RequestProtocolError.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/ResourceReference.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/ResponseCachingPolicy.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/ResponseProtocolCompliance.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/ResponseProxyHandler.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/SchedulingStrategy.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/SizeLimitedResponseReader.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/Variant.java',
- 'ch/boye/httpclientandroidlib/impl/client/cache/WarningValue.java',
- 'ch/boye/httpclientandroidlib/impl/client/ClientParamsStack.java',
- 'ch/boye/httpclientandroidlib/impl/client/Clock.java',
- 'ch/boye/httpclientandroidlib/impl/client/CloseableHttpClient.java',
- 'ch/boye/httpclientandroidlib/impl/client/CloseableHttpResponseProxy.java',
- 'ch/boye/httpclientandroidlib/impl/client/ContentEncodingHttpClient.java',
- 'ch/boye/httpclientandroidlib/impl/client/DecompressingHttpClient.java',
- 'ch/boye/httpclientandroidlib/impl/client/DefaultBackoffStrategy.java',
- 'ch/boye/httpclientandroidlib/impl/client/DefaultConnectionKeepAliveStrategy.java',
- 'ch/boye/httpclientandroidlib/impl/client/DefaultHttpClient.java',
- 'ch/boye/httpclientandroidlib/impl/client/DefaultHttpRequestRetryHandler.java',
- 'ch/boye/httpclientandroidlib/impl/client/DefaultProxyAuthenticationHandler.java',
- 'ch/boye/httpclientandroidlib/impl/client/DefaultRedirectHandler.java',
- 'ch/boye/httpclientandroidlib/impl/client/DefaultRedirectStrategy.java',
- 'ch/boye/httpclientandroidlib/impl/client/DefaultRedirectStrategyAdaptor.java',
- 'ch/boye/httpclientandroidlib/impl/client/DefaultRequestDirector.java',
- 'ch/boye/httpclientandroidlib/impl/client/DefaultServiceUnavailableRetryStrategy.java',
- 'ch/boye/httpclientandroidlib/impl/client/DefaultTargetAuthenticationHandler.java',
- 'ch/boye/httpclientandroidlib/impl/client/DefaultUserTokenHandler.java',
- 'ch/boye/httpclientandroidlib/impl/client/EntityEnclosingRequestWrapper.java',
- 'ch/boye/httpclientandroidlib/impl/client/FutureRequestExecutionMetrics.java',
- 'ch/boye/httpclientandroidlib/impl/client/FutureRequestExecutionService.java',
- 'ch/boye/httpclientandroidlib/impl/client/HttpAuthenticator.java',
- 'ch/boye/httpclientandroidlib/impl/client/HttpClientBuilder.java',
- 'ch/boye/httpclientandroidlib/impl/client/HttpClients.java',
- 'ch/boye/httpclientandroidlib/impl/client/HttpRequestFutureTask.java',
- 'ch/boye/httpclientandroidlib/impl/client/HttpRequestTaskCallable.java',
- 'ch/boye/httpclientandroidlib/impl/client/InternalHttpClient.java',
- 'ch/boye/httpclientandroidlib/impl/client/LaxRedirectStrategy.java',
- 'ch/boye/httpclientandroidlib/impl/client/MinimalHttpClient.java',
- 'ch/boye/httpclientandroidlib/impl/client/NoopUserTokenHandler.java',
- 'ch/boye/httpclientandroidlib/impl/client/NullBackoffStrategy.java',
- 'ch/boye/httpclientandroidlib/impl/client/package-info.java',
- 'ch/boye/httpclientandroidlib/impl/client/ProxyAuthenticationStrategy.java',
- 'ch/boye/httpclientandroidlib/impl/client/ProxyClient.java',
- 'ch/boye/httpclientandroidlib/impl/client/RedirectLocations.java',
- 'ch/boye/httpclientandroidlib/impl/client/RequestWrapper.java',
- 'ch/boye/httpclientandroidlib/impl/client/RoutedRequest.java',
- 'ch/boye/httpclientandroidlib/impl/client/StandardHttpRequestRetryHandler.java',
- 'ch/boye/httpclientandroidlib/impl/client/SystemClock.java',
- 'ch/boye/httpclientandroidlib/impl/client/SystemDefaultCredentialsProvider.java',
- 'ch/boye/httpclientandroidlib/impl/client/SystemDefaultHttpClient.java',
- 'ch/boye/httpclientandroidlib/impl/client/TargetAuthenticationStrategy.java',
- 'ch/boye/httpclientandroidlib/impl/client/TunnelRefusedException.java',
- 'ch/boye/httpclientandroidlib/impl/conn/AbstractClientConnAdapter.java',
- 'ch/boye/httpclientandroidlib/impl/conn/AbstractPooledConnAdapter.java',
- 'ch/boye/httpclientandroidlib/impl/conn/AbstractPoolEntry.java',
- 'ch/boye/httpclientandroidlib/impl/conn/BasicClientConnectionManager.java',
- 'ch/boye/httpclientandroidlib/impl/conn/BasicHttpClientConnectionManager.java',
- 'ch/boye/httpclientandroidlib/impl/conn/ConnectionShutdownException.java',
- 'ch/boye/httpclientandroidlib/impl/conn/CPool.java',
- 'ch/boye/httpclientandroidlib/impl/conn/CPoolEntry.java',
- 'ch/boye/httpclientandroidlib/impl/conn/CPoolProxy.java',
- 'ch/boye/httpclientandroidlib/impl/conn/DefaultClientConnection.java',
- 'ch/boye/httpclientandroidlib/impl/conn/DefaultClientConnectionOperator.java',
- 'ch/boye/httpclientandroidlib/impl/conn/DefaultHttpResponseParser.java',
- 'ch/boye/httpclientandroidlib/impl/conn/DefaultHttpResponseParserFactory.java',
- 'ch/boye/httpclientandroidlib/impl/conn/DefaultHttpRoutePlanner.java',
- 'ch/boye/httpclientandroidlib/impl/conn/DefaultManagedHttpClientConnection.java',
- 'ch/boye/httpclientandroidlib/impl/conn/DefaultProxyRoutePlanner.java',
- 'ch/boye/httpclientandroidlib/impl/conn/DefaultResponseParser.java',
- 'ch/boye/httpclientandroidlib/impl/conn/DefaultRoutePlanner.java',
- 'ch/boye/httpclientandroidlib/impl/conn/DefaultSchemePortResolver.java',
- 'ch/boye/httpclientandroidlib/impl/conn/HttpClientConnectionOperator.java',
- 'ch/boye/httpclientandroidlib/impl/conn/HttpConnPool.java',
- 'ch/boye/httpclientandroidlib/impl/conn/HttpPoolEntry.java',
- 'ch/boye/httpclientandroidlib/impl/conn/IdleConnectionHandler.java',
- 'ch/boye/httpclientandroidlib/impl/conn/InMemoryDnsResolver.java',
- 'ch/boye/httpclientandroidlib/impl/conn/LoggingInputStream.java',
- 'ch/boye/httpclientandroidlib/impl/conn/LoggingManagedHttpClientConnection.java',
- 'ch/boye/httpclientandroidlib/impl/conn/LoggingOutputStream.java',
- 'ch/boye/httpclientandroidlib/impl/conn/LoggingSessionInputBuffer.java',
- 'ch/boye/httpclientandroidlib/impl/conn/LoggingSessionOutputBuffer.java',
- 'ch/boye/httpclientandroidlib/impl/conn/ManagedClientConnectionImpl.java',
- 'ch/boye/httpclientandroidlib/impl/conn/ManagedHttpClientConnectionFactory.java',
- 'ch/boye/httpclientandroidlib/impl/conn/package-info.java',
- 'ch/boye/httpclientandroidlib/impl/conn/PoolingClientConnectionManager.java',
- 'ch/boye/httpclientandroidlib/impl/conn/PoolingHttpClientConnectionManager.java',
- 'ch/boye/httpclientandroidlib/impl/conn/ProxySelectorRoutePlanner.java',
- 'ch/boye/httpclientandroidlib/impl/conn/SchemeRegistryFactory.java',
- 'ch/boye/httpclientandroidlib/impl/conn/SingleClientConnManager.java',
- 'ch/boye/httpclientandroidlib/impl/conn/SystemDefaultDnsResolver.java',
- 'ch/boye/httpclientandroidlib/impl/conn/SystemDefaultRoutePlanner.java',
- 'ch/boye/httpclientandroidlib/impl/conn/tsccm/AbstractConnPool.java',
- 'ch/boye/httpclientandroidlib/impl/conn/tsccm/BasicPooledConnAdapter.java',
- 'ch/boye/httpclientandroidlib/impl/conn/tsccm/BasicPoolEntry.java',
- 'ch/boye/httpclientandroidlib/impl/conn/tsccm/BasicPoolEntryRef.java',
- 'ch/boye/httpclientandroidlib/impl/conn/tsccm/ConnPoolByRoute.java',
- 'ch/boye/httpclientandroidlib/impl/conn/tsccm/package-info.java',
- 'ch/boye/httpclientandroidlib/impl/conn/tsccm/PoolEntryRequest.java',
- 'ch/boye/httpclientandroidlib/impl/conn/tsccm/RouteSpecificPool.java',
- 'ch/boye/httpclientandroidlib/impl/conn/tsccm/ThreadSafeClientConnManager.java',
- 'ch/boye/httpclientandroidlib/impl/conn/tsccm/WaitingThread.java',
- 'ch/boye/httpclientandroidlib/impl/conn/tsccm/WaitingThreadAborter.java',
- 'ch/boye/httpclientandroidlib/impl/conn/Wire.java',
- 'ch/boye/httpclientandroidlib/impl/ConnSupport.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/AbstractCookieAttributeHandler.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/AbstractCookieSpec.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/BasicClientCookie.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/BasicClientCookie2.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/BasicCommentHandler.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/BasicDomainHandler.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/BasicExpiresHandler.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/BasicMaxAgeHandler.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/BasicPathHandler.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/BasicSecureHandler.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/BestMatchSpec.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/BestMatchSpecFactory.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/BrowserCompatSpec.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/BrowserCompatSpecFactory.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/BrowserCompatVersionAttributeHandler.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/CookieSpecBase.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/DateParseException.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/DateUtils.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/IgnoreSpec.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/IgnoreSpecFactory.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/NetscapeDomainHandler.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/NetscapeDraftHeaderParser.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/NetscapeDraftSpec.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/NetscapeDraftSpecFactory.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/package-info.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/PublicSuffixFilter.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/PublicSuffixListParser.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/RFC2109DomainHandler.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/RFC2109Spec.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/RFC2109SpecFactory.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/RFC2109VersionHandler.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/RFC2965CommentUrlAttributeHandler.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/RFC2965DiscardAttributeHandler.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/RFC2965DomainAttributeHandler.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/RFC2965PortAttributeHandler.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/RFC2965Spec.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/RFC2965SpecFactory.java',
- 'ch/boye/httpclientandroidlib/impl/cookie/RFC2965VersionAttributeHandler.java',
- 'ch/boye/httpclientandroidlib/impl/DefaultBHttpClientConnection.java',
- 'ch/boye/httpclientandroidlib/impl/DefaultBHttpClientConnectionFactory.java',
- 'ch/boye/httpclientandroidlib/impl/DefaultBHttpServerConnection.java',
- 'ch/boye/httpclientandroidlib/impl/DefaultBHttpServerConnectionFactory.java',
- 'ch/boye/httpclientandroidlib/impl/DefaultConnectionReuseStrategy.java',
- 'ch/boye/httpclientandroidlib/impl/DefaultHttpClientConnection.java',
- 'ch/boye/httpclientandroidlib/impl/DefaultHttpRequestFactory.java',
- 'ch/boye/httpclientandroidlib/impl/DefaultHttpResponseFactory.java',
- 'ch/boye/httpclientandroidlib/impl/DefaultHttpServerConnection.java',
- 'ch/boye/httpclientandroidlib/impl/EnglishReasonPhraseCatalog.java',
- 'ch/boye/httpclientandroidlib/impl/entity/DisallowIdentityContentLengthStrategy.java',
- 'ch/boye/httpclientandroidlib/impl/entity/EntityDeserializer.java',
- 'ch/boye/httpclientandroidlib/impl/entity/EntitySerializer.java',
- 'ch/boye/httpclientandroidlib/impl/entity/LaxContentLengthStrategy.java',
- 'ch/boye/httpclientandroidlib/impl/entity/package-info.java',
- 'ch/boye/httpclientandroidlib/impl/entity/StrictContentLengthStrategy.java',
- 'ch/boye/httpclientandroidlib/impl/execchain/BackoffStrategyExec.java',
- 'ch/boye/httpclientandroidlib/impl/execchain/ClientExecChain.java',
- 'ch/boye/httpclientandroidlib/impl/execchain/ConnectionHolder.java',
- 'ch/boye/httpclientandroidlib/impl/execchain/HttpResponseProxy.java',
- 'ch/boye/httpclientandroidlib/impl/execchain/MainClientExec.java',
- 'ch/boye/httpclientandroidlib/impl/execchain/MinimalClientExec.java',
- 'ch/boye/httpclientandroidlib/impl/execchain/package-info.java',
- 'ch/boye/httpclientandroidlib/impl/execchain/ProtocolExec.java',
- 'ch/boye/httpclientandroidlib/impl/execchain/RedirectExec.java',
- 'ch/boye/httpclientandroidlib/impl/execchain/RequestAbortedException.java',
- 'ch/boye/httpclientandroidlib/impl/execchain/RequestEntityProxy.java',
- 'ch/boye/httpclientandroidlib/impl/execchain/ResponseEntityProxy.java',
- 'ch/boye/httpclientandroidlib/impl/execchain/RetryExec.java',
- 'ch/boye/httpclientandroidlib/impl/execchain/ServiceUnavailableRetryExec.java',
- 'ch/boye/httpclientandroidlib/impl/execchain/TunnelRefusedException.java',
- 'ch/boye/httpclientandroidlib/impl/HttpConnectionMetricsImpl.java',
- 'ch/boye/httpclientandroidlib/impl/io/AbstractMessageParser.java',
- 'ch/boye/httpclientandroidlib/impl/io/AbstractMessageWriter.java',
- 'ch/boye/httpclientandroidlib/impl/io/AbstractSessionInputBuffer.java',
- 'ch/boye/httpclientandroidlib/impl/io/AbstractSessionOutputBuffer.java',
- 'ch/boye/httpclientandroidlib/impl/io/ChunkedInputStream.java',
- 'ch/boye/httpclientandroidlib/impl/io/ChunkedOutputStream.java',
- 'ch/boye/httpclientandroidlib/impl/io/ContentLengthInputStream.java',
- 'ch/boye/httpclientandroidlib/impl/io/ContentLengthOutputStream.java',
- 'ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestParser.java',
- 'ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestParserFactory.java',
- 'ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestWriter.java',
- 'ch/boye/httpclientandroidlib/impl/io/DefaultHttpRequestWriterFactory.java',
- 'ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseParser.java',
- 'ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseParserFactory.java',
- 'ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseWriter.java',
- 'ch/boye/httpclientandroidlib/impl/io/DefaultHttpResponseWriterFactory.java',
- 'ch/boye/httpclientandroidlib/impl/io/HttpRequestParser.java',
- 'ch/boye/httpclientandroidlib/impl/io/HttpRequestWriter.java',
- 'ch/boye/httpclientandroidlib/impl/io/HttpResponseParser.java',
- 'ch/boye/httpclientandroidlib/impl/io/HttpResponseWriter.java',
- 'ch/boye/httpclientandroidlib/impl/io/HttpTransportMetricsImpl.java',
- 'ch/boye/httpclientandroidlib/impl/io/IdentityInputStream.java',
- 'ch/boye/httpclientandroidlib/impl/io/IdentityOutputStream.java',
- 'ch/boye/httpclientandroidlib/impl/io/package-info.java',
- 'ch/boye/httpclientandroidlib/impl/io/SessionInputBufferImpl.java',
- 'ch/boye/httpclientandroidlib/impl/io/SessionOutputBufferImpl.java',
- 'ch/boye/httpclientandroidlib/impl/io/SocketInputBuffer.java',
- 'ch/boye/httpclientandroidlib/impl/io/SocketOutputBuffer.java',
- 'ch/boye/httpclientandroidlib/impl/NoConnectionReuseStrategy.java',
- 'ch/boye/httpclientandroidlib/impl/package-info.java',
- 'ch/boye/httpclientandroidlib/impl/pool/BasicConnFactory.java',
- 'ch/boye/httpclientandroidlib/impl/pool/BasicConnPool.java',
- 'ch/boye/httpclientandroidlib/impl/pool/BasicPoolEntry.java',
- 'ch/boye/httpclientandroidlib/impl/pool/package-info.java',
- 'ch/boye/httpclientandroidlib/impl/SocketHttpClientConnection.java',
- 'ch/boye/httpclientandroidlib/impl/SocketHttpServerConnection.java',
- 'ch/boye/httpclientandroidlib/io/BufferInfo.java',
- 'ch/boye/httpclientandroidlib/io/EofSensor.java',
- 'ch/boye/httpclientandroidlib/io/HttpMessageParser.java',
- 'ch/boye/httpclientandroidlib/io/HttpMessageParserFactory.java',
- 'ch/boye/httpclientandroidlib/io/HttpMessageWriter.java',
- 'ch/boye/httpclientandroidlib/io/HttpMessageWriterFactory.java',
- 'ch/boye/httpclientandroidlib/io/HttpTransportMetrics.java',
- 'ch/boye/httpclientandroidlib/io/package-info.java',
- 'ch/boye/httpclientandroidlib/io/SessionInputBuffer.java',
- 'ch/boye/httpclientandroidlib/io/SessionOutputBuffer.java',
- 'ch/boye/httpclientandroidlib/MalformedChunkCodingException.java',
- 'ch/boye/httpclientandroidlib/message/AbstractHttpMessage.java',
- 'ch/boye/httpclientandroidlib/message/BasicHeader.java',
- 'ch/boye/httpclientandroidlib/message/BasicHeaderElement.java',
- 'ch/boye/httpclientandroidlib/message/BasicHeaderElementIterator.java',
- 'ch/boye/httpclientandroidlib/message/BasicHeaderIterator.java',
- 'ch/boye/httpclientandroidlib/message/BasicHeaderValueFormatter.java',
- 'ch/boye/httpclientandroidlib/message/BasicHeaderValueParser.java',
- 'ch/boye/httpclientandroidlib/message/BasicHttpEntityEnclosingRequest.java',
- 'ch/boye/httpclientandroidlib/message/BasicHttpRequest.java',
- 'ch/boye/httpclientandroidlib/message/BasicHttpResponse.java',
- 'ch/boye/httpclientandroidlib/message/BasicLineFormatter.java',
- 'ch/boye/httpclientandroidlib/message/BasicLineParser.java',
- 'ch/boye/httpclientandroidlib/message/BasicListHeaderIterator.java',
- 'ch/boye/httpclientandroidlib/message/BasicNameValuePair.java',
- 'ch/boye/httpclientandroidlib/message/BasicRequestLine.java',
- 'ch/boye/httpclientandroidlib/message/BasicStatusLine.java',
- 'ch/boye/httpclientandroidlib/message/BasicTokenIterator.java',
- 'ch/boye/httpclientandroidlib/message/BufferedHeader.java',
- 'ch/boye/httpclientandroidlib/message/HeaderGroup.java',
- 'ch/boye/httpclientandroidlib/message/HeaderValueFormatter.java',
- 'ch/boye/httpclientandroidlib/message/HeaderValueParser.java',
- 'ch/boye/httpclientandroidlib/message/LineFormatter.java',
- 'ch/boye/httpclientandroidlib/message/LineParser.java',
- 'ch/boye/httpclientandroidlib/message/package-info.java',
- 'ch/boye/httpclientandroidlib/message/ParserCursor.java',
- 'ch/boye/httpclientandroidlib/MessageConstraintException.java',
- 'ch/boye/httpclientandroidlib/MethodNotSupportedException.java',
- 'ch/boye/httpclientandroidlib/NameValuePair.java',
- 'ch/boye/httpclientandroidlib/NoHttpResponseException.java',
- 'ch/boye/httpclientandroidlib/package-info.java',
- 'ch/boye/httpclientandroidlib/params/AbstractHttpParams.java',
- 'ch/boye/httpclientandroidlib/params/BasicHttpParams.java',
- 'ch/boye/httpclientandroidlib/params/CoreConnectionPNames.java',
- 'ch/boye/httpclientandroidlib/params/CoreProtocolPNames.java',
- 'ch/boye/httpclientandroidlib/params/DefaultedHttpParams.java',
- 'ch/boye/httpclientandroidlib/params/HttpAbstractParamBean.java',
- 'ch/boye/httpclientandroidlib/params/HttpConnectionParamBean.java',
- 'ch/boye/httpclientandroidlib/params/HttpConnectionParams.java',
- 'ch/boye/httpclientandroidlib/params/HttpParamConfig.java',
- 'ch/boye/httpclientandroidlib/params/HttpParams.java',
- 'ch/boye/httpclientandroidlib/params/HttpParamsNames.java',
- 'ch/boye/httpclientandroidlib/params/HttpProtocolParamBean.java',
- 'ch/boye/httpclientandroidlib/params/HttpProtocolParams.java',
- 'ch/boye/httpclientandroidlib/params/package-info.java',
- 'ch/boye/httpclientandroidlib/params/SyncBasicHttpParams.java',
- 'ch/boye/httpclientandroidlib/ParseException.java',
- 'ch/boye/httpclientandroidlib/pool/AbstractConnPool.java',
- 'ch/boye/httpclientandroidlib/pool/ConnFactory.java',
- 'ch/boye/httpclientandroidlib/pool/ConnPool.java',
- 'ch/boye/httpclientandroidlib/pool/ConnPoolControl.java',
- 'ch/boye/httpclientandroidlib/pool/package-info.java',
- 'ch/boye/httpclientandroidlib/pool/PoolEntry.java',
- 'ch/boye/httpclientandroidlib/pool/PoolEntryCallback.java',
- 'ch/boye/httpclientandroidlib/pool/PoolEntryFuture.java',
- 'ch/boye/httpclientandroidlib/pool/PoolStats.java',
- 'ch/boye/httpclientandroidlib/pool/RouteSpecificPool.java',
- 'ch/boye/httpclientandroidlib/protocol/BasicHttpContext.java',
- 'ch/boye/httpclientandroidlib/protocol/BasicHttpProcessor.java',
- 'ch/boye/httpclientandroidlib/protocol/ChainBuilder.java',
- 'ch/boye/httpclientandroidlib/protocol/DefaultedHttpContext.java',
- 'ch/boye/httpclientandroidlib/protocol/ExecutionContext.java',
- 'ch/boye/httpclientandroidlib/protocol/HTTP.java',
- 'ch/boye/httpclientandroidlib/protocol/HttpContext.java',
- 'ch/boye/httpclientandroidlib/protocol/HttpCoreContext.java',
- 'ch/boye/httpclientandroidlib/protocol/HttpDateGenerator.java',
- 'ch/boye/httpclientandroidlib/protocol/HttpExpectationVerifier.java',
- 'ch/boye/httpclientandroidlib/protocol/HttpProcessor.java',
- 'ch/boye/httpclientandroidlib/protocol/HttpProcessorBuilder.java',
- 'ch/boye/httpclientandroidlib/protocol/HttpRequestExecutor.java',
- 'ch/boye/httpclientandroidlib/protocol/HttpRequestHandler.java',
- 'ch/boye/httpclientandroidlib/protocol/HttpRequestHandlerMapper.java',
- 'ch/boye/httpclientandroidlib/protocol/HttpRequestHandlerRegistry.java',
- 'ch/boye/httpclientandroidlib/protocol/HttpRequestHandlerResolver.java',
- 'ch/boye/httpclientandroidlib/protocol/HttpRequestInterceptorList.java',
- 'ch/boye/httpclientandroidlib/protocol/HttpResponseInterceptorList.java',
- 'ch/boye/httpclientandroidlib/protocol/HttpService.java',
- 'ch/boye/httpclientandroidlib/protocol/ImmutableHttpProcessor.java',
- 'ch/boye/httpclientandroidlib/protocol/package-info.java',
- 'ch/boye/httpclientandroidlib/protocol/RequestConnControl.java',
- 'ch/boye/httpclientandroidlib/protocol/RequestContent.java',
- 'ch/boye/httpclientandroidlib/protocol/RequestDate.java',
- 'ch/boye/httpclientandroidlib/protocol/RequestExpectContinue.java',
- 'ch/boye/httpclientandroidlib/protocol/RequestTargetHost.java',
- 'ch/boye/httpclientandroidlib/protocol/RequestUserAgent.java',
- 'ch/boye/httpclientandroidlib/protocol/ResponseConnControl.java',
- 'ch/boye/httpclientandroidlib/protocol/ResponseContent.java',
- 'ch/boye/httpclientandroidlib/protocol/ResponseDate.java',
- 'ch/boye/httpclientandroidlib/protocol/ResponseServer.java',
- 'ch/boye/httpclientandroidlib/protocol/SyncBasicHttpContext.java',
- 'ch/boye/httpclientandroidlib/protocol/UriHttpRequestHandlerMapper.java',
- 'ch/boye/httpclientandroidlib/protocol/UriPatternMatcher.java',
- 'ch/boye/httpclientandroidlib/ProtocolException.java',
- 'ch/boye/httpclientandroidlib/ProtocolVersion.java',
- 'ch/boye/httpclientandroidlib/ReasonPhraseCatalog.java',
- 'ch/boye/httpclientandroidlib/RequestLine.java',
- 'ch/boye/httpclientandroidlib/StatusLine.java',
- 'ch/boye/httpclientandroidlib/TokenIterator.java',
- 'ch/boye/httpclientandroidlib/TruncatedChunkException.java',
- 'ch/boye/httpclientandroidlib/UnsupportedHttpVersionException.java',
- 'ch/boye/httpclientandroidlib/util/Args.java',
- 'ch/boye/httpclientandroidlib/util/Asserts.java',
- 'ch/boye/httpclientandroidlib/util/ByteArrayBuffer.java',
- 'ch/boye/httpclientandroidlib/util/CharArrayBuffer.java',
- 'ch/boye/httpclientandroidlib/util/CharsetUtils.java',
- 'ch/boye/httpclientandroidlib/util/EncodingUtils.java',
- 'ch/boye/httpclientandroidlib/util/EntityUtils.java',
- 'ch/boye/httpclientandroidlib/util/ExceptionUtils.java',
- 'ch/boye/httpclientandroidlib/util/LangUtils.java',
- 'ch/boye/httpclientandroidlib/util/NetUtils.java',
- 'ch/boye/httpclientandroidlib/util/package-info.java',
- 'ch/boye/httpclientandroidlib/util/TextUtils.java',
- 'ch/boye/httpclientandroidlib/util/VersionInfo.java',
- 'org/json/simple/ItemList.java',
- 'org/json/simple/JSONArray.java',
- 'org/json/simple/JSONAware.java',
- 'org/json/simple/JSONObject.java',
- 'org/json/simple/JSONStreamAware.java',
- 'org/json/simple/JSONValue.java',
- 'org/json/simple/parser/ContainerFactory.java',
- 'org/json/simple/parser/ContentHandler.java',
- 'org/json/simple/parser/JSONParser.java',
- 'org/json/simple/parser/ParseException.java',
- 'org/json/simple/parser/Yylex.java',
- 'org/json/simple/parser/Yytoken.java',
- 'org/mozilla/apache/commons/codec/binary/Base32.java',
- 'org/mozilla/apache/commons/codec/binary/Base32InputStream.java',
- 'org/mozilla/apache/commons/codec/binary/Base32OutputStream.java',
- 'org/mozilla/apache/commons/codec/binary/Base64.java',
- 'org/mozilla/apache/commons/codec/binary/Base64InputStream.java',
- 'org/mozilla/apache/commons/codec/binary/Base64OutputStream.java',
- 'org/mozilla/apache/commons/codec/binary/BaseNCodec.java',
- 'org/mozilla/apache/commons/codec/binary/BaseNCodecInputStream.java',
- 'org/mozilla/apache/commons/codec/binary/BaseNCodecOutputStream.java',
- 'org/mozilla/apache/commons/codec/binary/BinaryCodec.java',
- 'org/mozilla/apache/commons/codec/binary/Hex.java',
- 'org/mozilla/apache/commons/codec/binary/StringUtils.java',
- 'org/mozilla/apache/commons/codec/BinaryDecoder.java',
- 'org/mozilla/apache/commons/codec/BinaryEncoder.java',
- 'org/mozilla/apache/commons/codec/CharEncoding.java',
- 'org/mozilla/apache/commons/codec/Decoder.java',
- 'org/mozilla/apache/commons/codec/DecoderException.java',
- 'org/mozilla/apache/commons/codec/digest/DigestUtils.java',
- 'org/mozilla/apache/commons/codec/Encoder.java',
- 'org/mozilla/apache/commons/codec/EncoderException.java',
- 'org/mozilla/apache/commons/codec/language/AbstractCaverphone.java',
- 'org/mozilla/apache/commons/codec/language/Caverphone.java',
- 'org/mozilla/apache/commons/codec/language/Caverphone1.java',
- 'org/mozilla/apache/commons/codec/language/Caverphone2.java',
- 'org/mozilla/apache/commons/codec/language/ColognePhonetic.java',
- 'org/mozilla/apache/commons/codec/language/DoubleMetaphone.java',
- 'org/mozilla/apache/commons/codec/language/Metaphone.java',
- 'org/mozilla/apache/commons/codec/language/RefinedSoundex.java',
- 'org/mozilla/apache/commons/codec/language/Soundex.java',
- 'org/mozilla/apache/commons/codec/language/SoundexUtils.java',
- 'org/mozilla/apache/commons/codec/net/BCodec.java',
- 'org/mozilla/apache/commons/codec/net/QCodec.java',
- 'org/mozilla/apache/commons/codec/net/QuotedPrintableCodec.java',
- 'org/mozilla/apache/commons/codec/net/RFC1522Codec.java',
- 'org/mozilla/apache/commons/codec/net/URLCodec.java',
- 'org/mozilla/apache/commons/codec/net/Utils.java',
- 'org/mozilla/apache/commons/codec/StringDecoder.java',
- 'org/mozilla/apache/commons/codec/StringEncoder.java',
- 'org/mozilla/apache/commons/codec/StringEncoderComparator.java',
-]
-
-sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozilla/gecko/' + x for x in [
- 'background/common/EditorBranch.java',
- 'background/common/GlobalConstants.java',
- 'background/common/log/Logger.java',
- 'background/common/log/writers/AndroidLevelCachingLogWriter.java',
- 'background/common/log/writers/AndroidLogWriter.java',
- 'background/common/log/writers/LevelFilteringLogWriter.java',
- 'background/common/log/writers/LogWriter.java',
- 'background/common/log/writers/PrintLogWriter.java',
- 'background/common/log/writers/SimpleTagLogWriter.java',
- 'background/common/log/writers/StringLogWriter.java',
- 'background/common/log/writers/TagLogWriter.java',
- 'background/common/log/writers/ThreadLocalTagLogWriter.java',
- 'background/common/PrefsBranch.java',
- 'background/common/telemetry/TelemetryWrapper.java',
- 'background/db/CursorDumper.java',
- 'background/db/Tab.java',
- 'background/nativecode/NativeCrypto.java',
- 'background/preferences/PreferenceFragment.java',
- 'background/preferences/PreferenceManagerCompat.java',
- 'background/ReadingListConstants.java',
- 'browserid/ASNUtils.java',
- 'browserid/BrowserIDKeyPair.java',
- 'browserid/DSACryptoImplementation.java',
- 'browserid/JSONWebTokenUtils.java',
- 'browserid/MockMyIDTokenFactory.java',
- 'browserid/RSACryptoImplementation.java',
- 'browserid/SigningPrivateKey.java',
- 'browserid/verifier/AbstractBrowserIDRemoteVerifierClient.java',
- 'browserid/verifier/BrowserIDRemoteVerifierClient10.java',
- 'browserid/verifier/BrowserIDRemoteVerifierClient20.java',
- 'browserid/verifier/BrowserIDVerifierClient.java',
- 'browserid/verifier/BrowserIDVerifierDelegate.java',
- 'browserid/verifier/BrowserIDVerifierException.java',
- 'browserid/VerifyingPublicKey.java',
- 'push/autopush/AutopushClient.java',
- 'push/autopush/AutopushClientException.java',
- 'push/RegisterUserAgentResponse.java',
- 'push/SubscribeChannelResponse.java',
- 'sync/AlreadySyncingException.java',
- 'sync/BackoffHandler.java',
- 'sync/BadRequiredFieldJSONException.java',
- 'sync/CollectionKeys.java',
- 'sync/CommandProcessor.java',
- 'sync/CommandRunner.java',
- 'sync/CredentialException.java',
- 'sync/crypto/CryptoException.java',
- 'sync/crypto/CryptoInfo.java',
- 'sync/crypto/HKDF.java',
- 'sync/crypto/HMACVerificationException.java',
- 'sync/crypto/KeyBundle.java',
- 'sync/crypto/MissingCryptoInputException.java',
- 'sync/crypto/NoKeyBundleException.java',
- 'sync/crypto/PBKDF2.java',
- 'sync/crypto/PersistedCrypto5Keys.java',
- 'sync/CryptoRecord.java',
- 'sync/DelayedWorkTracker.java',
- 'sync/delegates/ClientsDataDelegate.java',
- 'sync/delegates/FreshStartDelegate.java',
- 'sync/delegates/GlobalSessionCallback.java',
- 'sync/delegates/JSONRecordFetchDelegate.java',
- 'sync/delegates/KeyUploadDelegate.java',
- 'sync/delegates/MetaGlobalDelegate.java',
- 'sync/delegates/WipeServerDelegate.java',
- 'sync/EngineSettings.java',
- 'sync/ExtendedJSONObject.java',
- 'sync/GlobalSession.java',
- 'sync/HTTPFailureException.java',
- 'sync/InfoCollections.java',
- 'sync/InfoConfiguration.java',
- 'sync/InfoCounts.java',
- 'sync/JSONRecordFetcher.java',
- 'sync/KeyBundleProvider.java',
- 'sync/MetaGlobal.java',
- 'sync/MetaGlobalException.java',
- 'sync/MetaGlobalMissingEnginesException.java',
- 'sync/MetaGlobalNotSetException.java',
- 'sync/middleware/Crypto5MiddlewareRepository.java',
- 'sync/middleware/Crypto5MiddlewareRepositorySession.java',
- 'sync/middleware/MiddlewareRepository.java',
- 'sync/middleware/MiddlewareRepositorySession.java',
- 'sync/net/AbstractBearerTokenAuthHeaderProvider.java',
- 'sync/net/AuthHeaderProvider.java',
- 'sync/net/BaseResource.java',
- 'sync/net/BaseResourceDelegate.java',
- 'sync/net/BasicAuthHeaderProvider.java',
- 'sync/net/BearerAuthHeaderProvider.java',
- 'sync/net/BrowserIDAuthHeaderProvider.java',
- 'sync/net/ConnectionMonitorThread.java',
- 'sync/net/GzipNonChunkedCompressingEntity.java',
- 'sync/net/HandleProgressException.java',
- 'sync/net/HawkAuthHeaderProvider.java',
- 'sync/net/HMACAuthHeaderProvider.java',
- 'sync/net/HttpResponseObserver.java',
- 'sync/net/MozResponse.java',
- 'sync/net/Resource.java',
- 'sync/net/ResourceDelegate.java',
- 'sync/net/SRPConstants.java',
- 'sync/net/SyncResponse.java',
- 'sync/net/SyncStorageCollectionRequest.java',
- 'sync/net/SyncStorageCollectionRequestDelegate.java',
- 'sync/net/SyncStorageRecordRequest.java',
- 'sync/net/SyncStorageRequest.java',
- 'sync/net/SyncStorageRequestDelegate.java',
- 'sync/net/SyncStorageRequestIncrementalDelegate.java',
- 'sync/net/SyncStorageResponse.java',
- 'sync/net/TLSSocketFactory.java',
- 'sync/net/WBOCollectionRequestDelegate.java',
- 'sync/net/WBORequestDelegate.java',
- 'sync/NoCollectionKeysSetException.java',
- 'sync/NodeAuthenticationException.java',
- 'sync/NonArrayJSONException.java',
- 'sync/NonObjectJSONException.java',
- 'sync/NullClusterURLException.java',
- 'sync/PersistedMetaGlobal.java',
- 'sync/PrefsBackoffHandler.java',
- 'sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java',
- 'sync/repositories/android/AndroidBrowserBookmarksRepository.java',
- 'sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java',
- 'sync/repositories/android/AndroidBrowserHistoryDataAccessor.java',
- 'sync/repositories/android/AndroidBrowserHistoryRepository.java',
- 'sync/repositories/android/AndroidBrowserHistoryRepositorySession.java',
- 'sync/repositories/android/AndroidBrowserRepository.java',
- 'sync/repositories/android/AndroidBrowserRepositoryDataAccessor.java',
- 'sync/repositories/android/AndroidBrowserRepositorySession.java',
- 'sync/repositories/android/BookmarksDeletionManager.java',
- 'sync/repositories/android/BookmarksInsertionManager.java',
- 'sync/repositories/android/BrowserContractHelpers.java',
- 'sync/repositories/android/CachedSQLiteOpenHelper.java',
- 'sync/repositories/android/ClientsDatabase.java',
- 'sync/repositories/android/ClientsDatabaseAccessor.java',
- 'sync/repositories/android/FennecTabsRepository.java',
- 'sync/repositories/android/FormHistoryRepositorySession.java',
- 'sync/repositories/android/PasswordsRepositorySession.java',
- 'sync/repositories/android/RepoUtils.java',
- 'sync/repositories/android/VisitsHelper.java',
- 'sync/repositories/BookmarkNeedsReparentingException.java',
- 'sync/repositories/BookmarksRepository.java',
- 'sync/repositories/ConstrainedServer11Repository.java',
- 'sync/repositories/delegates/DeferrableRepositorySessionCreationDelegate.java',
- 'sync/repositories/delegates/DeferredRepositorySessionBeginDelegate.java',
- 'sync/repositories/delegates/DeferredRepositorySessionFetchRecordsDelegate.java',
- 'sync/repositories/delegates/DeferredRepositorySessionFinishDelegate.java',
- 'sync/repositories/delegates/DeferredRepositorySessionStoreDelegate.java',
- 'sync/repositories/delegates/RepositorySessionBeginDelegate.java',
- 'sync/repositories/delegates/RepositorySessionCleanDelegate.java',
- 'sync/repositories/delegates/RepositorySessionCreationDelegate.java',
- 'sync/repositories/delegates/RepositorySessionFetchRecordsDelegate.java',
- 'sync/repositories/delegates/RepositorySessionFinishDelegate.java',
- 'sync/repositories/delegates/RepositorySessionGuidsSinceDelegate.java',
- 'sync/repositories/delegates/RepositorySessionStoreDelegate.java',
- 'sync/repositories/delegates/RepositorySessionWipeDelegate.java',
- 'sync/repositories/domain/BookmarkRecord.java',
- 'sync/repositories/domain/BookmarkRecordFactory.java',
- 'sync/repositories/domain/ClientRecord.java',
- 'sync/repositories/domain/ClientRecordFactory.java',
- 'sync/repositories/domain/FormHistoryRecord.java',
- 'sync/repositories/domain/HistoryRecord.java',
- 'sync/repositories/domain/HistoryRecordFactory.java',
- 'sync/repositories/domain/PasswordRecord.java',
- 'sync/repositories/domain/PasswordRecordFactory.java',
- 'sync/repositories/domain/Record.java',
- 'sync/repositories/domain/RecordParseException.java',
- 'sync/repositories/domain/TabsRecord.java',
- 'sync/repositories/domain/TabsRecordFactory.java',
- 'sync/repositories/domain/VersionConstants.java',
- 'sync/repositories/downloaders/BatchingDownloader.java',
- 'sync/repositories/downloaders/BatchingDownloaderDelegate.java',
- 'sync/repositories/FetchFailedException.java',
- 'sync/repositories/HashSetStoreTracker.java',
- 'sync/repositories/HistoryRepository.java',
- 'sync/repositories/IdentityRecordFactory.java',
- 'sync/repositories/InactiveSessionException.java',
- 'sync/repositories/InvalidBookmarkTypeException.java',
- 'sync/repositories/InvalidRequestException.java',
- 'sync/repositories/InvalidSessionTransitionException.java',
- 'sync/repositories/MultipleRecordsForGuidException.java',
- 'sync/repositories/NoContentProviderException.java',
- 'sync/repositories/NoGuidForIdException.java',
- 'sync/repositories/NoStoreDelegateException.java',
- 'sync/repositories/NullCursorException.java',
- 'sync/repositories/ParentNotFoundException.java',
- 'sync/repositories/ProfileDatabaseException.java',
- 'sync/repositories/RecordFactory.java',
- 'sync/repositories/RecordFilter.java',
- 'sync/repositories/Repository.java',
- 'sync/repositories/RepositorySession.java',
- 'sync/repositories/RepositorySessionBundle.java',
- 'sync/repositories/Server11Repository.java',
- 'sync/repositories/Server11RepositorySession.java',
- 'sync/repositories/StoreFailedException.java',
- 'sync/repositories/StoreTracker.java',
- 'sync/repositories/StoreTrackingRepositorySession.java',
- 'sync/repositories/uploaders/BatchingUploader.java',
- 'sync/repositories/uploaders/BatchMeta.java',
- 'sync/repositories/uploaders/BufferSizeTracker.java',
- 'sync/repositories/uploaders/MayUploadProvider.java',
- 'sync/repositories/uploaders/Payload.java',
- 'sync/repositories/uploaders/PayloadUploadDelegate.java',
- 'sync/repositories/uploaders/RecordUploadRunnable.java',
- 'sync/Server11PreviousPostFailedException.java',
- 'sync/Server11RecordPostFailedException.java',
- 'sync/setup/activities/ActivityUtils.java',
- 'sync/setup/activities/WebURLFinder.java',
- 'sync/setup/Constants.java',
- 'sync/setup/InvalidSyncKeyException.java',
- 'sync/SharedPreferencesClientsDataDelegate.java',
- 'sync/stage/AbstractNonRepositorySyncStage.java',
- 'sync/stage/AbstractSessionManagingSyncStage.java',
- 'sync/stage/AndroidBrowserBookmarksServerSyncStage.java',
- 'sync/stage/AndroidBrowserHistoryServerSyncStage.java',
- 'sync/stage/CheckPreconditionsStage.java',
- 'sync/stage/CompletedStage.java',
- 'sync/stage/EnsureCrypto5KeysStage.java',
- 'sync/stage/FennecTabsServerSyncStage.java',
- 'sync/stage/FetchInfoCollectionsStage.java',
- 'sync/stage/FetchInfoConfigurationStage.java',
- 'sync/stage/FetchMetaGlobalStage.java',
- 'sync/stage/FormHistoryServerSyncStage.java',
- 'sync/stage/GlobalSyncStage.java',
- 'sync/stage/NoSuchStageException.java',
- 'sync/stage/PasswordsServerSyncStage.java',
- 'sync/stage/SafeConstrainedServer11Repository.java',
- 'sync/stage/ServerSyncStage.java',
- 'sync/stage/SyncClientsEngineStage.java',
- 'sync/stage/UploadMetaGlobalStage.java',
- 'sync/Sync11Configuration.java',
- 'sync/SyncConfiguration.java',
- 'sync/SyncConfigurationException.java',
- 'sync/SyncConstants.java',
- 'sync/SyncException.java',
- 'sync/synchronizer/ConcurrentRecordConsumer.java',
- 'sync/synchronizer/RecordConsumer.java',
- 'sync/synchronizer/RecordsChannel.java',
- 'sync/synchronizer/RecordsChannelDelegate.java',
- 'sync/synchronizer/RecordsConsumerDelegate.java',
- 'sync/synchronizer/SerialRecordConsumer.java',
- 'sync/synchronizer/ServerLocalSynchronizer.java',
- 'sync/synchronizer/ServerLocalSynchronizerSession.java',
- 'sync/synchronizer/SessionNotBegunException.java',
- 'sync/synchronizer/Synchronizer.java',
- 'sync/synchronizer/SynchronizerDelegate.java',
- 'sync/synchronizer/SynchronizerSession.java',
- 'sync/synchronizer/SynchronizerSessionDelegate.java',
- 'sync/synchronizer/UnbundleError.java',
- 'sync/synchronizer/UnexpectedSessionException.java',
- 'sync/SynchronizerConfiguration.java',
- 'sync/telemetry/TelemetryContract.java',
- 'sync/ThreadPool.java',
- 'sync/UnexpectedJSONException.java',
- 'sync/UnknownSynchronizerConfigurationVersionException.java',
- 'sync/Utils.java',
- 'tokenserver/TokenServerClient.java',
- 'tokenserver/TokenServerClientDelegate.java',
- 'tokenserver/TokenServerException.java',
- 'tokenserver/TokenServerToken.java',
- 'util/PRNGFixes.java',
-]]
diff --git a/mobile/android/base/crashreporter/res/drawable-mdpi/crash_reporter.png b/mobile/android/base/crashreporter/res/drawable-mdpi/crash_reporter.png
deleted file mode 100644
index c9f495d30..000000000
--- a/mobile/android/base/crashreporter/res/drawable-mdpi/crash_reporter.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/crashreporter/res/drawable/textbox_bg.xml b/mobile/android/base/crashreporter/res/drawable/textbox_bg.xml
deleted file mode 100644
index bcc884a08..000000000
--- a/mobile/android/base/crashreporter/res/drawable/textbox_bg.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_enabled="true">
- <shape>
- <solid android:color="@color/textbox_background" />
- <stroke android:width="1dp" android:color="@color/textbox_stroke" />
- </shape>
- </item>
-
- <item android:state_enabled="false">
- <shape>
- <solid android:color="@color/textbox_background_disabled" />
- <stroke android:width="1dp" android:color="@color/textbox_stroke_disabled" />
- </shape>
- </item>
-
-</selector>
diff --git a/mobile/android/base/crashreporter/res/layout/crash_reporter.xml b/mobile/android/base/crashreporter/res/layout/crash_reporter.xml
deleted file mode 100644
index b7285dc20..000000000
--- a/mobile/android/base/crashreporter/res/layout/crash_reporter.xml
+++ /dev/null
@@ -1,126 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:fillViewport="true">
-
- <LinearLayout android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:background="@color/toolbar_grey">
-
- <LinearLayout android:layout_width="match_parent"
- android:layout_height="0dp"
- android:orientation="vertical"
- android:padding="10dp"
- android:layout_weight="1">
-
- <TextView android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="10dip"
- android:textSize="30sp"
- android:textColor="#000"
- android:layout_gravity="center_horizontal"
- android:fontFamily="sans-serif-light"
- android:text="@string/crash_sorry"/>
-
- <TextView android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="10dip"
- android:textAppearance="@style/TextAppearance"
- android:text="@string/crash_message2"/>
-
- <CheckBox android:id="@+id/send_report"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:checked="true"
- android:textColor="@color/primary_text"
- android:layout_marginBottom="10dp"
- android:text="@string/crash_send_report_message3"/>
-
- <EditText android:id="@+id/comment"
- style="@style/CrashReporter.EditText"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:ems="10"
- android:inputType="textMultiLine"
- android:lines="5"
- android:gravity="top"
- android:layout_marginLeft="8dp"
- android:layout_marginRight="8dp"
- android:layout_marginBottom="10dp"
- android:hint="@string/crash_comment" />
-
- <CheckBox android:id="@+id/include_url"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textColor="@color/primary_text"
- android:textAppearance="@style/TextAppearance"
- android:layout_marginBottom="10dp"
- android:text="@string/crash_include_url2"/>
-
- <CheckBox android:id="@+id/allow_contact"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textColor="@color/primary_text"
- android:textAppearance="@style/TextAppearance"
- android:layout_marginBottom="10dp"
- android:text="@string/crash_allow_contact2"/>
-
- <org.mozilla.gecko.widget.ClickableWhenDisabledEditText
- android:id="@+id/email"
- style="@style/CrashReporter.EditText"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:ems="10"
- android:inputType="textEmailAddress"
- android:layout_marginLeft="8dp"
- android:layout_marginRight="8dp"
- android:enabled="false"
- android:clickable="true"
- android:hint="@string/crash_email" />
-
- </LinearLayout>
-
- <View android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="#999" />
-
- <LinearLayout android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_gravity="bottom">
-
- <Button android:id="@+id/close"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1.0"
- android:padding="15dp"
- android:onClick="onCloseClick"
- android:text="@string/crash_close_label"
- android:textAppearance="?android:attr/textAppearance"
- android:background="@drawable/action_bar_button"/>
-
- <View android:layout_width="1dp"
- android:layout_height="match_parent"
- android:background="#999" />
-
- <Button android:id="@+id/restart"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1.0"
- android:padding="15dp"
- android:onClick="onRestartClick"
- android:text="@string/crash_restart_label"
- android:textAppearance="?android:attr/textAppearance"
- android:background="@drawable/action_bar_button"/>
-
- </LinearLayout>
-
- </LinearLayout>
-
-</ScrollView>
diff --git a/mobile/android/base/crashreporter/res/values/colors.xml b/mobile/android/base/crashreporter/res/values/colors.xml
deleted file mode 100644
index 7eeab5b69..000000000
--- a/mobile/android/base/crashreporter/res/values/colors.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
- <!-- Crash Reporter Colors -->
- <color name="textbox_background">#FFF</color>
- <color name="textbox_background_disabled">#DDD</color>
- <color name="textbox_stroke">#000</color>
- <color name="textbox_stroke_disabled">#666</color>
-</resources>
-
diff --git a/mobile/android/base/crashreporter/res/values/styles.xml b/mobile/android/base/crashreporter/res/values/styles.xml
deleted file mode 100644
index 17f5409e4..000000000
--- a/mobile/android/base/crashreporter/res/values/styles.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
- <!-- Crash Reporter Styles -->
- <style name="CrashReporter" />
-
- <style name="CrashReporter.EditText">
- <item name="android:background">@drawable/textbox_bg</item>
- <item name="android:padding">10dp</item>
- <item name="android:textAppearance">@style/TextAppearance</item>
- </style>
-</resources>
diff --git a/mobile/android/base/geckoview.ddf b/mobile/android/base/geckoview.ddf
deleted file mode 100644
index 015a0d3e7..000000000
--- a/mobile/android/base/geckoview.ddf
+++ /dev/null
@@ -1,75 +0,0 @@
-# This is a Classycle dependency definition file that asserts that the contents
-# of the GeckoView library (Classycle set [lib]) is a dependency (but does not
-# depend) on Fennec (Classycle set [main]). The additional Classycle set
-# [middle] consists of classes referenced by GeckoView that probably should not
-# be referenced. We want this middle set to shrink over time.
-
-show allResults
-
-[lib] = \
- org.mozilla.gecko.gfx.* \
- org.mozilla.gecko.mozglue.* \
- org.mozilla.gecko.sqlite.* \
- org.mozilla.gecko.util.* \
- org.mozilla.gecko.AndroidGamepadManager \
- org.mozilla.gecko.AppConstants \
- org.mozilla.gecko.BaseGeckoInterface \
- org.mozilla.gecko.ContextGetter \
- org.mozilla.gecko.CrashHandler \
- org.mozilla.gecko.EventDispatcher \
- org.mozilla.gecko.GeckoAccessibility \
- org.mozilla.gecko.GeckoAppShell \
- org.mozilla.gecko.GeckoBatteryManager \
- org.mozilla.gecko.GeckoEditable \
- org.mozilla.gecko.GeckoEditableClient \
- org.mozilla.gecko.GeckoEditableListener \
- org.mozilla.gecko.GeckoEvent \
- org.mozilla.gecko.GeckoInputConnection \
- org.mozilla.gecko.GeckoJavaSampler \
- org.mozilla.gecko.GeckoNetworkManager \
- org.mozilla.gecko.GeckoProfile \
- org.mozilla.gecko.GeckoScreenOrientation \
- org.mozilla.gecko.GeckoSharedPrefs \
- org.mozilla.gecko.GeckoThread \
- org.mozilla.gecko.GeckoView \
- org.mozilla.gecko.GlobalHistory \
- org.mozilla.gecko.InputMethods \
- org.mozilla.gecko.NSSBridge \
- org.mozilla.gecko.NotificationClient \
- org.mozilla.gecko.NotificationHandler \
- org.mozilla.gecko.PrefsHelper \
- org.mozilla.gecko.SysInfo \
- org.mozilla.gecko.TouchEventInterceptor \
- org.mozilla.gecko.ZoomConstraints
-
-[middle] = \
- org.mozilla.gecko.prompts.* \
- org.mozilla.gecko.FormAssistPopup \
- org.mozilla.gecko.GeckoActivity \
- org.mozilla.gecko.GeckoApp \
- org.mozilla.gecko.GeckoProfileDirectories \
- org.mozilla.gecko.GuestSession \
- org.mozilla.gecko.R \
- org.mozilla.gecko.Tab \
- org.mozilla.gecko.Tabs \
- org.mozilla.gecko.Telemetry \
- org.mozilla.gecko.TelemetryContract \
- org.mozilla.gecko.ThumbnailHelper \
- org.mozilla.gecko.db.BrowserDB \
- org.mozilla.gecko.db.LocalBrowserDB \
- org.mozilla.gecko.distribution.Distribution \
- org.mozilla.gecko.icons.*
-
-[main] = org.mozilla.gecko.* excluding [lib] [middle]
-
-check sets [lib] [middle] [main]
-
-# Bug 1107134: it appears that Classycle can be fooled if the Java
-# compiler inlines a constant from [main] into [lib]. That is, [main]
-# really does depend on [lib] but Classycle only sees the dependency
-# with some javac versions. For now, disable the check. Yes, this
-# processing is useless without this check.
-# check [lib] directlyIndependentOf [main]
-
-# This fails; if this passed, GeckoView would be ready to extract from Fennec.
-# check [lib] independentOf [middle]
diff --git a/mobile/android/base/java/org/mozilla/gecko/ANRReporter.java b/mobile/android/base/java/org/mozilla/gecko/ANRReporter.java
deleted file mode 100644
index 3c29edef3..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/ANRReporter.java
+++ /dev/null
@@ -1,596 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.Reader;
-import java.util.Locale;
-import java.util.UUID;
-import java.util.regex.Pattern;
-
-import org.json.JSONObject;
-import org.mozilla.gecko.annotation.WrapForJNI;
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
-
-public final class ANRReporter extends BroadcastReceiver
-{
- private static final boolean DEBUG = false;
- private static final String LOGTAG = "GeckoANRReporter";
-
- private static final String ANR_ACTION = "android.intent.action.ANR";
- // Number of lines to search traces.txt to decide whether it's a Gecko ANR
- private static final int LINES_TO_IDENTIFY_TRACES = 10;
- // ANRs may happen because of memory pressure,
- // so don't use up too much memory here
- // Size of buffer to hold one line of text
- private static final int TRACES_LINE_SIZE = 100;
- // Size of block to use when processing traces.txt
- private static final int TRACES_BLOCK_SIZE = 2000;
- private static final String TRACES_CHARSET = "utf-8";
- private static final String PING_CHARSET = "utf-8";
-
- private static final ANRReporter sInstance = new ANRReporter();
- private static int sRegisteredCount;
- private Handler mHandler;
- private volatile boolean mPendingANR;
-
- @WrapForJNI
- private static native boolean requestNativeStack(boolean unwind);
- @WrapForJNI
- private static native String getNativeStack();
- @WrapForJNI
- private static native void releaseNativeStack();
-
- public static void register(Context context) {
- if (sRegisteredCount++ != 0) {
- // Already registered
- return;
- }
- sInstance.start(context);
- }
-
- public static void unregister() {
- if (sRegisteredCount == 0) {
- Log.w(LOGTAG, "register/unregister mismatch");
- return;
- }
- if (--sRegisteredCount != 0) {
- // Should still be registered
- return;
- }
- sInstance.stop();
- }
-
- private void start(final Context context) {
-
- Thread receiverThread = new Thread(new Runnable() {
- @Override
- public void run() {
- Looper.prepare();
- synchronized (ANRReporter.this) {
- mHandler = new Handler();
- ANRReporter.this.notify();
- }
- if (DEBUG) {
- Log.d(LOGTAG, "registering receiver");
- }
- context.registerReceiver(ANRReporter.this,
- new IntentFilter(ANR_ACTION),
- null,
- mHandler);
- Looper.loop();
-
- if (DEBUG) {
- Log.d(LOGTAG, "unregistering receiver");
- }
- context.unregisterReceiver(ANRReporter.this);
- mHandler = null;
- }
- }, LOGTAG);
-
- receiverThread.setDaemon(true);
- receiverThread.start();
- }
-
- private void stop() {
- synchronized (this) {
- while (mHandler == null) {
- try {
- wait(1000);
- if (mHandler == null) {
- // We timed out; just give up. The process is probably
- // quitting anyways, so we let the OS do the clean up
- Log.w(LOGTAG, "timed out waiting for handler");
- return;
- }
- } catch (InterruptedException e) {
- }
- }
- }
- Looper looper = mHandler.getLooper();
- looper.quit();
- try {
- looper.getThread().join();
- } catch (InterruptedException e) {
- }
- }
-
- private ANRReporter() {
- }
-
- // Return the "traces.txt" file, or null if there is no such file
- private static File getTracesFile() {
- // Check most common location first.
- File tracesFile = new File("/data/anr/traces.txt");
- if (tracesFile.isFile() && tracesFile.canRead()) {
- return tracesFile;
- }
-
- // Find the traces file name if we can.
- try {
- // getprop [prop-name [default-value]]
- Process propProc = (new ProcessBuilder())
- .command("/system/bin/getprop", "dalvik.vm.stack-trace-file")
- .redirectErrorStream(true)
- .start();
- try {
- BufferedReader buf = new BufferedReader(
- new InputStreamReader(propProc.getInputStream()), TRACES_LINE_SIZE);
- String propVal = buf.readLine();
- if (DEBUG) {
- Log.d(LOGTAG, "getprop returned " + String.valueOf(propVal));
- }
- // getprop can return empty string when the prop value is empty
- // or prop is undefined, treat both cases the same way
- if (propVal != null && propVal.length() != 0) {
- tracesFile = new File(propVal);
- if (tracesFile.isFile() && tracesFile.canRead()) {
- return tracesFile;
- } else if (DEBUG) {
- Log.d(LOGTAG, "cannot access traces file");
- }
- } else if (DEBUG) {
- Log.d(LOGTAG, "empty getprop result");
- }
- } finally {
- propProc.destroy();
- }
- } catch (IOException e) {
- Log.w(LOGTAG, e);
- } catch (ClassCastException e) {
- Log.w(LOGTAG, e); // Bug 975436
- }
- return null;
- }
-
- private static File getPingFile() {
- if (GeckoAppShell.getContext() == null) {
- return null;
- }
- GeckoProfile profile = GeckoAppShell.getGeckoInterface().getProfile();
- if (profile == null) {
- return null;
- }
- File profDir = profile.getDir();
- if (profDir == null) {
- return null;
- }
- File pingDir = new File(profDir, "saved-telemetry-pings");
- pingDir.mkdirs();
- if (!(pingDir.exists() && pingDir.isDirectory())) {
- return null;
- }
- return new File(pingDir, UUID.randomUUID().toString());
- }
-
- // Return true if the traces file corresponds to a Gecko ANR
- private static boolean isGeckoTraces(String pkgName, File tracesFile) {
- try {
- final String END_OF_PACKAGE_NAME = "([^a-zA-Z0-9_]|$)";
- // Regex for finding our package name in the traces file
- Pattern pkgPattern = Pattern.compile(Pattern.quote(pkgName) + END_OF_PACKAGE_NAME);
- Pattern mangledPattern = null;
- if (!AppConstants.MANGLED_ANDROID_PACKAGE_NAME.equals(pkgName)) {
- mangledPattern = Pattern.compile(Pattern.quote(
- AppConstants.MANGLED_ANDROID_PACKAGE_NAME) + END_OF_PACKAGE_NAME);
- }
- if (DEBUG) {
- Log.d(LOGTAG, "trying to match package: " + pkgName);
- }
- BufferedReader traces = new BufferedReader(
- new FileReader(tracesFile), TRACES_BLOCK_SIZE);
- try {
- for (int count = 0; count < LINES_TO_IDENTIFY_TRACES; count++) {
- String line = traces.readLine();
- if (DEBUG) {
- Log.d(LOGTAG, "identifying line: " + String.valueOf(line));
- }
- if (line == null) {
- if (DEBUG) {
- Log.d(LOGTAG, "reached end of traces file");
- }
- return false;
- }
- if (pkgPattern.matcher(line).find()) {
- // traces.txt file contains our package
- return true;
- }
- if (mangledPattern != null && mangledPattern.matcher(line).find()) {
- // traces.txt file contains our alternate package
- return true;
- }
- }
- } finally {
- traces.close();
- }
- } catch (IOException e) {
- // meh, can't even read from it right. just return false
- }
- return false;
- }
-
- private static long getUptimeMins() {
-
- long uptimeMins = (new File("/proc/self/stat")).lastModified();
- if (uptimeMins != 0L) {
- uptimeMins = (System.currentTimeMillis() - uptimeMins) / 1000L / 60L;
- if (DEBUG) {
- Log.d(LOGTAG, "uptime " + String.valueOf(uptimeMins));
- }
- return uptimeMins;
- }
- if (DEBUG) {
- Log.d(LOGTAG, "could not get uptime");
- }
- return 0L;
- }
-
- /*
- a saved telemetry ping file consists of JSON in the following format,
- {
- "reason": "android-anr-report",
- "slug": "<uuid-string>",
- "payload": <json-object>
- }
- for Android ANR, our JSON payload should look like,
- {
- "ver": 1,
- "simpleMeasurements": {
- "uptime": <uptime>
- },
- "info": {
- "reason": "android-anr-report",
- "OS": "Android",
- ...
- },
- "androidANR": "...",
- "androidLogcat": "..."
- }
- */
-
- private static int writePingPayload(OutputStream ping,
- String payload) throws IOException {
- byte [] data = payload.getBytes(PING_CHARSET);
- ping.write(data);
- return data.length;
- }
-
- private static void fillPingHeader(OutputStream ping, String slug)
- throws IOException {
-
- // ping file header
- byte [] data = ("{" +
- "\"reason\":\"android-anr-report\"," +
- "\"slug\":" + JSONObject.quote(slug) + "," +
- "\"payload\":").getBytes(PING_CHARSET);
- ping.write(data);
- if (DEBUG) {
- Log.d(LOGTAG, "wrote ping header, size = " + String.valueOf(data.length));
- }
-
- // payload start
- int size = writePingPayload(ping, ("{" +
- "\"ver\":1," +
- "\"simpleMeasurements\":{" +
- "\"uptime\":" + String.valueOf(getUptimeMins()) +
- "}," +
- "\"info\":{" +
- "\"reason\":\"android-anr-report\"," +
- "\"OS\":" + JSONObject.quote(SysInfo.getName()) + "," +
- "\"version\":\"" + String.valueOf(SysInfo.getVersion()) + "\"," +
- "\"appID\":" + JSONObject.quote(AppConstants.MOZ_APP_ID) + "," +
- "\"appVersion\":" + JSONObject.quote(AppConstants.MOZ_APP_VERSION) + "," +
- "\"appName\":" + JSONObject.quote(AppConstants.MOZ_APP_BASENAME) + "," +
- "\"appBuildID\":" + JSONObject.quote(AppConstants.MOZ_APP_BUILDID) + "," +
- "\"appUpdateChannel\":" + JSONObject.quote(AppConstants.MOZ_UPDATE_CHANNEL) + "," +
- // Technically the platform build ID may be different, but we'll never know
- "\"platformBuildID\":" + JSONObject.quote(AppConstants.MOZ_APP_BUILDID) + "," +
- "\"locale\":" + JSONObject.quote(Locales.getLanguageTag(Locale.getDefault())) + "," +
- "\"cpucount\":" + String.valueOf(SysInfo.getCPUCount()) + "," +
- "\"memsize\":" + String.valueOf(SysInfo.getMemSize()) + "," +
- "\"arch\":" + JSONObject.quote(SysInfo.getArchABI()) + "," +
- "\"kernel_version\":" + JSONObject.quote(SysInfo.getKernelVersion()) + "," +
- "\"device\":" + JSONObject.quote(SysInfo.getDevice()) + "," +
- "\"manufacturer\":" + JSONObject.quote(SysInfo.getManufacturer()) + "," +
- "\"hardware\":" + JSONObject.quote(SysInfo.getHardware()) +
- "}," +
- "\"androidANR\":\""));
- if (DEBUG) {
- Log.d(LOGTAG, "wrote metadata, size = " + String.valueOf(size));
- }
-
- // We are at the start of ANR data
- }
-
- // Block is a section of the larger input stream, and we want to find pattern within
- // the stream. This is straightforward if the entire pattern is within one block;
- // however, if the pattern spans across two blocks, we have to match both the start of
- // the pattern in the first block and the end of the pattern in the second block.
- // * If pattern is found in block, this method returns the index at the end of the
- // found pattern, which must always be > 0.
- // * If pattern is not found, it returns 0.
- // * If the start of the pattern matches the end of the block, it returns a number
- // < 0, which equals the negated value of how many characters in pattern are already
- // matched; when processing the next block, this number is passed in through
- // prevIndex, and the rest of the characters in pattern are matched against the
- // start of this second block. The method returns value > 0 if the rest of the
- // characters match, or 0 if they do not.
- private static int getEndPatternIndex(String block, String pattern, int prevIndex) {
- if (pattern == null || block.length() < pattern.length()) {
- // Nothing to do
- return 0;
- }
- if (prevIndex < 0) {
- // Last block ended with a partial start; now match start of block to rest of pattern
- if (block.startsWith(pattern.substring(-prevIndex, pattern.length()))) {
- // Rest of pattern matches; return index at end of pattern
- return pattern.length() + prevIndex;
- }
- // Not a match; continue with normal search
- }
- // Did not find pattern in last block; see if entire pattern is inside this block
- int index = block.indexOf(pattern);
- if (index >= 0) {
- // Found pattern; return index at end of the pattern
- return index + pattern.length();
- }
- // Block does not contain the entire pattern, but see if the end of the block
- // contains the start of pattern. To do that, we see if block ends with the
- // first n-1 characters of pattern, the first n-2 characters of pattern, etc.
- for (index = block.length() - pattern.length() + 1; index < block.length(); index++) {
- // Using index as a start, see if the rest of block contains the start of pattern
- if (block.charAt(index) == pattern.charAt(0) &&
- block.endsWith(pattern.substring(0, block.length() - index))) {
- // Found partial match; return -(number of characters matched),
- // i.e. -1 for 1 character matched, -2 for 2 characters matched, etc.
- return index - block.length();
- }
- }
- return 0;
- }
-
- // Copy the content of reader to ping;
- // copying stops when endPattern is found in the input stream
- private static int fillPingBlock(OutputStream ping,
- Reader reader, String endPattern)
- throws IOException {
-
- int total = 0;
- int endIndex = 0;
- char [] block = new char[TRACES_BLOCK_SIZE];
- for (int size = reader.read(block); size >= 0; size = reader.read(block)) {
- String stringBlock = new String(block, 0, size);
- endIndex = getEndPatternIndex(stringBlock, endPattern, endIndex);
- if (endIndex > 0) {
- // Found end pattern; clip the string
- stringBlock = stringBlock.substring(0, endIndex);
- }
- String quoted = JSONObject.quote(stringBlock);
- total += writePingPayload(ping, quoted.substring(1, quoted.length() - 1));
- if (endIndex > 0) {
- // End pattern already found; return now
- break;
- }
- }
- return total;
- }
-
- private static void fillLogcat(final OutputStream ping) {
- if (Versions.preJB) {
- // Logcat retrieval is not supported on pre-JB devices.
- return;
- }
-
- try {
- // get the last 200 lines of logcat
- Process proc = (new ProcessBuilder())
- .command("/system/bin/logcat", "-v", "threadtime", "-t", "200", "-d", "*:D")
- .redirectErrorStream(true)
- .start();
- try {
- Reader procOut = new InputStreamReader(proc.getInputStream(), TRACES_CHARSET);
- int size = fillPingBlock(ping, procOut, null);
- if (DEBUG) {
- Log.d(LOGTAG, "wrote logcat, size = " + String.valueOf(size));
- }
- } finally {
- proc.destroy();
- }
- } catch (IOException e) {
- // ignore because logcat is not essential
- Log.w(LOGTAG, e);
- }
- }
-
- private static void fillPingFooter(OutputStream ping,
- boolean haveNativeStack)
- throws IOException {
-
- // We are at the end of ANR data
-
- int total = writePingPayload(ping, ("\"," +
- "\"androidLogcat\":\""));
- fillLogcat(ping);
-
- if (haveNativeStack) {
- total += writePingPayload(ping, ("\"," +
- "\"androidNativeStack\":"));
-
- String nativeStack = String.valueOf(getNativeStack());
- int size = writePingPayload(ping, nativeStack);
- if (DEBUG) {
- Log.d(LOGTAG, "wrote native stack, size = " + String.valueOf(size));
- }
- total += size + writePingPayload(ping, "}");
- } else {
- total += writePingPayload(ping, "\"}");
- }
-
- byte [] data = (
- "}").getBytes(PING_CHARSET);
- ping.write(data);
- if (DEBUG) {
- Log.d(LOGTAG, "wrote ping footer, size = " + String.valueOf(data.length + total));
- }
- }
-
- private static void processTraces(Reader traces, File pingFile) {
-
- // Only get native stack if Gecko is running.
- // Also, unwinding is memory intensive, so only unwind if we have enough memory.
- final boolean haveNativeStack =
- GeckoThread.isRunning() ?
- requestNativeStack(/* unwind */ SysInfo.getMemSize() >= 640) : false;
-
- try {
- OutputStream ping = new BufferedOutputStream(
- new FileOutputStream(pingFile), TRACES_BLOCK_SIZE);
- try {
- fillPingHeader(ping, pingFile.getName());
- // Traces file has the format
- // ----- pid xxx at xxx -----
- // Cmd line: org.mozilla.xxx
- // * stack trace *
- // ----- end xxx -----
- // ----- pid xxx at xxx -----
- // Cmd line: com.android.xxx
- // * stack trace *
- // ...
- // If we end the stack dump at the first end marker,
- // only Fennec stacks will be dumped
- int size = fillPingBlock(ping, traces, "\n----- end");
- if (DEBUG) {
- Log.d(LOGTAG, "wrote traces, size = " + String.valueOf(size));
- }
- fillPingFooter(ping, haveNativeStack);
- if (DEBUG) {
- Log.d(LOGTAG, "finished creating ping file");
- }
- return;
- } finally {
- ping.close();
- if (haveNativeStack) {
- releaseNativeStack();
- }
- }
- } catch (IOException e) {
- Log.w(LOGTAG, e);
- }
- // exception; delete ping file
- if (pingFile.exists()) {
- pingFile.delete();
- }
- }
-
- private static void processTraces(File tracesFile, File pingFile) {
- try {
- Reader traces = new InputStreamReader(
- new FileInputStream(tracesFile), TRACES_CHARSET);
- try {
- processTraces(traces, pingFile);
- } finally {
- traces.close();
- }
- } catch (IOException e) {
- Log.w(LOGTAG, e);
- }
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (mPendingANR) {
- // we already processed an ANR without getting unstuck; skip this one
- if (DEBUG) {
- Log.d(LOGTAG, "skipping duplicate ANR");
- }
- return;
- }
- if (ThreadUtils.getUiHandler() != null) {
- mPendingANR = true;
- // detect when the main thread gets unstuck
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- // okay to reset mPendingANR on main thread
- mPendingANR = false;
- if (DEBUG) {
- Log.d(LOGTAG, "yay we got unstuck!");
- }
- }
- });
- }
- if (DEBUG) {
- Log.d(LOGTAG, "receiving " + String.valueOf(intent));
- }
- if (!ANR_ACTION.equals(intent.getAction())) {
- return;
- }
-
- // make sure we have a good save location first
- File pingFile = getPingFile();
- if (DEBUG) {
- Log.d(LOGTAG, "using ping file: " + String.valueOf(pingFile));
- }
- if (pingFile == null) {
- return;
- }
-
- File tracesFile = getTracesFile();
- if (DEBUG) {
- Log.d(LOGTAG, "using traces file: " + String.valueOf(tracesFile));
- }
- if (tracesFile == null) {
- return;
- }
-
- // We get ANR intents from all ANRs in the system, but we only want Gecko ANRs
- if (!isGeckoTraces(context.getPackageName(), tracesFile)) {
- if (DEBUG) {
- Log.d(LOGTAG, "traces is not Gecko ANR");
- }
- return;
- }
- Log.i(LOGTAG, "processing Gecko ANR");
- processTraces(tracesFile, pingFile);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/AboutPages.java b/mobile/android/base/java/org/mozilla/gecko/AboutPages.java
deleted file mode 100644
index 705d700af..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/AboutPages.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.home.HomeConfig;
-import org.mozilla.gecko.home.HomeConfig.PanelType;
-import org.mozilla.gecko.util.StringUtils;
-
-public class AboutPages {
- // All of our special pages.
- public static final String ACCOUNTS = "about:accounts";
- public static final String ADDONS = "about:addons";
- public static final String CONFIG = "about:config";
- public static final String DOWNLOADS = "about:downloads";
- public static final String FIREFOX = "about:firefox";
- public static final String HEALTHREPORT = "about:healthreport";
- public static final String HOME = "about:home";
- public static final String LOGINS = "about:logins";
- public static final String PRIVATEBROWSING = "about:privatebrowsing";
- public static final String READER = "about:reader";
- public static final String UPDATER = "about:";
-
- public static final String URL_FILTER = "about:%";
-
- public static final String PANEL_PARAM = "panel";
-
- public static final boolean isAboutPage(final String url) {
- return url != null && url.startsWith("about:");
- }
-
- public static final boolean isTitlelessAboutPage(final String url) {
- return isAboutHome(url) ||
- PRIVATEBROWSING.equals(url);
- }
-
- public static final boolean isAboutHome(final String url) {
- if (url == null || !url.startsWith(HOME)) {
- return false;
- }
- // We sometimes append a parameter to "about:home" to specify which page to
- // show when we open the home pager. Discard this parameter when checking
- // whether or not this URL is "about:home".
- return HOME.equals(url.split("\\?")[0]);
- }
-
- public static final String getPanelIdFromAboutHomeUrl(String aboutHomeUrl) {
- return StringUtils.getQueryParameter(aboutHomeUrl, PANEL_PARAM);
- }
-
- public static boolean isAboutReader(final String url) {
- return isAboutPage(READER, url);
- }
-
- public static boolean isAboutConfig(final String url) {
- return isAboutPage(CONFIG, url);
- }
-
- public static boolean isAboutAddons(final String url) {
- return isAboutPage(ADDONS, url);
- }
-
- public static boolean isAboutPrivateBrowsing(final String url) {
- return isAboutPage(PRIVATEBROWSING, url);
- }
-
- public static boolean isAboutPage(String page, String url) {
- return url != null && url.toLowerCase().startsWith(page);
-
- }
-
- public static final String[] DEFAULT_ICON_PAGES = new String[] {
- HOME,
- ACCOUNTS,
- ADDONS,
- CONFIG,
- DOWNLOADS,
- FIREFOX,
- HEALTHREPORT,
- UPDATER
- };
-
- public static boolean isBuiltinIconPage(final String url) {
- if (url == null ||
- !url.startsWith("about:")) {
- return false;
- }
-
- // about:home uses a separate search built-in icon.
- if (isAboutHome(url)) {
- return true;
- }
-
- // TODO: it'd be quicker to not compare the "about:" part every time.
- for (int i = 0; i < DEFAULT_ICON_PAGES.length; ++i) {
- if (DEFAULT_ICON_PAGES[i].equals(url)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Get a URL that navigates to the specified built-in Home Panel.
- *
- * @param panelType to navigate to.
- * @return URL.
- * @throws IllegalArgumentException if the built-in panel type is not a built-in panel.
- */
- @RobocopTarget
- public static String getURLForBuiltinPanelType(PanelType panelType) throws IllegalArgumentException {
- return HOME + "?panel=" + HomeConfig.getIdForBuiltinPanelType(panelType);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/AccountsHelper.java b/mobile/android/base/java/org/mozilla/gecko/AccountsHelper.java
deleted file mode 100644
index 5892c16b6..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/AccountsHelper.java
+++ /dev/null
@@ -1,318 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.AccountManagerCallback;
-import android.accounts.AccountManagerFuture;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.background.fxa.FxAccountUtils;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.FxAccountDeviceRegistrator;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.fxa.login.Engaged;
-import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.restrictions.Restrictable;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.sync.SyncConfiguration;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.NativeEventListener;
-import org.mozilla.gecko.util.NativeJSObject;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.URISyntaxException;
-import java.security.GeneralSecurityException;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Helper class to manage Android Accounts corresponding to Firefox Accounts.
- */
-public class AccountsHelper implements NativeEventListener {
- public static final String LOGTAG = "GeckoAccounts";
-
- protected final Context mContext;
- protected final GeckoProfile mProfile;
-
- public AccountsHelper(Context context, GeckoProfile profile) {
- mContext = context;
- mProfile = profile;
-
- EventDispatcher dispatcher = GeckoApp.getEventDispatcher();
- if (dispatcher == null) {
- Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
- return;
- }
- dispatcher.registerGeckoThreadListener(this,
- "Accounts:CreateFirefoxAccountFromJSON",
- "Accounts:UpdateFirefoxAccountFromJSON",
- "Accounts:Create",
- "Accounts:DeleteFirefoxAccount",
- "Accounts:Exist",
- "Accounts:ProfileUpdated",
- "Accounts:ShowSyncPreferences");
- }
-
- public synchronized void uninit() {
- EventDispatcher dispatcher = GeckoApp.getEventDispatcher();
- if (dispatcher == null) {
- Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
- return;
- }
- dispatcher.unregisterGeckoThreadListener(this,
- "Accounts:CreateFirefoxAccountFromJSON",
- "Accounts:UpdateFirefoxAccountFromJSON",
- "Accounts:Create",
- "Accounts:DeleteFirefoxAccount",
- "Accounts:Exist",
- "Accounts:ProfileUpdated",
- "Accounts:ShowSyncPreferences");
- }
-
- @Override
- public void handleMessage(String event, NativeJSObject message, final EventCallback callback) {
- if (!Restrictions.isAllowed(mContext, Restrictable.MODIFY_ACCOUNTS)) {
- // We register for messages in all contexts; we drop, with a log and an error to JavaScript,
- // when the profile is restricted. It's better to return errors than silently ignore messages.
- Log.e(LOGTAG, "Profile is not allowed to modify accounts! Ignoring event: " + event);
- if (callback != null) {
- callback.sendError("Profile is not allowed to modify accounts!");
- }
- return;
- }
-
- if ("Accounts:CreateFirefoxAccountFromJSON".equals(event)) {
- // As we are about to create a new account, let's ensure our in-memory accounts cache
- // is empty so that there are no undesired side-effects.
- AndroidFxAccount.invalidateCaches();
-
- AndroidFxAccount fxAccount = null;
- try {
- final NativeJSObject json = message.getObject("json");
- final String email = json.getString("email");
- final String uid = json.getString("uid");
- final boolean verified = json.optBoolean("verified", false);
- final byte[] unwrapkB = Utils.hex2Byte(json.getString("unwrapBKey"));
- final byte[] sessionToken = Utils.hex2Byte(json.getString("sessionToken"));
- final byte[] keyFetchToken = Utils.hex2Byte(json.getString("keyFetchToken"));
- final String authServerEndpoint =
- json.optString("authServerEndpoint", FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT);
- final String tokenServerEndpoint =
- json.optString("tokenServerEndpoint", FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT);
- final String profileServerEndpoint =
- json.optString("profileServerEndpoint", FxAccountConstants.DEFAULT_PROFILE_SERVER_ENDPOINT);
- // TODO: handle choose what to Sync.
- State state = new Engaged(email, uid, verified, unwrapkB, sessionToken, keyFetchToken);
- fxAccount = AndroidFxAccount.addAndroidAccount(mContext,
- email,
- mProfile.getName(),
- authServerEndpoint,
- tokenServerEndpoint,
- profileServerEndpoint,
- state,
- AndroidFxAccount.DEFAULT_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP);
-
- final String[] declinedSyncEngines = json.optStringArray("declinedSyncEngines", null);
- if (declinedSyncEngines != null) {
- Log.i(LOGTAG, "User has selected engines; storing to prefs.");
- final Map<String, Boolean> selectedEngines = new HashMap<String, Boolean>();
- for (String enabledSyncEngine : SyncConfiguration.validEngineNames()) {
- selectedEngines.put(enabledSyncEngine, true);
- }
- for (String declinedSyncEngine : declinedSyncEngines) {
- selectedEngines.put(declinedSyncEngine, false);
- }
- // The "forms" engine has the same state as the "history" engine.
- selectedEngines.put("forms", selectedEngines.get("history"));
- FxAccountUtils.pii(LOGTAG, "User selected engines: " + selectedEngines.toString());
- try {
- SyncConfiguration.storeSelectedEnginesToPrefs(fxAccount.getSyncPrefs(), selectedEngines);
- } catch (UnsupportedEncodingException | GeneralSecurityException e) {
- Log.e(LOGTAG, "Got exception storing selected engines; ignoring.", e);
- }
- }
- } catch (URISyntaxException | GeneralSecurityException | UnsupportedEncodingException e) {
- Log.w(LOGTAG, "Got exception creating Firefox Account from JSON; ignoring.", e);
- if (callback != null) {
- callback.sendError("Could not create Firefox Account from JSON: " + e.toString());
- return;
- }
- }
- if (callback != null) {
- callback.sendSuccess(fxAccount != null);
- }
-
- } else if ("Accounts:UpdateFirefoxAccountFromJSON".equals(event)) {
- // We might be significantly changing state of the account; let's ensure our in-memory
- // accounts cache is empty so that there are no undesired side-effects.
- AndroidFxAccount.invalidateCaches();
-
- try {
- final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
- if (account == null) {
- if (callback != null) {
- callback.sendError("Could not update Firefox Account since none exists");
- }
- return;
- }
-
- final NativeJSObject json = message.getObject("json");
- final String email = json.getString("email");
- final String uid = json.getString("uid");
-
- // Protect against cross-connecting accounts.
- if (account.name == null || !account.name.equals(email)) {
- final String errorMessage = "Cannot update Firefox Account from JSON: datum has different email address!";
- Log.e(LOGTAG, errorMessage);
- if (callback != null) {
- callback.sendError(errorMessage);
- }
- return;
- }
-
- final boolean verified = json.optBoolean("verified", false);
- final byte[] unwrapkB = Utils.hex2Byte(json.getString("unwrapBKey"));
- final byte[] sessionToken = Utils.hex2Byte(json.getString("sessionToken"));
- final byte[] keyFetchToken = Utils.hex2Byte(json.getString("keyFetchToken"));
- final State state = new Engaged(email, uid, verified, unwrapkB, sessionToken, keyFetchToken);
-
- final AndroidFxAccount fxAccount = new AndroidFxAccount(mContext, account);
- fxAccount.setState(state);
-
- if (callback != null) {
- callback.sendSuccess(true);
- }
- } catch (NativeJSObject.InvalidPropertyException e) {
- Log.w(LOGTAG, "Got exception updating Firefox Account from JSON; ignoring.", e);
- if (callback != null) {
- callback.sendError("Could not update Firefox Account from JSON: " + e.toString());
- return;
- }
- }
-
- } else if ("Accounts:Create".equals(event)) {
- // Do exactly the same thing as if you tapped 'Sync' in Settings.
- final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- final NativeJSObject extras = message.optObject("extras", null);
- if (extras != null) {
- intent.putExtra("extras", extras.toString());
- }
- mContext.startActivity(intent);
-
- } else if ("Accounts:DeleteFirefoxAccount".equals(event)) {
- try {
- final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
- if (account == null) {
- Log.w(LOGTAG, "Could not delete Firefox Account since none exists!");
- if (callback != null) {
- callback.sendError("Could not delete Firefox Account since none exists");
- }
- return;
- }
-
- final AccountManagerCallback<Boolean> accountManagerCallback = new AccountManagerCallback<Boolean>() {
- @Override
- public void run(AccountManagerFuture<Boolean> future) {
- try {
- final boolean result = future.getResult();
- Log.i(LOGTAG, "Account named like " + Utils.obfuscateEmail(account.name) + " removed: " + result);
- if (callback != null) {
- callback.sendSuccess(result);
- }
- } catch (OperationCanceledException | IOException | AuthenticatorException e) {
- if (callback != null) {
- callback.sendError("Could not delete Firefox Account: " + e.toString());
- }
- }
- }
- };
-
- AccountManager.get(mContext).removeAccount(account, accountManagerCallback, null);
- } catch (Exception e) {
- Log.w(LOGTAG, "Got exception updating Firefox Account from JSON; ignoring.", e);
- if (callback != null) {
- callback.sendError("Could not update Firefox Account from JSON: " + e.toString());
- return;
- }
- }
-
- } else if ("Accounts:Exist".equals(event)) {
- if (callback == null) {
- Log.w(LOGTAG, "Accounts:Exist requires a callback");
- return;
- }
-
- final String kind = message.optString("kind", null);
- final JSONObject response = new JSONObject();
-
- try {
- if ("any".equals(kind)) {
- response.put("exists", FirefoxAccounts.firefoxAccountsExist(mContext));
- callback.sendSuccess(response);
- } else if ("fxa".equals(kind)) {
- final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
- response.put("exists", account != null);
- if (account != null) {
- response.put("email", account.name);
- // We should always be able to extract the server endpoints.
- final AndroidFxAccount fxAccount = new AndroidFxAccount(mContext, account);
- response.put("authServerEndpoint", fxAccount.getAccountServerURI());
- response.put("profileServerEndpoint", fxAccount.getProfileServerURI());
- response.put("tokenServerEndpoint", fxAccount.getTokenServerURI());
- try {
- // It is possible for the state fetch to fail and us to not be able to provide a UID.
- // Long term, the UID (and verification flag) will be attached to the Android account
- // user data and not the internal state representation.
- final State state = fxAccount.getState();
- response.put("uid", state.uid);
- } catch (Exception e) {
- Log.w(LOGTAG, "Got exception extracting account UID; ignoring.", e);
- }
- }
-
- callback.sendSuccess(response);
- } else {
- callback.sendError("Could not query account existence: unknown kind.");
- }
- } catch (JSONException e) {
- Log.w(LOGTAG, "Got exception querying account existence; ignoring.", e);
- callback.sendError("Could not query account existence: " + e.toString());
- return;
- }
- } else if ("Accounts:ProfileUpdated".equals(event)) {
- final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
- if (account == null) {
- Log.w(LOGTAG, "Can't change profile of non-existent Firefox Account!; ignored");
- return;
- }
- final AndroidFxAccount androidFxAccount = new AndroidFxAccount(mContext, account);
- androidFxAccount.fetchProfileJSON();
- } else if ("Accounts:ShowSyncPreferences".equals(event)) {
- final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
- if (account == null) {
- Log.w(LOGTAG, "Can't change show Sync preferences of non-existent Firefox Account!; ignored");
- return;
- }
- // We don't necessarily have an Activity context here, so we always start in a new task.
- final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_STATUS);
- intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(intent);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/ActionBarTextSelection.java b/mobile/android/base/java/org/mozilla/gecko/ActionBarTextSelection.java
deleted file mode 100644
index 7f2eb219e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/ActionBarTextSelection.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.menu.GeckoMenu;
-import org.mozilla.gecko.menu.GeckoMenuItem;
-import org.mozilla.gecko.util.ResourceDrawableUtils;
-import org.mozilla.gecko.text.TextSelection;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.ActionModeCompat.Callback;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.view.MenuItem;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.Timer;
-import java.util.TimerTask;
-
-import android.util.Log;
-
-class ActionBarTextSelection implements TextSelection, GeckoEventListener {
- private static final String LOGTAG = "GeckoTextSelection";
- private static final int SHUTDOWN_DELAY_MS = 250;
-
- private final Context context;
-
- private boolean mDraggingHandles;
-
- private String selectionID; // Unique ID provided for each selection action.
-
- private String mCurrentItems;
-
- private TextSelectionActionModeCallback mCallback;
-
- // These timers are used to avoid flicker caused by selection handles showing/hiding quickly.
- // For instance when moving between single handle caret mode and two handle selection mode.
- private final Timer mActionModeTimer = new Timer("actionMode");
- private class ActionModeTimerTask extends TimerTask {
- @Override
- public void run() {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- endActionMode();
- }
- });
- }
- };
- private ActionModeTimerTask mActionModeTimerTask;
-
- ActionBarTextSelection(Context context) {
- this.context = context;
- }
-
- @Override
- public void create() {
- // Only register listeners if we have valid start/middle/end handles
- if (context == null) {
- Log.e(LOGTAG, "Failed to initialize text selection because at least one context is null");
- } else {
- GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
- "TextSelection:ActionbarInit",
- "TextSelection:ActionbarStatus",
- "TextSelection:ActionbarUninit",
- "TextSelection:Update");
- }
- }
-
- @Override
- public boolean dismiss() {
- // We do not call endActionMode() here because this is already handled by the activity.
- return false;
- }
-
- @Override
- public void destroy() {
- if (context == null) {
- Log.e(LOGTAG, "Do not unregister TextSelection:* listeners since context is null");
- } else {
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
- "TextSelection:ActionbarInit",
- "TextSelection:ActionbarStatus",
- "TextSelection:ActionbarUninit",
- "TextSelection:Update");
- }
- }
-
- @Override
- public void handleMessage(final String event, final JSONObject message) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- try {
- if (event.equals("TextSelection:Update")) {
- if (mActionModeTimerTask != null)
- mActionModeTimerTask.cancel();
- showActionMode(message.getJSONArray("actions"));
- } else if (event.equals("TextSelection:ActionbarInit")) {
- // Init / Open the action bar. Note the current selectionID,
- // cancel any pending actionBar close.
- Telemetry.sendUIEvent(TelemetryContract.Event.SHOW,
- TelemetryContract.Method.CONTENT, "text_selection");
-
- selectionID = message.getString("selectionID");
- mCurrentItems = null;
- if (mActionModeTimerTask != null) {
- mActionModeTimerTask.cancel();
- }
-
- } else if (event.equals("TextSelection:ActionbarStatus")) {
- // Ensure async updates from SearchService for example are valid.
- if (selectionID != message.optString("selectionID")) {
- return;
- }
-
- // Update the actionBar actions as provided by Gecko.
- showActionMode(message.getJSONArray("actions"));
-
- } else if (event.equals("TextSelection:ActionbarUninit")) {
- // Uninit the actionbar. Schedule a cancellable close
- // action to avoid UI jank. (During SelectionAll for ex).
- mCurrentItems = null;
- mActionModeTimerTask = new ActionModeTimerTask();
- mActionModeTimer.schedule(mActionModeTimerTask, SHUTDOWN_DELAY_MS);
- }
-
- } catch (JSONException e) {
- Log.e(LOGTAG, "JSON exception", e);
- }
- }
- });
- }
-
- private void showActionMode(final JSONArray items) {
- String itemsString = items.toString();
- if (itemsString.equals(mCurrentItems)) {
- return;
- }
- mCurrentItems = itemsString;
-
- if (mCallback != null) {
- mCallback.updateItems(items);
- return;
- }
-
- if (context instanceof ActionModeCompat.Presenter) {
- final ActionModeCompat.Presenter presenter = (ActionModeCompat.Presenter) context;
- mCallback = new TextSelectionActionModeCallback(items);
- presenter.startActionModeCompat(mCallback);
- mCallback.animateIn();
- }
- }
-
- private void endActionMode() {
- if (context instanceof ActionModeCompat.Presenter) {
- final ActionModeCompat.Presenter presenter = (ActionModeCompat.Presenter) context;
- presenter.endActionModeCompat();
- }
- mCurrentItems = null;
- }
-
- private class TextSelectionActionModeCallback implements Callback {
- private JSONArray mItems;
- private ActionModeCompat mActionMode;
-
- public TextSelectionActionModeCallback(JSONArray items) {
- mItems = items;
- }
-
- public void updateItems(JSONArray items) {
- mItems = items;
- if (mActionMode != null) {
- mActionMode.invalidate();
- }
- }
-
- public void animateIn() {
- if (mActionMode != null) {
- mActionMode.animateIn();
- }
- }
-
- @Override
- public boolean onPrepareActionMode(final ActionModeCompat mode, final GeckoMenu menu) {
- // Android would normally expect us to only update the state of menu items here
- // To make the js-java interaction a bit simpler, we just wipe out the menu here and recreate all
- // the javascript menu items in onPrepare instead. This will be called any time invalidate() is called on the
- // action mode.
- menu.clear();
-
- int length = mItems.length();
- for (int i = 0; i < length; i++) {
- try {
- final JSONObject obj = mItems.getJSONObject(i);
- final GeckoMenuItem menuitem = (GeckoMenuItem) menu.add(0, i, 0, obj.optString("label"));
- final int actionEnum = obj.optBoolean("showAsAction") ? GeckoMenuItem.SHOW_AS_ACTION_ALWAYS : GeckoMenuItem.SHOW_AS_ACTION_NEVER;
- menuitem.setShowAsAction(actionEnum, R.attr.menuItemActionModeStyle);
-
- final String iconString = obj.optString("icon");
- ResourceDrawableUtils.getDrawable(context, iconString, new ResourceDrawableUtils.BitmapLoader() {
- @Override
- public void onBitmapFound(Drawable d) {
- if (d != null) {
- menuitem.setIcon(d);
- }
- }
- });
- } catch (Exception ex) {
- Log.i(LOGTAG, "Exception building menu", ex);
- }
- }
- return true;
- }
-
- @Override
- public boolean onCreateActionMode(ActionModeCompat mode, GeckoMenu unused) {
- mActionMode = mode;
- return true;
- }
-
- @Override
- public boolean onActionItemClicked(ActionModeCompat mode, MenuItem item) {
- try {
- final JSONObject obj = mItems.getJSONObject(item.getItemId());
- GeckoAppShell.notifyObservers("TextSelection:Action", obj.optString("id"));
- return true;
- } catch (Exception ex) {
- Log.i(LOGTAG, "Exception calling action", ex);
- }
- return false;
- }
-
- // Called when the user exits the action mode
- @Override
- public void onDestroyActionMode(ActionModeCompat mode) {
- mActionMode = null;
- mCallback = null;
- final JSONObject args = new JSONObject();
- try {
- args.put("selectionID", selectionID);
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error building JSON arguments for TextSelection:End", e);
- return;
- }
-
- GeckoAppShell.notifyObservers("TextSelection:End", args.toString());
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/ActionModeCompat.java b/mobile/android/base/java/org/mozilla/gecko/ActionModeCompat.java
deleted file mode 100644
index 709c0056f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/ActionModeCompat.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.menu.GeckoMenu;
-import org.mozilla.gecko.menu.GeckoMenuItem;
-import org.mozilla.gecko.widget.GeckoPopupMenu;
-
-import android.view.Gravity;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.Toast;
-
-class ActionModeCompat implements GeckoPopupMenu.OnMenuItemClickListener,
- GeckoPopupMenu.OnMenuItemLongClickListener,
- View.OnClickListener {
- private final String LOGTAG = "GeckoActionModeCompat";
-
- private final Callback mCallback;
- private final ActionModeCompatView mView;
- private final Presenter mPresenter;
-
- /* A set of callbacks to be called during this ActionMode's lifecycle. These will control the
- * creation, interaction with, and destruction of menuitems for the view */
- public static interface Callback {
- /* Called when action mode is first created. Implementors should use this to inflate menu resources. */
- public boolean onCreateActionMode(ActionModeCompat mode, GeckoMenu menu);
-
- /* Called to refresh an action mode's action menu. Called whenever the mode is invalidated. Implementors
- * should use this to enable/disable/show/hide menu items. */
- public boolean onPrepareActionMode(ActionModeCompat mode, GeckoMenu menu);
-
- /* Called to report a user click on an action button. */
- public boolean onActionItemClicked(ActionModeCompat mode, MenuItem item);
-
- /* Called when an action mode is about to be exited and destroyed. */
- public void onDestroyActionMode(ActionModeCompat mode);
- }
-
- /* Presenters handle the actual showing/hiding of the action mode UI in the app. Its their responsibility
- * to create an action mode, and assign it Callbacks and ActionModeCompatView's. */
- public static interface Presenter {
- /* Called when an action mode should be shown */
- public void startActionModeCompat(final Callback callback);
-
- /* Called when whatever action mode is showing should be hidden */
- public void endActionModeCompat();
- }
-
- public ActionModeCompat(Presenter presenter, Callback callback, ActionModeCompatView view) {
- mPresenter = presenter;
- mCallback = callback;
-
- mView = view;
- mView.initForMode(this);
- }
-
- public void finish() {
- // Clearing the menu will also clear the ActionItemBar
- final GeckoMenu menu = mView.getMenu();
- menu.clear();
- menu.close();
-
- if (mCallback != null) {
- mCallback.onDestroyActionMode(this);
- }
- }
-
- public CharSequence getTitle() {
- return mView.getTitle();
- }
-
- public void setTitle(CharSequence title) {
- mView.setTitle(title);
- }
-
- public void setTitle(int resId) {
- mView.setTitle(resId);
- }
-
- public GeckoMenu getMenu() {
- return mView.getMenu();
- }
-
- public void invalidate() {
- if (mCallback != null) {
- mCallback.onPrepareActionMode(this, mView.getMenu());
- }
- mView.invalidate();
- }
-
- public void animateIn() {
- mView.animateIn();
- }
-
- /* GeckoPopupMenu.OnMenuItemClickListener */
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- if (mCallback != null) {
- return mCallback.onActionItemClicked(this, item);
- }
- return false;
- }
-
- /* GeckoPopupMenu.onMenuItemLongClickListener */
- @Override
- public boolean onMenuItemLongClick(MenuItem item) {
- showTooltip((GeckoMenuItem) item);
- return true;
- }
-
- /* View.OnClickListener*/
- @Override
- public void onClick(View v) {
- mPresenter.endActionModeCompat();
- }
-
- private void showTooltip(GeckoMenuItem item) {
- // Computes the tooltip toast screen position (shown when long-tapping the menu item) with regards to the
- // menu item's position (i.e below the item and slightly to the left)
- int[] location = new int[2];
- final View view = item.getActionView();
- view.getLocationOnScreen(location);
-
- int xOffset = location[0] - view.getWidth();
- int yOffset = location[1] + view.getHeight() / 2;
-
- Toast toast = Toast.makeText(view.getContext(), item.getTitle(), Toast.LENGTH_SHORT);
- toast.setGravity(Gravity.TOP | Gravity.LEFT, xOffset, yOffset);
- toast.show();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/ActionModeCompatView.java b/mobile/android/base/java/org/mozilla/gecko/ActionModeCompatView.java
deleted file mode 100644
index c9021b710..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/ActionModeCompatView.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import org.mozilla.gecko.animation.AnimationUtils;
-import org.mozilla.gecko.menu.GeckoMenu;
-import org.mozilla.gecko.widget.GeckoPopupMenu;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.Animation;
-import android.view.animation.ScaleAnimation;
-import android.view.animation.TranslateAnimation;
-import android.widget.Button;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-
-class ActionModeCompatView extends LinearLayout implements GeckoMenu.ActionItemBarPresenter {
- private final String LOGTAG = "GeckoActionModeCompatPresenter";
-
- private static final int SPEC = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
-
- private Button mTitleView;
- private ImageButton mMenuButton;
- private ViewGroup mActionButtonBar;
- private GeckoPopupMenu mPopupMenu;
-
- // Maximum number of items to show as actions
- private static final int MAX_ACTION_ITEMS = 4;
-
- private int mActionButtonsWidth;
-
- private Paint mBottomDividerPaint;
- private int mBottomDividerOffset;
-
- public ActionModeCompatView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init(context, attrs, 0);
- }
-
- public ActionModeCompatView(Context context, AttributeSet attrs, int style) {
- super(context, attrs, style);
- init(context, attrs, style);
- }
-
- public void init(final Context context, final AttributeSet attrs, final int defStyle) {
- LayoutInflater.from(context).inflate(R.layout.actionbar, this);
-
- mTitleView = (Button) findViewById(R.id.actionmode_title);
- mMenuButton = (ImageButton) findViewById(R.id.actionbar_menu);
- mActionButtonBar = (ViewGroup) findViewById(R.id.actionbar_buttons);
-
- mPopupMenu = new GeckoPopupMenu(getContext(), mMenuButton);
- mPopupMenu.getMenu().setActionItemBarPresenter(this);
-
- mMenuButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- openMenu();
- }
- });
-
- // The built-in action bar uses colorAccent for the divider so we duplicate that here.
- final TypedArray arr = context.obtainStyledAttributes(attrs, new int[] { R.attr.colorAccent }, defStyle, 0);
- final int bottomDividerColor = arr.getColor(0, 0);
- arr.recycle();
-
- mBottomDividerPaint = new Paint();
- mBottomDividerPaint.setColor(bottomDividerColor);
- mBottomDividerOffset = getResources().getDimensionPixelSize(R.dimen.action_bar_divider_height);
- }
-
- public void initForMode(final ActionModeCompat mode) {
- mTitleView.setOnClickListener(mode);
- mPopupMenu.setOnMenuItemClickListener(mode);
- mPopupMenu.setOnMenuItemLongClickListener(mode);
- }
-
- public CharSequence getTitle() {
- return mTitleView.getText();
- }
-
- public void setTitle(CharSequence title) {
- mTitleView.setText(title);
- }
-
- public void setTitle(int resId) {
- mTitleView.setText(resId);
- }
-
- public GeckoMenu getMenu() {
- return mPopupMenu.getMenu();
- }
-
- @Override
- public void invalidate() {
- // onFinishInflate may not have been called yet on some versions of Android
- if (mPopupMenu != null && mMenuButton != null) {
- mMenuButton.setVisibility(mPopupMenu.getMenu().hasVisibleItems() ? View.VISIBLE : View.GONE);
- }
- super.invalidate();
- }
-
- /* GeckoMenu.ActionItemBarPresenter */
- @Override
- public boolean addActionItem(View actionItem) {
- final int count = mActionButtonBar.getChildCount();
- if (count >= MAX_ACTION_ITEMS) {
- return false;
- }
-
- int maxWidth = mActionButtonBar.getMeasuredWidth();
- if (maxWidth == 0) {
- mActionButtonBar.measure(SPEC, SPEC);
- maxWidth = mActionButtonBar.getMeasuredWidth();
- }
-
- // If the menu button is already visible, no need to account for it
- if (mMenuButton.getVisibility() == View.GONE) {
- // Since we don't know how many items will be added, we always reserve space for the overflow menu
- mMenuButton.measure(SPEC, SPEC);
- maxWidth -= mMenuButton.getMeasuredWidth();
- }
-
- if (mActionButtonsWidth <= 0) {
- mActionButtonsWidth = 0;
-
- // Loop over child views, measure them, and add their width to the taken width
- for (int i = 0; i < count; i++) {
- View v = mActionButtonBar.getChildAt(i);
- v.measure(SPEC, SPEC);
- mActionButtonsWidth += v.getMeasuredWidth();
- }
- }
-
- actionItem.measure(SPEC, SPEC);
- int w = actionItem.getMeasuredWidth();
- if (mActionButtonsWidth + w < maxWidth) {
- // We cache the new width of our children.
- mActionButtonsWidth += w;
- mActionButtonBar.addView(actionItem);
- return true;
- }
-
- return false;
- }
-
- /* GeckoMenu.ActionItemBarPresenter */
- @Override
- public void removeActionItem(View actionItem) {
- actionItem.measure(SPEC, SPEC);
- mActionButtonsWidth -= actionItem.getMeasuredWidth();
- mActionButtonBar.removeView(actionItem);
- }
-
- public void openMenu() {
- mPopupMenu.openMenu();
- }
-
- public void closeMenu() {
- mPopupMenu.dismiss();
- }
-
- public void animateIn() {
- long duration = AnimationUtils.getShortDuration(getContext());
- TranslateAnimation t = new TranslateAnimation(Animation.RELATIVE_TO_SELF, -0.5f, Animation.RELATIVE_TO_SELF, 0f,
- Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 0f);
- t.setDuration(duration);
-
- ScaleAnimation s = new ScaleAnimation(1f, 1f, 0f, 1f,
- Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
- s.setDuration((long) (duration * 1.5f));
-
- mTitleView.startAnimation(t);
- mActionButtonBar.startAnimation(s);
-
- if ((mMenuButton.getVisibility() == View.VISIBLE) &&
- (mPopupMenu.getMenu().size() > 0)) {
- mMenuButton.startAnimation(s);
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- // Draw the divider at the bottom of the screen. We could do this with a layer-list
- // but then we'd have overdraw (http://stackoverflow.com/a/13509472).
- final int bottom = getHeight();
- final int top = bottom - mBottomDividerOffset;
- canvas.drawRect(0, top, getWidth(), bottom, mBottomDividerPaint);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/ActivityHandlerHelper.java b/mobile/android/base/java/org/mozilla/gecko/ActivityHandlerHelper.java
deleted file mode 100644
index 7174c6580..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/ActivityHandlerHelper.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.util.ActivityResultHandler;
-import org.mozilla.gecko.util.ActivityResultHandlerMap;
-
-import android.app.Activity;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-public class ActivityHandlerHelper {
- private static final String LOGTAG = "GeckoActivityHandlerHelper";
- private static final ActivityResultHandlerMap mActivityResultHandlerMap = new ActivityResultHandlerMap();
-
- private static int makeRequestCode(ActivityResultHandler aHandler) {
- return mActivityResultHandlerMap.put(aHandler);
- }
-
- public static void startIntent(Intent intent, ActivityResultHandler activityResultHandler) {
- startIntentForActivity(GeckoAppShell.getGeckoInterface().getActivity(), intent, activityResultHandler);
- }
-
- /**
- * Starts the Activity, catching & logging if the Activity fails to start.
- *
- * We catch to prevent callers from passing in invalid Intents and crashing the browser.
- *
- * @return true if the Activity is successfully started, false otherwise.
- */
- public static boolean startIntentAndCatch(final String logtag, final Context context, final Intent intent) {
- try {
- context.startActivity(intent);
- return true;
- } catch (final ActivityNotFoundException e) {
- Log.w(logtag, "Activity not found.", e);
- return false;
- } catch (final SecurityException e) {
- Log.w(logtag, "Forbidden to launch activity.", e);
- return false;
- }
- }
-
- public static void startIntentForActivity(Activity activity, Intent intent, ActivityResultHandler activityResultHandler) {
- activity.startActivityForResult(intent, mActivityResultHandlerMap.put(activityResultHandler));
- }
-
-
- public static boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
- ActivityResultHandler handler = mActivityResultHandlerMap.getAndRemove(requestCode);
- if (handler != null) {
- handler.onActivityResult(resultCode, data);
- return true;
- }
- return false;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/BootReceiver.java b/mobile/android/base/java/org/mozilla/gecko/BootReceiver.java
deleted file mode 100644
index 39ca25b67..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/BootReceiver.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-import org.mozilla.gecko.feeds.FeedService;
-
-/**
- * This broadcast receiver receives ACTION_BOOT_COMPLETED broadcasts and starts components that should
- * run after the device has booted.
- */
-public class BootReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent == null || !intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
- return; // This is not the broadcast you are looking for.
- }
-
- FeedService.setup(context);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
deleted file mode 100644
index 5eddca3cf..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ /dev/null
@@ -1,4261 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.Manifest;
-import android.annotation.TargetApi;
-import android.app.DownloadManager;
-import android.content.ContentProviderClient;
-import android.os.Environment;
-import android.os.Process;
-import android.support.annotation.NonNull;
-
-import android.graphics.Rect;
-
-import org.json.JSONArray;
-import org.mozilla.gecko.activitystream.ActivityStream;
-import org.mozilla.gecko.adjust.AdjustBrowserAppDelegate;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
-import org.mozilla.gecko.Tabs.TabEvents;
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.animation.ViewHelper;
-import org.mozilla.gecko.cleanup.FileCleanupController;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.SuggestedSites;
-import org.mozilla.gecko.delegates.BrowserAppDelegate;
-import org.mozilla.gecko.delegates.OfflineTabStatusDelegate;
-import org.mozilla.gecko.delegates.ScreenshotDelegate;
-import org.mozilla.gecko.distribution.Distribution;
-import org.mozilla.gecko.distribution.DistributionStoreCallback;
-import org.mozilla.gecko.distribution.PartnerBrowserCustomizationsClient;
-import org.mozilla.gecko.dlc.DownloadContentService;
-import org.mozilla.gecko.icons.decoders.IconDirectoryEntry;
-import org.mozilla.gecko.feeds.ContentNotificationsDelegate;
-import org.mozilla.gecko.feeds.FeedService;
-import org.mozilla.gecko.firstrun.FirstrunAnimationContainer;
-import org.mozilla.gecko.gfx.DynamicToolbarAnimator;
-import org.mozilla.gecko.gfx.DynamicToolbarAnimator.PinReason;
-import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
-import org.mozilla.gecko.gfx.LayerView;
-import org.mozilla.gecko.home.BrowserSearch;
-import org.mozilla.gecko.home.HomeBanner;
-import org.mozilla.gecko.home.HomeConfig;
-import org.mozilla.gecko.home.HomeConfig.PanelType;
-import org.mozilla.gecko.home.HomeConfigPrefsBackend;
-import org.mozilla.gecko.home.HomeFragment;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.home.HomePanelsManager;
-import org.mozilla.gecko.home.HomeScreen;
-import org.mozilla.gecko.home.SearchEngine;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.javaaddons.JavaAddonManager;
-import org.mozilla.gecko.media.VideoPlayer;
-import org.mozilla.gecko.menu.GeckoMenu;
-import org.mozilla.gecko.menu.GeckoMenuItem;
-import org.mozilla.gecko.mozglue.SafeIntent;
-import org.mozilla.gecko.notifications.NotificationHelper;
-import org.mozilla.gecko.overlays.ui.ShareDialog;
-import org.mozilla.gecko.permissions.Permissions;
-import org.mozilla.gecko.preferences.ClearOnShutdownPref;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.promotion.AddToHomeScreenPromotion;
-import org.mozilla.gecko.delegates.BookmarkStateChangeDelegate;
-import org.mozilla.gecko.promotion.ReaderViewBookmarkPromotion;
-import org.mozilla.gecko.prompts.Prompt;
-import org.mozilla.gecko.reader.SavedReaderViewHelper;
-import org.mozilla.gecko.reader.ReaderModeUtils;
-import org.mozilla.gecko.reader.ReadingListHelper;
-import org.mozilla.gecko.restrictions.Restrictable;
-import org.mozilla.gecko.restrictions.RestrictedProfileConfiguration;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.search.SearchEngineManager;
-import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
-import org.mozilla.gecko.tabqueue.TabQueueHelper;
-import org.mozilla.gecko.tabqueue.TabQueuePrompt;
-import org.mozilla.gecko.tabs.TabHistoryController;
-import org.mozilla.gecko.tabs.TabHistoryController.OnShowTabHistory;
-import org.mozilla.gecko.tabs.TabHistoryFragment;
-import org.mozilla.gecko.tabs.TabHistoryPage;
-import org.mozilla.gecko.tabs.TabsPanel;
-import org.mozilla.gecko.telemetry.TelemetryUploadService;
-import org.mozilla.gecko.telemetry.TelemetryCorePingDelegate;
-import org.mozilla.gecko.telemetry.measurements.SearchCountMeasurements;
-import org.mozilla.gecko.toolbar.AutocompleteHandler;
-import org.mozilla.gecko.toolbar.BrowserToolbar;
-import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
-import org.mozilla.gecko.toolbar.ToolbarProgressView;
-import org.mozilla.gecko.trackingprotection.TrackingProtectionPrompt;
-import org.mozilla.gecko.updater.PostUpdateHandler;
-import org.mozilla.gecko.updater.UpdateServiceHelper;
-import org.mozilla.gecko.util.ActivityUtils;
-import org.mozilla.gecko.util.Clipboard;
-import org.mozilla.gecko.util.ContextUtils;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.FloatUtils;
-import org.mozilla.gecko.util.GamepadUtils;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.IntentUtils;
-import org.mozilla.gecko.util.MenuUtils;
-import org.mozilla.gecko.util.NativeEventListener;
-import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.gecko.util.PrefUtils;
-import org.mozilla.gecko.util.StringUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.widget.AnchoredPopup;
-
-import org.mozilla.gecko.widget.GeckoActionProvider;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.net.Uri;
-import android.nfc.NdefMessage;
-import android.nfc.NdefRecord;
-import android.nfc.NfcAdapter;
-import android.nfc.NfcEvent;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.StrictMode;
-import android.support.design.widget.Snackbar;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.view.MenuItemCompat;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Base64;
-import android.util.Base64OutputStream;
-import android.util.Log;
-import android.view.InputDevice;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.SubMenu;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.view.ViewTreeObserver;
-import android.view.Window;
-import android.view.animation.Interpolator;
-import android.widget.Button;
-import android.widget.ListView;
-import android.widget.RelativeLayout;
-import android.widget.ViewFlipper;
-import com.keepsafe.switchboard.AsyncConfigLoader;
-import com.keepsafe.switchboard.SwitchBoard;
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.net.URLEncoder;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Vector;
-import java.util.regex.Pattern;
-
-public class BrowserApp extends GeckoApp
- implements TabsPanel.TabsLayoutChangeListener,
- PropertyAnimator.PropertyAnimationListener,
- View.OnKeyListener,
- LayerView.DynamicToolbarListener,
- BrowserSearch.OnSearchListener,
- BrowserSearch.OnEditSuggestionListener,
- OnUrlOpenListener,
- OnUrlOpenInBackgroundListener,
- AnchoredPopup.OnVisibilityChangeListener,
- ActionModeCompat.Presenter,
- LayoutInflater.Factory {
- private static final String LOGTAG = "GeckoBrowserApp";
-
- private static final int TABS_ANIMATION_DURATION = 450;
-
- // Intent String extras used to specify custom Switchboard configurations.
- private static final String INTENT_KEY_SWITCHBOARD_SERVER = "switchboard-server";
-
- // TODO: Replace with kinto endpoint.
- private static final String SWITCHBOARD_SERVER = "https://firefox.settings.services.mozilla.com/v1/buckets/fennec/collections/experiments/records";
-
- private static final String STATE_ABOUT_HOME_TOP_PADDING = "abouthome_top_padding";
-
- private static final String BROWSER_SEARCH_TAG = "browser_search";
-
- // Request ID for startActivityForResult.
- private static final int ACTIVITY_REQUEST_PREFERENCES = 1001;
- private static final int ACTIVITY_REQUEST_TAB_QUEUE = 2001;
- public static final int ACTIVITY_REQUEST_FIRST_READERVIEW_BOOKMARK = 3001;
- public static final int ACTIVITY_RESULT_FIRST_READERVIEW_BOOKMARKS_GOTO_BOOKMARKS = 3002;
- public static final int ACTIVITY_RESULT_FIRST_READERVIEW_BOOKMARKS_IGNORE = 3003;
- public static final int ACTIVITY_REQUEST_TRIPLE_READERVIEW = 4001;
- public static final int ACTIVITY_RESULT_TRIPLE_READERVIEW_ADD_BOOKMARK = 4002;
- public static final int ACTIVITY_RESULT_TRIPLE_READERVIEW_IGNORE = 4003;
-
- public static final String ACTION_VIEW_MULTIPLE = AppConstants.ANDROID_PACKAGE_NAME + ".action.VIEW_MULTIPLE";
-
- @RobocopTarget
- public static final String EXTRA_SKIP_STARTPANE = "skipstartpane";
- private static final String EOL_NOTIFIED = "eol_notified";
-
- private BrowserSearch mBrowserSearch;
- private View mBrowserSearchContainer;
-
- public ViewGroup mBrowserChrome;
- public ViewFlipper mActionBarFlipper;
- public ActionModeCompatView mActionBar;
- private VideoPlayer mVideoPlayer;
- private BrowserToolbar mBrowserToolbar;
- private View mDoorhangerOverlay;
- // We can't name the TabStrip class because it's not included on API 9.
- private TabStripInterface mTabStrip;
- private ToolbarProgressView mProgressView;
- private FirstrunAnimationContainer mFirstrunAnimationContainer;
- private HomeScreen mHomeScreen;
- private TabsPanel mTabsPanel;
- /**
- * Container for the home screen implementation. This will be populated with any valid
- * home screen implementation (currently that is just the HomePager, but that will be extended
- * to permit further experimental replacement panels such as the activity-stream panel).
- */
- private ViewGroup mHomeScreenContainer;
- private int mCachedRecentTabsCount;
- private ActionModeCompat mActionMode;
- private TabHistoryController tabHistoryController;
- private ZoomedView mZoomedView;
-
- private static final int GECKO_TOOLS_MENU = -1;
- private static final int ADDON_MENU_OFFSET = 1000;
- public static final String TAB_HISTORY_FRAGMENT_TAG = "tabHistoryFragment";
-
- private static class MenuItemInfo {
- public int id;
- public String label;
- public boolean checkable;
- public boolean checked;
- public boolean enabled = true;
- public boolean visible = true;
- public int parent;
- public boolean added; // So we can re-add after a locale change.
- }
-
- // The types of guest mode dialogs we show.
- public static enum GuestModeDialog {
- ENTERING,
- LEAVING
- }
-
- private Vector<MenuItemInfo> mAddonMenuItemsCache;
- private PropertyAnimator mMainLayoutAnimator;
-
- private static final Interpolator sTabsInterpolator = new Interpolator() {
- @Override
- public float getInterpolation(float t) {
- t -= 1.0f;
- return t * t * t * t * t + 1.0f;
- }
- };
-
- private FindInPageBar mFindInPageBar;
- private MediaCastingBar mMediaCastingBar;
-
- // We'll ask for feedback after the user launches the app this many times.
- private static final int FEEDBACK_LAUNCH_COUNT = 15;
-
- // Stored value of the toolbar height, so we know when it's changed.
- private int mToolbarHeight;
-
- private SharedPreferencesHelper mSharedPreferencesHelper;
-
- private ReadingListHelper mReadingListHelper;
-
- private AccountsHelper mAccountsHelper;
-
- // The tab to be selected on editing mode exit.
- private Integer mTargetTabForEditingMode;
-
- private final TabEditingState mLastTabEditingState = new TabEditingState();
-
- // The animator used to toggle HomePager visibility has a race where if the HomePager is shown
- // (starting the animation), the HomePager is hidden, and the HomePager animation completes,
- // both the web content and the HomePager will be hidden. This flag is used to prevent the
- // race by determining if the web content should be hidden at the animation's end.
- private boolean mHideWebContentOnAnimationEnd;
-
- private final DynamicToolbar mDynamicToolbar = new DynamicToolbar();
-
- private final TelemetryCorePingDelegate mTelemetryCorePingDelegate = new TelemetryCorePingDelegate();
-
- private final List<BrowserAppDelegate> delegates = Collections.unmodifiableList(Arrays.asList(
- new AddToHomeScreenPromotion(),
- new ScreenshotDelegate(),
- new BookmarkStateChangeDelegate(),
- new ReaderViewBookmarkPromotion(),
- new ContentNotificationsDelegate(),
- new PostUpdateHandler(),
- mTelemetryCorePingDelegate,
- new OfflineTabStatusDelegate(),
- new AdjustBrowserAppDelegate(mTelemetryCorePingDelegate)
- ));
-
- @NonNull
- private SearchEngineManager mSearchEngineManager; // Contains reference to Context - DO NOT LEAK!
-
- private boolean mHasResumed;
-
- @Override
- public View onCreateView(final String name, final Context context, final AttributeSet attrs) {
- final View view;
- if (BrowserToolbar.class.getName().equals(name)) {
- view = BrowserToolbar.create(context, attrs);
- } else if (TabsPanel.TabsLayout.class.getName().equals(name)) {
- view = TabsPanel.createTabsLayout(context, attrs);
- } else {
- view = super.onCreateView(name, context, attrs);
- }
- return view;
- }
-
- @Override
- public void onTabChanged(Tab tab, TabEvents msg, String data) {
- if (tab == null) {
- // Only RESTORED is allowed a null tab: it's the only event that
- // isn't tied to a specific tab.
- if (msg != Tabs.TabEvents.RESTORED) {
- throw new IllegalArgumentException("onTabChanged:" + msg + " must specify a tab.");
- }
-
- final Tab selectedTab = Tabs.getInstance().getSelectedTab();
- if (selectedTab != null) {
- // After restoring the tabs we want to update the home pager immediately. Otherwise we
- // might wait for an event coming from Gecko and this can take several seconds. (Bug 1283627)
- updateHomePagerForTab(selectedTab);
- }
-
- return;
- }
-
- Log.d(LOGTAG, "BrowserApp.onTabChanged: " + tab.getId() + ": " + msg);
- switch (msg) {
- case SELECTED:
- if (mVideoPlayer.isPlaying()) {
- mVideoPlayer.stop();
- }
-
- if (Tabs.getInstance().isSelectedTab(tab) && mDynamicToolbar.isEnabled()) {
- final VisibilityTransition transition = (tab.getShouldShowToolbarWithoutAnimationOnFirstSelection()) ?
- VisibilityTransition.IMMEDIATE : VisibilityTransition.ANIMATE;
- mDynamicToolbar.setVisible(true, transition);
-
- // The first selection has happened - reset the state.
- tab.setShouldShowToolbarWithoutAnimationOnFirstSelection(false);
- }
- // fall through
- case LOCATION_CHANGE:
- if (mZoomedView != null) {
- mZoomedView.stopZoomDisplay(false);
- }
- if (Tabs.getInstance().isSelectedTab(tab)) {
- updateHomePagerForTab(tab);
- }
-
- mDynamicToolbar.persistTemporaryVisibility();
- break;
- case START:
- if (Tabs.getInstance().isSelectedTab(tab)) {
- invalidateOptionsMenu();
-
- if (mDynamicToolbar.isEnabled()) {
- mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
- }
- }
- break;
- case LOAD_ERROR:
- case STOP:
- case MENU_UPDATED:
- if (Tabs.getInstance().isSelectedTab(tab)) {
- invalidateOptionsMenu();
- }
- break;
- case PAGE_SHOW:
- tab.loadFavicon();
- break;
-
- case UNSELECTED:
- // We receive UNSELECTED immediately after the SELECTED listeners run
- // so we are ensured that the unselectedTabEditingText has not changed.
- if (tab.isEditing()) {
- // Copy to avoid constructing new objects.
- tab.getEditingState().copyFrom(mLastTabEditingState);
- }
- break;
- }
-
- if (HardwareUtils.isTablet() && msg == TabEvents.SELECTED) {
- updateEditingModeForTab(tab);
- }
-
- super.onTabChanged(tab, msg, data);
- }
-
- private void updateEditingModeForTab(final Tab selectedTab) {
- // (bug 1086983 comment 11) Because the tab may be selected from the gecko thread and we're
- // running this code on the UI thread, the selected tab argument may not still refer to the
- // selected tab. However, that means this code should be run again and the initial state
- // changes will be overridden. As an optimization, we can skip this update, but it may have
- // unknown side-effects so we don't.
- if (!Tabs.getInstance().isSelectedTab(selectedTab)) {
- Log.w(LOGTAG, "updateEditingModeForTab: Given tab is expected to be selected tab");
- }
-
- saveTabEditingState(mLastTabEditingState);
-
- if (selectedTab.isEditing()) {
- enterEditingMode();
- restoreTabEditingState(selectedTab.getEditingState());
- } else {
- mBrowserToolbar.cancelEdit();
- }
- }
-
- private void saveTabEditingState(final TabEditingState editingState) {
- mBrowserToolbar.saveTabEditingState(editingState);
- editingState.setIsBrowserSearchShown(mBrowserSearch.getUserVisibleHint());
- }
-
- private void restoreTabEditingState(final TabEditingState editingState) {
- mBrowserToolbar.restoreTabEditingState(editingState);
-
- // Since changing the editing text will show/hide browser search, this
- // must be called after we restore the editing state in the edit text View.
- if (editingState.isBrowserSearchShown()) {
- showBrowserSearch();
- } else {
- hideBrowserSearch();
- }
- }
-
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (AndroidGamepadManager.handleKeyEvent(event)) {
- return true;
- }
-
- // Global onKey handler. This is called if the focused UI doesn't
- // handle the key event, and before Gecko swallows the events.
- if (event.getAction() != KeyEvent.ACTION_DOWN) {
- return false;
- }
-
- if ((event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_BUTTON_Y:
- // Toggle/focus the address bar on gamepad-y button.
- if (mBrowserChrome.getVisibility() == View.VISIBLE) {
- if (mDynamicToolbar.isEnabled() && !isHomePagerVisible()) {
- mDynamicToolbar.setVisible(false, VisibilityTransition.ANIMATE);
- if (mLayerView != null) {
- mLayerView.requestFocus();
- }
- } else {
- // Just focus the address bar when about:home is visible
- // or when the dynamic toolbar isn't enabled.
- mBrowserToolbar.requestFocusFromTouch();
- }
- } else {
- mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
- mBrowserToolbar.requestFocusFromTouch();
- }
- return true;
- case KeyEvent.KEYCODE_BUTTON_L1:
- // Go back on L1
- Tabs.getInstance().getSelectedTab().doBack();
- return true;
- case KeyEvent.KEYCODE_BUTTON_R1:
- // Go forward on R1
- Tabs.getInstance().getSelectedTab().doForward();
- return true;
- }
- }
-
- // Check if this was a shortcut. Meta keys exists only on 11+.
- final Tab tab = Tabs.getInstance().getSelectedTab();
- if (tab != null && event.isCtrlPressed()) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_LEFT_BRACKET:
- tab.doBack();
- return true;
-
- case KeyEvent.KEYCODE_RIGHT_BRACKET:
- tab.doForward();
- return true;
-
- case KeyEvent.KEYCODE_R:
- tab.doReload(false);
- return true;
-
- case KeyEvent.KEYCODE_PERIOD:
- tab.doStop();
- return true;
-
- case KeyEvent.KEYCODE_T:
- addTab();
- return true;
-
- case KeyEvent.KEYCODE_W:
- Tabs.getInstance().closeTab(tab);
- return true;
-
- case KeyEvent.KEYCODE_F:
- mFindInPageBar.show();
- return true;
- }
- }
-
- return false;
- }
-
- private Runnable mCheckLongPress;
- {
- // Only initialise the runnable if we are >= N.
- // See onKeyDown() for more details of the back-button long-press workaround
- if (!Versions.preN) {
- mCheckLongPress = new Runnable() {
- public void run() {
- handleBackLongPress();
- }
- };
- }
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- // Bug 1304688: Android N has broken passing onKeyLongPress events for the back button, so we
- // instead copy the long-press-handler technique from Android's KeyButtonView.
- // - For short presses, we cancel the callback in onKeyUp
- // - For long presses, the normal keypress is marked as cancelled, hence won't be handled elsewhere
- // (but Android still provides the haptic feedback), and the runnable is run.
- if (!Versions.preN &&
- keyCode == KeyEvent.KEYCODE_BACK) {
- ThreadUtils.getUiHandler().removeCallbacks(mCheckLongPress);
- ThreadUtils.getUiHandler().postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
- }
-
- if (!mBrowserToolbar.isEditing() && onKey(null, keyCode, event)) {
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (!Versions.preN &&
- keyCode == KeyEvent.KEYCODE_BACK) {
- ThreadUtils.getUiHandler().removeCallbacks(mCheckLongPress);
- }
-
- if (AndroidGamepadManager.handleKeyEvent(event)) {
- return true;
- }
- return super.onKeyUp(keyCode, event);
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- if (!HardwareUtils.isSupportedSystem()) {
- // This build does not support the Android version of the device; Exit early.
- super.onCreate(savedInstanceState);
- return;
- }
-
- final SafeIntent intent = new SafeIntent(getIntent());
- final boolean isInAutomation = IntentUtils.getIsInAutomationFromEnvironment(intent);
-
- // This has to be prepared prior to calling GeckoApp.onCreate, because
- // widget code and BrowserToolbar need it, and they're created by the
- // layout, which GeckoApp takes care of.
- ((GeckoApplication) getApplication()).prepareLightweightTheme();
-
- super.onCreate(savedInstanceState);
-
- final Context appContext = getApplicationContext();
-
- initSwitchboard(this, intent, isInAutomation);
- initTelemetryUploader(isInAutomation);
-
- mBrowserChrome = (ViewGroup) findViewById(R.id.browser_chrome);
- mActionBarFlipper = (ViewFlipper) findViewById(R.id.browser_actionbar);
- mActionBar = (ActionModeCompatView) findViewById(R.id.actionbar);
-
- mVideoPlayer = (VideoPlayer) findViewById(R.id.video_player);
- mVideoPlayer.setFullScreenListener(new VideoPlayer.FullScreenListener() {
- @Override
- public void onFullScreenChanged(boolean fullScreen) {
- mVideoPlayer.setFullScreen(fullScreen);
- setFullScreen(fullScreen);
- }
- });
-
- mBrowserToolbar = (BrowserToolbar) findViewById(R.id.browser_toolbar);
- mBrowserToolbar.setTouchEventInterceptor(new TouchEventInterceptor() {
- @Override
- public boolean onInterceptTouchEvent(View view, MotionEvent event) {
- // Manually dismiss text selection bar if it's not overlaying the toolbar.
- mTextSelection.dismiss();
- return false;
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- return false;
- }
- });
-
- mProgressView = (ToolbarProgressView) findViewById(R.id.progress);
- mBrowserToolbar.setProgressBar(mProgressView);
-
- // Initialize Tab History Controller.
- tabHistoryController = new TabHistoryController(new OnShowTabHistory() {
- @Override
- public void onShowHistory(final List<TabHistoryPage> historyPageList, final int toIndex) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (BrowserApp.this.isFinishing()) {
- // TabHistoryController is rather slow - and involves calling into Gecko
- // to retrieve tab history. That means there can be a significant
- // delay between the back-button long-press, and onShowHistory()
- // being called. Hence we need to guard against the Activity being
- // shut down (in which case trying to perform UI changes, such as showing
- // fragments below, will crash).
- return;
- }
-
- final TabHistoryFragment fragment = TabHistoryFragment.newInstance(historyPageList, toIndex);
- final FragmentManager fragmentManager = getSupportFragmentManager();
- GeckoAppShell.vibrateOnHapticFeedbackEnabled(getResources().getIntArray(R.array.long_press_vibrate_msec));
- fragment.show(R.id.tab_history_panel, fragmentManager.beginTransaction(), TAB_HISTORY_FRAGMENT_TAG);
- }
- });
- }
- });
- mBrowserToolbar.setTabHistoryController(tabHistoryController);
-
- final String action = intent.getAction();
- if (Intent.ACTION_VIEW.equals(action)) {
- // Show the target URL immediately in the toolbar.
- mBrowserToolbar.setTitle(intent.getDataString());
-
- showTabQueuePromptIfApplicable(intent);
- } else if (ACTION_VIEW_MULTIPLE.equals(action) && savedInstanceState == null) {
- // We only want to handle this intent if savedInstanceState is null. In the case where
- // savedInstanceState is not null this activity is being re-created and we already
- // opened tabs for the URLs the last time. Our session store will take care of restoring
- // them.
- openMultipleTabsFromIntent(intent);
- } else if (GuestSession.NOTIFICATION_INTENT.equals(action)) {
- GuestSession.onNotificationIntentReceived(this);
- } else if (TabQueueHelper.LOAD_URLS_ACTION.equals(action)) {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.NOTIFICATION, "tabqueue");
- }
-
- if (HardwareUtils.isTablet()) {
- mTabStrip = (TabStripInterface) (((ViewStub) findViewById(R.id.tablet_tab_strip)).inflate());
- }
-
- ((GeckoApp.MainLayout) mMainLayout).setTouchEventInterceptor(new HideOnTouchListener());
- ((GeckoApp.MainLayout) mMainLayout).setMotionEventInterceptor(new MotionEventInterceptor() {
- @Override
- public boolean onInterceptMotionEvent(View view, MotionEvent event) {
- // If we get a gamepad panning MotionEvent while the focus is not on the layerview,
- // put the focus on the layerview and carry on
- if (mLayerView != null && !mLayerView.hasFocus() && GamepadUtils.isPanningControl(event)) {
- if (mHomeScreen == null) {
- return false;
- }
-
- if (isHomePagerVisible()) {
- mLayerView.requestFocus();
- } else {
- mHomeScreen.requestFocus();
- }
- }
- return false;
- }
- });
-
- mHomeScreenContainer = (ViewGroup) findViewById(R.id.home_screen_container);
-
- mBrowserSearchContainer = findViewById(R.id.search_container);
- mBrowserSearch = (BrowserSearch) getSupportFragmentManager().findFragmentByTag(BROWSER_SEARCH_TAG);
- if (mBrowserSearch == null) {
- mBrowserSearch = BrowserSearch.newInstance();
- mBrowserSearch.setUserVisibleHint(false);
- }
-
- setBrowserToolbarListeners();
-
- mFindInPageBar = (FindInPageBar) findViewById(R.id.find_in_page);
- mMediaCastingBar = (MediaCastingBar) findViewById(R.id.media_casting);
-
- mDoorhangerOverlay = findViewById(R.id.doorhanger_overlay);
-
- EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener)this,
- "Gecko:DelayedStartup",
- "Menu:Open",
- "Menu:Update",
- "LightweightTheme:Update",
- "Search:Keyword",
- "Prompt:ShowTop",
- "Tab:Added",
- "Video:Play");
-
- EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener)this,
- "CharEncoding:Data",
- "CharEncoding:State",
- "Download:AndroidDownloadManager",
- "Experiments:GetActive",
- "Experiments:SetOverride",
- "Experiments:ClearOverride",
- "Favicon:CacheLoad",
- "Feedback:MaybeLater",
- "Menu:Add",
- "Menu:Remove",
- "Sanitize:ClearHistory",
- "Sanitize:ClearSyncedTabs",
- "Settings:Show",
- "Telemetry:Gather",
- "Updater:Launch",
- "Website:Metadata");
-
- final GeckoProfile profile = getProfile();
-
- // We want to upload the telemetry core ping as soon after startup as possible. It relies on the
- // Distribution being initialized. If you move this initialization, ensure it plays well with telemetry.
- final Distribution distribution = Distribution.init(getApplicationContext());
- distribution.addOnDistributionReadyCallback(
- new DistributionStoreCallback(getApplicationContext(), profile.getName()));
-
- mSearchEngineManager = new SearchEngineManager(this, distribution);
-
- // Init suggested sites engine in BrowserDB.
- final SuggestedSites suggestedSites = new SuggestedSites(appContext, distribution);
- final BrowserDB db = BrowserDB.from(profile);
- db.setSuggestedSites(suggestedSites);
-
- JavaAddonManager.getInstance().init(appContext);
- mSharedPreferencesHelper = new SharedPreferencesHelper(appContext);
- mReadingListHelper = new ReadingListHelper(appContext, profile);
- mAccountsHelper = new AccountsHelper(appContext, profile);
-
- if (AppConstants.MOZ_ANDROID_BEAM) {
- NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
- if (nfc != null) {
- nfc.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
- @Override
- public NdefMessage createNdefMessage(NfcEvent event) {
- Tab tab = Tabs.getInstance().getSelectedTab();
- if (tab == null || tab.isPrivate()) {
- return null;
- }
- return new NdefMessage(new NdefRecord[] { NdefRecord.createUri(tab.getURL()) });
- }
- }, this);
- }
- }
-
- if (savedInstanceState != null) {
- mDynamicToolbar.onRestoreInstanceState(savedInstanceState);
- mHomeScreenContainer.setPadding(0, savedInstanceState.getInt(STATE_ABOUT_HOME_TOP_PADDING), 0, 0);
- }
-
- mDynamicToolbar.setEnabledChangedListener(new DynamicToolbar.OnEnabledChangedListener() {
- @Override
- public void onEnabledChanged(boolean enabled) {
- setDynamicToolbarEnabled(enabled);
- }
- });
-
- // Set the maximum bits-per-pixel the favicon system cares about.
- IconDirectoryEntry.setMaxBPP(GeckoAppShell.getScreenDepth());
-
- // The update service is enabled for RELEASE_OR_BETA, which includes the release and beta channels.
- // However, no updates are served. Therefore, we don't trust the update service directly, and
- // try to avoid prompting unnecessarily. See Bug 1232798.
- if (!AppConstants.RELEASE_OR_BETA && UpdateServiceHelper.isUpdaterEnabled(this)) {
- Permissions.from(this)
- .withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
- .doNotPrompt()
- .andFallback(new Runnable() {
- @Override
- public void run() {
- showUpdaterPermissionSnackbar();
- }
- })
- .run();
- }
-
- for (final BrowserAppDelegate delegate : delegates) {
- delegate.onCreate(this, savedInstanceState);
- }
-
- // We want to get an understanding of how our user base is spread (bug 1221646).
- final String installerPackageName = getPackageManager().getInstallerPackageName(getPackageName());
- Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH, TelemetryContract.Method.SYSTEM, "installer_" + installerPackageName);
- }
-
- /**
- * Initializes the default Switchboard URLs the first time.
- * @param intent
- */
- private static void initSwitchboard(final Context context, final SafeIntent intent, final boolean isInAutomation) {
- if (isInAutomation) {
- Log.d(LOGTAG, "Switchboard disabled - in automation");
- return;
- } else if (!AppConstants.MOZ_SWITCHBOARD) {
- Log.d(LOGTAG, "Switchboard compile-time disabled");
- return;
- }
-
- final String serverExtra = intent.getStringExtra(INTENT_KEY_SWITCHBOARD_SERVER);
- final String serverUrl = TextUtils.isEmpty(serverExtra) ? SWITCHBOARD_SERVER : serverExtra;
- new AsyncConfigLoader(context, serverUrl).execute();
- }
-
- private static void initTelemetryUploader(final boolean isInAutomation) {
- TelemetryUploadService.setDisabled(isInAutomation);
- }
-
- private void showUpdaterPermissionSnackbar() {
- SnackbarBuilder.SnackbarCallback allowCallback = new SnackbarBuilder.SnackbarCallback() {
- @Override
- public void onClick(View v) {
- Permissions.from(BrowserApp.this)
- .withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
- .run();
- }
- };
-
- SnackbarBuilder.builder(this)
- .message(R.string.updater_permission_text)
- .duration(Snackbar.LENGTH_INDEFINITE)
- .action(R.string.updater_permission_allow)
- .callback(allowCallback)
- .buildAndShow();
- }
-
- private void conditionallyNotifyEOL() {
- final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
- try {
- final SharedPreferences prefs = GeckoSharedPrefs.forProfile(this);
- if (!prefs.contains(EOL_NOTIFIED)) {
-
- // Launch main App to load SUMO url on EOL notification.
- final String link = getString(R.string.eol_notification_url,
- AppConstants.MOZ_APP_VERSION,
- AppConstants.OS_TARGET,
- Locales.getLanguageTag(Locale.getDefault()));
-
- final Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
- intent.setData(Uri.parse(link));
- final PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
-
- final Notification notification = new NotificationCompat.Builder(this)
- .setContentTitle(getString(R.string.eol_notification_title))
- .setContentText(getString(R.string.eol_notification_summary))
- .setSmallIcon(R.drawable.ic_status_logo)
- .setAutoCancel(true)
- .setContentIntent(pendingIntent)
- .build();
-
- final NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- final int notificationID = EOL_NOTIFIED.hashCode();
- notificationManager.notify(notificationID, notification);
-
- GeckoSharedPrefs.forProfile(this)
- .edit()
- .putBoolean(EOL_NOTIFIED, true)
- .apply();
- }
- } finally {
- StrictMode.setThreadPolicy(savedPolicy);
- }
- }
-
- /**
- * Check and show the firstrun pane if the browser has never been launched and
- * is not opening an external link from another application.
- *
- * @param context Context of application; used to show firstrun pane if appropriate
- * @param intent Intent that launched this activity
- */
- private void checkFirstrun(Context context, SafeIntent intent) {
- if (getProfile().inGuestMode()) {
- // We do not want to show any first run tour for guest profiles.
- return;
- }
-
- if (intent.getBooleanExtra(EXTRA_SKIP_STARTPANE, false)) {
- // Note that we don't set the pref, so subsequent launches can result
- // in the firstrun pane being shown.
- return;
- }
- final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
-
- try {
- final SharedPreferences prefs = GeckoSharedPrefs.forProfile(this);
-
- if (prefs.getBoolean(FirstrunAnimationContainer.PREF_FIRSTRUN_ENABLED, true)) {
- if (!Intent.ACTION_VIEW.equals(intent.getAction())) {
- showFirstrunPager();
-
- if (HardwareUtils.isTablet()) {
- mTabStrip.setOnTabChangedListener(new TabStripInterface.OnTabAddedOrRemovedListener() {
- @Override
- public void onTabChanged() {
- hideFirstrunPager(TelemetryContract.Method.BUTTON);
- mTabStrip.setOnTabChangedListener(null);
- }
- });
- }
- }
-
- // Don't bother trying again to show the v1 minimal first run.
- prefs.edit().putBoolean(FirstrunAnimationContainer.PREF_FIRSTRUN_ENABLED, false).apply();
-
- // We have no intention of stopping this session. The FIRSTRUN session
- // ends when the browsing session/activity has ended. All events
- // during firstrun will be tagged as FIRSTRUN.
- Telemetry.startUISession(TelemetryContract.Session.FIRSTRUN);
- }
- } finally {
- StrictMode.setThreadPolicy(savedPolicy);
- }
- }
-
- private Class<?> getMediaPlayerManager() {
- if (AppConstants.MOZ_MEDIA_PLAYER) {
- try {
- return Class.forName("org.mozilla.gecko.MediaPlayerManager");
- } catch (Exception ex) {
- // Ignore failures
- Log.e(LOGTAG, "No native casting support", ex);
- }
- }
-
- return null;
- }
-
- @Override
- public void onBackPressed() {
- if (mTextSelection.dismiss()) {
- return;
- }
-
- if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
- super.onBackPressed();
- return;
- }
-
- if (mBrowserToolbar.onBackPressed()) {
- return;
- }
-
- if (mActionMode != null) {
- endActionModeCompat();
- return;
- }
-
- if (hideFirstrunPager(TelemetryContract.Method.BACK)) {
- return;
- }
-
- if (mVideoPlayer.isFullScreen()) {
- mVideoPlayer.setFullScreen(false);
- setFullScreen(false);
- return;
- }
-
- if (mVideoPlayer.isPlaying()) {
- mVideoPlayer.stop();
- return;
- }
-
- super.onBackPressed();
- }
-
- @Override
- public void onAttachedToWindow() {
- // We can't show the first run experience until Gecko has finished initialization (bug 1077583).
- checkFirstrun(this, new SafeIntent(getIntent()));
- }
-
- @Override
- protected void processTabQueue() {
- if (TabQueueHelper.TAB_QUEUE_ENABLED && mInitialized) {
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- if (TabQueueHelper.shouldOpenTabQueueUrls(BrowserApp.this)) {
- openQueuedTabs();
- }
- }
- });
- }
- }
-
- @Override
- protected void openQueuedTabs() {
- ThreadUtils.assertNotOnUiThread();
-
- int queuedTabCount = TabQueueHelper.getTabQueueLength(BrowserApp.this);
-
- Telemetry.addToHistogram("FENNEC_TABQUEUE_QUEUESIZE", queuedTabCount);
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "tabqueue-delayed");
-
- TabQueueHelper.openQueuedUrls(BrowserApp.this, getProfile(), TabQueueHelper.FILE_NAME, false);
-
- // If there's more than one tab then also show the tabs panel.
- if (queuedTabCount > 1) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- showNormalTabs();
- }
- });
- }
- }
-
- private void openMultipleTabsFromIntent(final SafeIntent intent) {
- final List<String> urls = intent.getStringArrayListExtra("urls");
- if (urls != null) {
- openUrls(urls);
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- if (mIsAbortingAppLaunch) {
- return;
- }
-
- if (!mHasResumed) {
- EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener) this,
- "Prompt:ShowTop");
- mHasResumed = true;
- }
-
- processTabQueue();
-
- for (BrowserAppDelegate delegate : delegates) {
- delegate.onResume(this);
- }
- }
-
- @Override
- public void onPause() {
- super.onPause();
- if (mIsAbortingAppLaunch) {
- return;
- }
-
- if (mHasResumed) {
- // Register for Prompt:ShowTop so we can foreground this activity even if it's hidden.
- EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener) this,
- "Prompt:ShowTop");
- mHasResumed = false;
- }
-
- for (BrowserAppDelegate delegate : delegates) {
- delegate.onPause(this);
- }
- }
-
- @Override
- public void onRestart() {
- super.onRestart();
- if (mIsAbortingAppLaunch) {
- return;
- }
-
- for (final BrowserAppDelegate delegate : delegates) {
- delegate.onRestart(this);
- }
- }
-
- @Override
- public void onStart() {
- super.onStart();
- if (mIsAbortingAppLaunch) {
- return;
- }
-
- // Queue this work so that the first launch of the activity doesn't
- // trigger profile init too early.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- final GeckoProfile profile = getProfile();
- if (profile.inGuestMode()) {
- GuestSession.showNotification(BrowserApp.this);
- } else {
- // If we're restarting, we won't destroy the activity.
- // Make sure we remove any guest notifications that might
- // have been shown.
- GuestSession.hideNotification(BrowserApp.this);
- }
-
- // It'd be better to launch this once, in onCreate, but there's ambiguity for when the
- // profile is created so we run here instead. Don't worry, call start short-circuits pretty fast.
- final SharedPreferences sharedPrefs = GeckoSharedPrefs.forProfileName(BrowserApp.this, profile.getName());
- FileCleanupController.startIfReady(BrowserApp.this, sharedPrefs, profile.getDir().getAbsolutePath());
- }
- });
-
- for (final BrowserAppDelegate delegate : delegates) {
- delegate.onStart(this);
- }
- }
-
- @Override
- public void onStop() {
- super.onStop();
- if (mIsAbortingAppLaunch) {
- return;
- }
-
- // We only show the guest mode notification when our activity is in the foreground.
- GuestSession.hideNotification(this);
-
- for (final BrowserAppDelegate delegate : delegates) {
- delegate.onStop(this);
- }
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
- super.onWindowFocusChanged(hasFocus);
-
- // Sending a message to the toolbar when the browser window gains focus
- // This is needed for qr code input
- if (hasFocus) {
- mBrowserToolbar.onParentFocus();
- }
- }
-
- private void setBrowserToolbarListeners() {
- mBrowserToolbar.setOnActivateListener(new BrowserToolbar.OnActivateListener() {
- @Override
- public void onActivate() {
- enterEditingMode();
- }
- });
-
- mBrowserToolbar.setOnCommitListener(new BrowserToolbar.OnCommitListener() {
- @Override
- public void onCommit() {
- commitEditingMode();
- }
- });
-
- mBrowserToolbar.setOnDismissListener(new BrowserToolbar.OnDismissListener() {
- @Override
- public void onDismiss() {
- mBrowserToolbar.cancelEdit();
- }
- });
-
- mBrowserToolbar.setOnFilterListener(new BrowserToolbar.OnFilterListener() {
- @Override
- public void onFilter(String searchText, AutocompleteHandler handler) {
- filterEditingMode(searchText, handler);
- }
- });
-
- mBrowserToolbar.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (isHomePagerVisible()) {
- mHomeScreen.onToolbarFocusChange(hasFocus);
- }
- }
- });
-
- mBrowserToolbar.setOnStartEditingListener(new BrowserToolbar.OnStartEditingListener() {
- @Override
- public void onStartEditing() {
- final Tab selectedTab = Tabs.getInstance().getSelectedTab();
- if (selectedTab != null) {
- selectedTab.setIsEditing(true);
- }
-
- // Temporarily disable doorhanger notifications.
- if (mDoorHangerPopup != null) {
- mDoorHangerPopup.disable();
- }
- }
- });
-
- mBrowserToolbar.setOnStopEditingListener(new BrowserToolbar.OnStopEditingListener() {
- @Override
- public void onStopEditing() {
- final Tab selectedTab = Tabs.getInstance().getSelectedTab();
- if (selectedTab != null) {
- selectedTab.setIsEditing(false);
- }
-
- selectTargetTabForEditingMode();
-
- // Since the underlying LayerView is set visible in hideHomePager, we would
- // ordinarily want to call it first. However, hideBrowserSearch changes the
- // visibility of the HomePager and hideHomePager will take no action if the
- // HomePager is hidden, so we want to call hideBrowserSearch to restore the
- // HomePager visibility first.
- hideBrowserSearch();
- hideHomePager();
-
- // Re-enable doorhanger notifications. They may trigger on the selected tab above.
- if (mDoorHangerPopup != null) {
- mDoorHangerPopup.enable();
- }
- }
- });
-
- // Intercept key events for gamepad shortcuts
- mBrowserToolbar.setOnKeyListener(this);
- }
-
- private void setDynamicToolbarEnabled(boolean enabled) {
- ThreadUtils.assertOnUiThread();
-
- if (enabled) {
- if (mLayerView != null) {
- mLayerView.getDynamicToolbarAnimator().addTranslationListener(this);
- }
- setToolbarMargin(0);
- mHomeScreenContainer.setPadding(0, mBrowserChrome.getHeight(), 0, 0);
- } else {
- // Immediately show the toolbar when disabling the dynamic
- // toolbar.
- if (mLayerView != null) {
- mLayerView.getDynamicToolbarAnimator().removeTranslationListener(this);
- }
- mHomeScreenContainer.setPadding(0, 0, 0, 0);
- if (mBrowserChrome != null) {
- ViewHelper.setTranslationY(mBrowserChrome, 0);
- }
- if (mLayerView != null) {
- mLayerView.setSurfaceTranslation(0);
- }
- }
-
- refreshToolbarHeight();
- }
-
- private static boolean isAboutHome(final Tab tab) {
- return AboutPages.isAboutHome(tab.getURL());
- }
-
- @Override
- public boolean onSearchRequested() {
- enterEditingMode();
- return true;
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- final int itemId = item.getItemId();
- if (itemId == R.id.pasteandgo) {
- hideFirstrunPager(TelemetryContract.Method.CONTEXT_MENU);
-
- String text = Clipboard.getText();
- if (!TextUtils.isEmpty(text)) {
- loadUrlOrKeywordSearch(text);
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.CONTEXT_MENU);
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "pasteandgo");
- }
- return true;
- }
-
- if (itemId == R.id.paste) {
- String text = Clipboard.getText();
- if (!TextUtils.isEmpty(text)) {
- enterEditingMode(text);
- showBrowserSearch();
- mBrowserSearch.filter(text, null);
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "paste");
- }
- return true;
- }
-
- if (itemId == R.id.subscribe) {
- // This can be selected from either the browser menu or the contextmenu, depending on the size and version (v11+) of the phone.
- Tab tab = Tabs.getInstance().getSelectedTab();
- if (tab != null && tab.hasFeeds()) {
- JSONObject args = new JSONObject();
- try {
- args.put("tabId", tab.getId());
- } catch (JSONException e) {
- Log.e(LOGTAG, "error building json arguments", e);
- }
- GeckoAppShell.notifyObservers("Feeds:Subscribe", args.toString());
- }
- return true;
- }
-
- if (itemId == R.id.add_search_engine) {
- // This can be selected from either the browser menu or the contextmenu, depending on the size and version (v11+) of the phone.
- Tab tab = Tabs.getInstance().getSelectedTab();
- if (tab != null && tab.hasOpenSearch()) {
- JSONObject args = new JSONObject();
- try {
- args.put("tabId", tab.getId());
- } catch (JSONException e) {
- Log.e(LOGTAG, "error building json arguments", e);
- return true;
- }
- GeckoAppShell.notifyObservers("SearchEngines:Add", args.toString());
- }
- return true;
- }
-
- if (itemId == R.id.copyurl) {
- Tab tab = Tabs.getInstance().getSelectedTab();
- if (tab != null) {
- String url = ReaderModeUtils.stripAboutReaderUrl(tab.getURL());
- if (url != null) {
- Clipboard.setText(url);
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, "copyurl");
- }
- }
- return true;
- }
-
- if (itemId == R.id.add_to_launcher) {
- final Tab tab = Tabs.getInstance().getSelectedTab();
- if (tab == null) {
- return true;
- }
-
- final String url = tab.getURL();
- final String title = tab.getDisplayTitle();
- if (url == null || title == null) {
- return true;
- }
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- GeckoAppShell.createShortcut(title, url);
-
- }
- });
-
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU,
- getResources().getResourceEntryName(itemId));
- return true;
- }
-
- return false;
- }
-
- @Override
- public void setAccessibilityEnabled(boolean enabled) {
- super.setAccessibilityEnabled(enabled);
- mDynamicToolbar.setAccessibilityEnabled(enabled);
- }
-
- @Override
- public void onDestroy() {
- if (mIsAbortingAppLaunch) {
- super.onDestroy();
- return;
- }
-
- mDynamicToolbar.destroy();
-
- if (mBrowserToolbar != null)
- mBrowserToolbar.onDestroy();
-
- if (mFindInPageBar != null) {
- mFindInPageBar.onDestroy();
- mFindInPageBar = null;
- }
-
- if (mMediaCastingBar != null) {
- mMediaCastingBar.onDestroy();
- mMediaCastingBar = null;
- }
-
- if (mSharedPreferencesHelper != null) {
- mSharedPreferencesHelper.uninit();
- mSharedPreferencesHelper = null;
- }
-
- if (mReadingListHelper != null) {
- mReadingListHelper.uninit();
- mReadingListHelper = null;
- }
-
- if (mAccountsHelper != null) {
- mAccountsHelper.uninit();
- mAccountsHelper = null;
- }
-
- if (mZoomedView != null) {
- mZoomedView.destroy();
- }
-
- mSearchEngineManager.unregisterListeners();
-
- EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener) this,
- "Gecko:DelayedStartup",
- "Menu:Open",
- "Menu:Update",
- "LightweightTheme:Update",
- "Search:Keyword",
- "Prompt:ShowTop",
- "Tab:Added",
- "Video:Play");
-
- EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener) this,
- "CharEncoding:Data",
- "CharEncoding:State",
- "Download:AndroidDownloadManager",
- "Experiments:GetActive",
- "Experiments:SetOverride",
- "Experiments:ClearOverride",
- "Favicon:CacheLoad",
- "Feedback:MaybeLater",
- "Menu:Add",
- "Menu:Remove",
- "Sanitize:ClearHistory",
- "Sanitize:ClearSyncedTabs",
- "Settings:Show",
- "Telemetry:Gather",
- "Updater:Launch",
- "Website:Metadata");
-
- if (AppConstants.MOZ_ANDROID_BEAM) {
- NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
- if (nfc != null) {
- // null this out even though the docs say it's not needed,
- // because the source code looks like it will only do this
- // automatically on API 14+
- nfc.setNdefPushMessageCallback(null, this);
- }
- }
-
- for (final BrowserAppDelegate delegate : delegates) {
- delegate.onDestroy(this);
- }
-
- deleteTempFiles();
-
- if (mDoorHangerPopup != null)
- mDoorHangerPopup.destroy();
- if (mFormAssistPopup != null)
- mFormAssistPopup.destroy();
- if (mTextSelection != null)
- mTextSelection.destroy();
- NotificationHelper.destroy();
- IntentHelper.destroy();
- GeckoNetworkManager.destroy();
-
- super.onDestroy();
-
- if (!isFinishing()) {
- // GeckoApp was not intentionally destroyed, so keep our process alive.
- return;
- }
-
- // Wait for Gecko to handle our pause event sent in onPause.
- if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
- GeckoThread.waitOnGecko();
- }
-
- if (mRestartIntent != null) {
- // Restarting, so let Restarter kill us.
- final Intent intent = new Intent();
- intent.setClass(getApplicationContext(), Restarter.class)
- .putExtra("pid", Process.myPid())
- .putExtra(Intent.EXTRA_INTENT, mRestartIntent);
- startService(intent);
- } else {
- // Exiting, so kill our own process.
- Process.killProcess(Process.myPid());
- }
- }
-
- @Override
- protected void initializeChrome() {
- super.initializeChrome();
-
- mDoorHangerPopup.setAnchor(mBrowserToolbar.getDoorHangerAnchor());
- mDoorHangerPopup.setOnVisibilityChangeListener(this);
-
- mDynamicToolbar.setLayerView(mLayerView);
- setDynamicToolbarEnabled(mDynamicToolbar.isEnabled());
-
- // Intercept key events for gamepad shortcuts
- mLayerView.setOnKeyListener(this);
-
- // Initialize the actionbar menu items on startup for both large and small tablets
- if (HardwareUtils.isTablet()) {
- onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, null);
- invalidateOptionsMenu();
- }
- }
-
- @Override
- public void onDoorHangerShow() {
- mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
-
- final Animator alphaAnimator = ObjectAnimator.ofFloat(mDoorhangerOverlay, "alpha", 1);
- alphaAnimator.setDuration(250);
-
- alphaAnimator.start();
- }
-
- @Override
- public void onDoorHangerHide() {
- final Animator alphaAnimator = ObjectAnimator.ofFloat(mDoorhangerOverlay, "alpha", 0);
- alphaAnimator.setDuration(200);
-
- alphaAnimator.start();
- }
-
- private void handleClearHistory(final boolean clearSearchHistory) {
- final BrowserDB db = BrowserDB.from(getProfile());
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- db.clearHistory(getContentResolver(), clearSearchHistory);
- }
- });
- }
-
- private void handleClearSyncedTabs() {
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- FennecTabsRepository.deleteNonLocalClientsAndTabs(getContext());
- }
- });
- }
-
- private void setToolbarMargin(int margin) {
- ((RelativeLayout.LayoutParams) mGeckoLayout.getLayoutParams()).topMargin = margin;
- mGeckoLayout.requestLayout();
- }
-
- @Override
- public void onTranslationChanged(float aToolbarTranslation, float aLayerViewTranslation) {
- if (mBrowserChrome == null) {
- return;
- }
-
- final View browserChrome = mBrowserChrome;
- final ToolbarProgressView progressView = mProgressView;
-
- ViewHelper.setTranslationY(browserChrome, -aToolbarTranslation);
- mLayerView.setSurfaceTranslation(mToolbarHeight - aLayerViewTranslation);
-
- // Stop the progressView from moving all the way up so that we can still see a good chunk of it
- // when the chrome is offscreen.
- final float offset = getResources().getDimensionPixelOffset(R.dimen.progress_bar_scroll_offset);
- final float progressTranslationY = Math.min(aToolbarTranslation, mToolbarHeight - offset);
- ViewHelper.setTranslationY(progressView, -progressTranslationY);
-
- if (mFormAssistPopup != null) {
- mFormAssistPopup.onTranslationChanged();
- }
- }
-
- @Override
- public void onMetricsChanged(ImmutableViewportMetrics aMetrics) {
- if (isHomePagerVisible() || mBrowserChrome == null) {
- return;
- }
-
- if (mFormAssistPopup != null) {
- mFormAssistPopup.onMetricsChanged(aMetrics);
- }
- }
-
- @Override
- public void onPanZoomStopped() {
- if (!mDynamicToolbar.isEnabled() || isHomePagerVisible() ||
- mBrowserChrome.getVisibility() != View.VISIBLE) {
- return;
- }
-
- // Make sure the toolbar is fully hidden or fully shown when the user
- // lifts their finger, depending on various conditions.
- ImmutableViewportMetrics metrics = mLayerView.getViewportMetrics();
- float toolbarTranslation = mLayerView.getDynamicToolbarAnimator().getToolbarTranslation();
-
- boolean shortPage = metrics.getPageHeight() < metrics.getHeight();
- boolean atBottomOfLongPage =
- FloatUtils.fuzzyEquals(metrics.pageRectBottom, metrics.viewportRectBottom())
- && (metrics.pageRectBottom > 2 * metrics.getHeight());
- Log.v(LOGTAG, "On pan/zoom stopped, short page: " + shortPage
- + "; atBottomOfLongPage: " + atBottomOfLongPage);
- if (shortPage || atBottomOfLongPage) {
- mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
- }
- }
-
- public void refreshToolbarHeight() {
- ThreadUtils.assertOnUiThread();
-
- int height = 0;
- if (mBrowserChrome != null) {
- height = mBrowserChrome.getHeight();
- }
-
- if (!mDynamicToolbar.isEnabled() || isHomePagerVisible()) {
- // Use aVisibleHeight here so that when the dynamic toolbar is
- // enabled, the padding will animate with the toolbar becoming
- // visible.
- if (mDynamicToolbar.isEnabled()) {
- // When the dynamic toolbar is enabled, set the padding on the
- // about:home widget directly - this is to avoid resizing the
- // LayerView, which can cause visible artifacts.
- mHomeScreenContainer.setPadding(0, height, 0, 0);
- } else {
- setToolbarMargin(height);
- height = 0;
- }
- } else {
- setToolbarMargin(0);
- }
-
- if (mLayerView != null && height != mToolbarHeight) {
- mToolbarHeight = height;
- mLayerView.setMaxTranslation(height);
- mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
- }
- }
-
- @Override
- void toggleChrome(final boolean aShow) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- if (aShow) {
- mBrowserChrome.setVisibility(View.VISIBLE);
- } else {
- mBrowserChrome.setVisibility(View.GONE);
- }
- }
- });
-
- super.toggleChrome(aShow);
- }
-
- @Override
- void focusChrome() {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- mBrowserChrome.setVisibility(View.VISIBLE);
- mActionBarFlipper.requestFocusFromTouch();
- }
- });
- }
-
- @Override
- public void refreshChrome() {
- invalidateOptionsMenu();
-
- if (mTabsPanel != null) {
- mTabsPanel.refresh();
- }
-
- if (mTabStrip != null) {
- mTabStrip.refresh();
- }
-
- mBrowserToolbar.refresh();
- }
-
- @Override
- public void handleMessage(final String event, final NativeJSObject message,
- final EventCallback callback) {
- switch (event) {
- case "CharEncoding:Data":
- final NativeJSObject[] charsets = message.getObjectArray("charsets");
- final int selected = message.getInt("selected");
-
- final String[] titleArray = new String[charsets.length];
- final String[] codeArray = new String[charsets.length];
- for (int i = 0; i < charsets.length; i++) {
- final NativeJSObject charset = charsets[i];
- titleArray[i] = charset.getString("title");
- codeArray[i] = charset.getString("code");
- }
-
- final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
- dialogBuilder.setSingleChoiceItems(titleArray, selected,
- new AlertDialog.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- GeckoAppShell.notifyObservers("CharEncoding:Set", codeArray[which]);
- dialog.dismiss();
- }
- });
- dialogBuilder.setNegativeButton(R.string.button_cancel,
- new AlertDialog.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- dialog.dismiss();
- }
- });
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- dialogBuilder.show();
- }
- });
- break;
-
- case "CharEncoding:State":
- final boolean visible = message.getString("visible").equals("true");
- GeckoPreferences.setCharEncodingState(visible);
- final Menu menu = mMenu;
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- if (menu != null) {
- menu.findItem(R.id.char_encoding).setVisible(visible);
- }
- }
- });
- break;
-
- case "Experiments:GetActive":
- final List<String> experiments = SwitchBoard.getActiveExperiments(this);
- final JSONArray json = new JSONArray(experiments);
- callback.sendSuccess(json.toString());
- break;
-
- case "Experiments:SetOverride":
- Experiments.setOverride(getContext(), message.getString("name"), message.getBoolean("isEnabled"));
- break;
-
- case "Experiments:ClearOverride":
- Experiments.clearOverride(getContext(), message.getString("name"));
- break;
-
- case "Favicon:CacheLoad":
- final String url = message.getString("url");
- getFaviconFromCache(callback, url);
- break;
-
- case "Feedback:MaybeLater":
- resetFeedbackLaunchCount();
- break;
-
- case "Menu:Add":
- final MenuItemInfo info = new MenuItemInfo();
- info.label = message.getString("name");
- info.id = message.getInt("id") + ADDON_MENU_OFFSET;
- info.checked = message.optBoolean("checked", false);
- info.enabled = message.optBoolean("enabled", true);
- info.visible = message.optBoolean("visible", true);
- info.checkable = message.optBoolean("checkable", false);
- final int parent = message.optInt("parent", 0);
- info.parent = parent <= 0 ? parent : parent + ADDON_MENU_OFFSET;
- final MenuItemInfo menuItemInfo = info;
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- addAddonMenuItem(menuItemInfo);
- }
- });
- break;
-
- case "Menu:Remove":
- final int id = message.getInt("id") + ADDON_MENU_OFFSET;
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- removeAddonMenuItem(id);
- }
- });
- break;
-
- case "Sanitize:ClearHistory":
- handleClearHistory(message.optBoolean("clearSearchHistory", false));
- callback.sendSuccess(true);
- break;
-
- case "Sanitize:ClearSyncedTabs":
- handleClearSyncedTabs();
- callback.sendSuccess(true);
- break;
-
- case "Settings:Show":
- final String resource =
- message.optString(GeckoPreferences.INTENT_EXTRA_RESOURCES, null);
- final Intent settingsIntent = new Intent(this, GeckoPreferences.class);
- GeckoPreferences.setResourceToOpen(settingsIntent, resource);
- startActivityForResult(settingsIntent, ACTIVITY_REQUEST_PREFERENCES);
-
- // Don't use a transition to settings if we're on a device where that
- // would look bad.
- if (HardwareUtils.IS_KINDLE_DEVICE) {
- overridePendingTransition(0, 0);
- }
- break;
-
- case "Telemetry:Gather":
- final BrowserDB db = BrowserDB.from(getProfile());
- final ContentResolver cr = getContentResolver();
- Telemetry.addToHistogram("PLACES_PAGES_COUNT", db.getCount(cr, "history"));
- Telemetry.addToHistogram("FENNEC_BOOKMARKS_COUNT", db.getCount(cr, "bookmarks"));
- Telemetry.addToHistogram("BROWSER_IS_USER_DEFAULT", (isDefaultBrowser(Intent.ACTION_VIEW) ? 1 : 0));
- Telemetry.addToHistogram("FENNEC_CUSTOM_HOMEPAGE", (TextUtils.isEmpty(getHomepage()) ? 0 : 1));
- final SharedPreferences prefs = GeckoSharedPrefs.forProfile(getContext());
- final boolean hasCustomHomepanels =
- prefs.contains(HomeConfigPrefsBackend.PREFS_CONFIG_KEY) || prefs.contains(HomeConfigPrefsBackend.PREFS_CONFIG_KEY_OLD);
- Telemetry.addToHistogram("FENNEC_HOMEPANELS_CUSTOM", hasCustomHomepanels ? 1 : 0);
-
- Telemetry.addToHistogram("FENNEC_READER_VIEW_CACHE_SIZE",
- SavedReaderViewHelper.getSavedReaderViewHelper(getContext()).getDiskSpacedUsedKB());
-
- if (Versions.feature16Plus) {
- Telemetry.addToHistogram("BROWSER_IS_ASSIST_DEFAULT", (isDefaultBrowser(Intent.ACTION_ASSIST) ? 1 : 0));
- }
-
- Telemetry.addToHistogram("FENNEC_ORBOT_INSTALLED",
- ContextUtils.isPackageInstalled(getContext(), "org.torproject.android") ? 1 : 0);
- break;
-
- case "Updater:Launch":
- handleUpdaterLaunch();
- break;
-
- case "Download:AndroidDownloadManager":
- // Downloading via Android's download manager
-
- final String uri = message.getString("uri");
- final String filename = message.getString("filename");
- final String mimeType = message.getString("mimeType");
-
- final DownloadManager.Request request = new DownloadManager.Request(Uri.parse(uri));
- request.setMimeType(mimeType);
-
- try {
- request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename);
- } catch (IllegalStateException e) {
- Log.e(LOGTAG, "Cannot create download directory");
- return;
- }
-
- request.allowScanningByMediaScanner();
- request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
- request.addRequestHeader("User-Agent", HardwareUtils.isTablet() ?
- AppConstants.USER_AGENT_FENNEC_TABLET :
- AppConstants.USER_AGENT_FENNEC_MOBILE);
-
- try {
- DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
- manager.enqueue(request);
-
- Log.d(LOGTAG, "Enqueued download (Download Manager)");
- } catch (RuntimeException e) {
- Log.e(LOGTAG, "Download failed: " + e);
- }
- break;
-
- case "Website:Metadata":
- final NativeJSObject metadata = message.getObject("metadata");
- final String location = message.getString("location");
-
- final boolean hasImage = !TextUtils.isEmpty(metadata.optString("image_url", null));
- final String metadataJSON = metadata.toString();
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- final ContentProviderClient contentProviderClient = getContentResolver()
- .acquireContentProviderClient(BrowserContract.PageMetadata.CONTENT_URI);
- if (contentProviderClient == null) {
- Log.w(LOGTAG, "Failed to obtain content provider client for: " + BrowserContract.PageMetadata.CONTENT_URI);
- return;
- }
- try {
- GlobalPageMetadata.getInstance().add(
- BrowserDB.from(getProfile()),
- contentProviderClient,
- location, hasImage, metadataJSON);
- } finally {
- contentProviderClient.release();
- }
- }
- });
-
- break;
-
- default:
- super.handleMessage(event, message, callback);
- break;
- }
- }
-
- private void getFaviconFromCache(final EventCallback callback, final String url) {
- Icons.with(this)
- .pageUrl(url)
- .skipNetwork()
- .executeCallbackOnBackgroundThread()
- .build()
- .execute(new IconCallback() {
- @Override
- public void onIconResponse(IconResponse response) {
- ByteArrayOutputStream out = null;
- Base64OutputStream b64 = null;
-
- try {
- out = new ByteArrayOutputStream();
- out.write("data:image/png;base64,".getBytes());
- b64 = new Base64OutputStream(out, Base64.NO_WRAP);
- response.getBitmap().compress(Bitmap.CompressFormat.PNG, 100, b64);
- callback.sendSuccess(new String(out.toByteArray()));
- } catch (IOException e) {
- Log.w(LOGTAG, "Failed to convert to base64 data URI");
- callback.sendError("Failed to convert favicon to a base64 data URI");
- } finally {
- try {
- if (out != null) {
- out.close();
- }
- if (b64 != null) {
- b64.close();
- }
- } catch (IOException e) {
- Log.w(LOGTAG, "Failed to close the streams");
- }
- }
- }
- });
- }
-
- /**
- * Use a dummy Intent to do a default browser check.
- *
- * @return true if this package is the default browser on this device, false otherwise.
- */
- private boolean isDefaultBrowser(String action) {
- final Intent viewIntent = new Intent(action, Uri.parse("http://www.mozilla.org"));
- final ResolveInfo info = getPackageManager().resolveActivity(viewIntent, PackageManager.MATCH_DEFAULT_ONLY);
- if (info == null) {
- // No default is set
- return false;
- }
-
- final String packageName = info.activityInfo.packageName;
- return (TextUtils.equals(packageName, getPackageName()));
- }
-
- @Override
- public void handleMessage(String event, JSONObject message) {
- try {
- switch (event) {
- case "Menu:Open":
- if (mBrowserToolbar.isEditing()) {
- mBrowserToolbar.cancelEdit();
- }
-
- openOptionsMenu();
- break;
-
- case "Menu:Update":
- final int id = message.getInt("id") + ADDON_MENU_OFFSET;
- final JSONObject options = message.getJSONObject("options");
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- updateAddonMenuItem(id, options);
- }
- });
- break;
-
- case "Gecko:DelayedStartup":
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- // Force tabs panel inflation once the initial
- // pageload is finished.
- ensureTabsPanelExists();
- if (AppConstants.NIGHTLY_BUILD && mZoomedView == null) {
- ViewStub stub = (ViewStub) findViewById(R.id.zoomed_view_stub);
- mZoomedView = (ZoomedView) stub.inflate();
- }
- }
- });
-
- if (AppConstants.MOZ_MEDIA_PLAYER) {
- // Check if the fragment is already added. This should never be true here, but this is
- // a nice safety check.
- // If casting is disabled, these classes aren't built. We use reflection to initialize them.
- final Class<?> mediaManagerClass = getMediaPlayerManager();
-
- if (mediaManagerClass != null) {
- try {
- final String tag = "";
- mediaManagerClass.getDeclaredField("MEDIA_PLAYER_TAG").get(tag);
- Log.i(LOGTAG, "Found tag " + tag);
- final Fragment frag = getSupportFragmentManager().findFragmentByTag(tag);
- if (frag == null) {
- final Method getInstance = mediaManagerClass.getMethod("getInstance", (Class[]) null);
- final Fragment mpm = (Fragment) getInstance.invoke(null);
- getSupportFragmentManager().beginTransaction().disallowAddToBackStack().add(mpm, tag).commit();
- }
- } catch (Exception ex) {
- Log.e(LOGTAG, "Error initializing media manager", ex);
- }
- }
- }
-
- if (AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED && Restrictions.isAllowed(this, Restrictable.DATA_CHOICES)) {
- // Start (this acts as ping if started already) the stumbler lib; if the stumbler has queued data it will upload it.
- // Stumbler operates on its own thread, and startup impact is further minimized by delaying work (such as upload) a few seconds.
- // Avoid any potential startup CPU/thread contention by delaying the pref broadcast.
- final long oneSecondInMillis = 1000;
- ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() {
- @Override
- public void run() {
- GeckoPreferences.broadcastStumblerPref(BrowserApp.this);
- }
- }, oneSecondInMillis);
- }
-
- if (AppConstants.MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE) {
- // TODO: Better scheduling of sync action (Bug 1257492)
- DownloadContentService.startSync(this);
-
- DownloadContentService.startVerification(this);
- }
-
- FeedService.setup(this);
-
- super.handleMessage(event, message);
- break;
-
- case "Gecko:Ready":
- // Handle this message in GeckoApp, but also enable the Settings
- // menuitem, which is specific to BrowserApp.
- super.handleMessage(event, message);
- final Menu menu = mMenu;
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- if (menu != null) {
- menu.findItem(R.id.settings).setEnabled(true);
- menu.findItem(R.id.help).setEnabled(true);
- }
- }
- });
-
- // Display notification for Mozilla data reporting, if data should be collected.
- if (AppConstants.MOZ_DATA_REPORTING && Restrictions.isAllowed(this, Restrictable.DATA_CHOICES)) {
- DataReportingNotification.checkAndNotifyPolicy(GeckoAppShell.getContext());
- }
- break;
-
- case "Search:Keyword":
- storeSearchQuery(message.getString("query"));
- recordSearch(GeckoSharedPrefs.forProfile(this), message.getString("identifier"),
- TelemetryContract.Method.ACTIONBAR);
- break;
-
- case "LightweightTheme:Update":
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
- }
- });
- break;
-
- case "Video:Play":
- if (SwitchBoard.isInExperiment(this, Experiments.HLS_VIDEO_PLAYBACK)) {
- final String uri = message.getString("uri");
- final String uuid = message.getString("uuid");
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- mVideoPlayer.start(Uri.parse(uri));
- Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.CONTENT, "playhls");
- }
- });
- }
- break;
-
- case "Prompt:ShowTop":
- // Bring this activity to front so the prompt is visible..
- Intent bringToFrontIntent = new Intent();
- bringToFrontIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
- bringToFrontIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
- startActivity(bringToFrontIntent);
- break;
-
- case "Tab:Added":
- if (message.getBoolean("cancelEditMode")) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- // Set the target tab to null so it does not get selected (on editing
- // mode exit) in lieu of the tab that we're going to open and select.
- mTargetTabForEditingMode = null;
- mBrowserToolbar.cancelEdit();
- }
- });
- }
- break;
-
- default:
- super.handleMessage(event, message);
- break;
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
- }
- }
-
- @Override
- public void addTab() {
- Tabs.getInstance().addTab();
- }
-
- @Override
- public void addPrivateTab() {
- Tabs.getInstance().addPrivateTab();
- }
-
- public void showTrackingProtectionPromptIfApplicable() {
- final SharedPreferences prefs = getSharedPreferences();
-
- final boolean hasTrackingProtectionPromptBeShownBefore = prefs.getBoolean(GeckoPreferences.PREFS_TRACKING_PROTECTION_PROMPT_SHOWN, false);
-
- if (hasTrackingProtectionPromptBeShownBefore) {
- return;
- }
-
- prefs.edit().putBoolean(GeckoPreferences.PREFS_TRACKING_PROTECTION_PROMPT_SHOWN, true).apply();
-
- startActivity(new Intent(BrowserApp.this, TrackingProtectionPrompt.class));
- }
-
- @Override
- public void showNormalTabs() {
- showTabs(TabsPanel.Panel.NORMAL_TABS);
- }
-
- @Override
- public void showPrivateTabs() {
- showTabs(TabsPanel.Panel.PRIVATE_TABS);
- }
- /**
- * Ensure the TabsPanel view is properly inflated and returns
- * true when the view has been inflated, false otherwise.
- */
- private boolean ensureTabsPanelExists() {
- if (mTabsPanel != null) {
- return false;
- }
-
- ViewStub tabsPanelStub = (ViewStub) findViewById(R.id.tabs_panel);
- mTabsPanel = (TabsPanel) tabsPanelStub.inflate();
-
- mTabsPanel.setTabsLayoutChangeListener(this);
-
- return true;
- }
-
- private void showTabs(final TabsPanel.Panel panel) {
- if (Tabs.getInstance().getDisplayCount() == 0)
- return;
-
- hideFirstrunPager(TelemetryContract.Method.BUTTON);
-
- if (ensureTabsPanelExists()) {
- // If we've just inflated the tabs panel, only show it once the current
- // layout pass is done to avoid displayed temporary UI states during
- // relayout.
- ViewTreeObserver vto = mTabsPanel.getViewTreeObserver();
- if (vto.isAlive()) {
- vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- mTabsPanel.getViewTreeObserver().removeGlobalOnLayoutListener(this);
- showTabs(panel);
- }
- });
- }
- } else {
- if (mDoorHangerPopup != null) {
- mDoorHangerPopup.disable();
- }
- mTabsPanel.show(panel);
-
- // Hide potentially visible "find in page" bar (Bug 1177338)
- mFindInPageBar.hide();
-
- for (final BrowserAppDelegate delegate : delegates) {
- delegate.onTabsTrayShown(this, mTabsPanel);
- }
- }
- }
-
- @Override
- public void hideTabs() {
- mTabsPanel.hide();
- if (mDoorHangerPopup != null) {
- mDoorHangerPopup.enable();
- }
-
- for (final BrowserAppDelegate delegate : delegates) {
- delegate.onTabsTrayHidden(this, mTabsPanel);
- }
- }
-
- @Override
- public boolean autoHideTabs() {
- if (areTabsShown()) {
- hideTabs();
- return true;
- }
- return false;
- }
-
- @Override
- public boolean areTabsShown() {
- return (mTabsPanel != null && mTabsPanel.isShown());
- }
-
- @Override
- public String getHomepage() {
- final SharedPreferences preferences = GeckoSharedPrefs.forProfile(this);
- final String homepagePreference = preferences.getString(GeckoPreferences.PREFS_HOMEPAGE, null);
-
- final boolean readFromPartnerProvider = preferences.getBoolean(
- GeckoPreferences.PREFS_READ_PARTNER_CUSTOMIZATIONS_PROVIDER, false);
-
- if (!readFromPartnerProvider) {
- // Just return homepage as set by the user (or null).
- return homepagePreference;
- }
-
-
- final String homepagePrevious = preferences.getString(GeckoPreferences.PREFS_HOMEPAGE_PARTNER_COPY, null);
- if (homepagePrevious != null && !homepagePrevious.equals(homepagePreference)) {
- // We have read the homepage once and the user has changed it since then. Just use the
- // value the user has set.
- return homepagePreference;
- }
-
- // This is the first time we read the partner provider or the value has not been altered by the user
- final String homepagePartner = PartnerBrowserCustomizationsClient.getHomepage(this);
-
- if (homepagePartner == null) {
- // We didn't get anything from the provider. Let's just use what we have locally.
- return homepagePreference;
- }
-
- if (!homepagePartner.equals(homepagePrevious)) {
- // We have a new value. Update the preferences.
- preferences.edit()
- .putString(GeckoPreferences.PREFS_HOMEPAGE, homepagePartner)
- .putString(GeckoPreferences.PREFS_HOMEPAGE_PARTNER_COPY, homepagePartner)
- .apply();
- }
-
- return homepagePartner;
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
- @Override
- public void onTabsLayoutChange(int width, int height) {
- int animationLength = TABS_ANIMATION_DURATION;
-
- if (mMainLayoutAnimator != null) {
- animationLength = Math.max(1, animationLength - (int)mMainLayoutAnimator.getRemainingTime());
- mMainLayoutAnimator.stop(false);
- }
-
- if (areTabsShown()) {
- mTabsPanel.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
- // Hide the web content from accessibility tools even though it's visible
- // so that you can't examine it as long as the tabs are being shown.
- if (Versions.feature16Plus) {
- mLayerView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
- }
- } else {
- if (Versions.feature16Plus) {
- mLayerView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- }
- }
-
- mMainLayoutAnimator = new PropertyAnimator(animationLength, sTabsInterpolator);
- mMainLayoutAnimator.addPropertyAnimationListener(this);
- mMainLayoutAnimator.attach(mMainLayout,
- PropertyAnimator.Property.SCROLL_Y,
- -height);
-
- mTabsPanel.prepareTabsAnimation(mMainLayoutAnimator);
- mBrowserToolbar.triggerTabsPanelTransition(mMainLayoutAnimator, areTabsShown());
-
- // If the tabs panel is animating onto the screen, pin the dynamic
- // toolbar.
- if (mDynamicToolbar.isEnabled()) {
- if (width > 0 && height > 0) {
- mDynamicToolbar.setPinned(true, PinReason.RELAYOUT);
- mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
- } else {
- mDynamicToolbar.setPinned(false, PinReason.RELAYOUT);
- }
- }
-
- mMainLayoutAnimator.start();
- }
-
- @Override
- public void onPropertyAnimationStart() {
- }
-
- @Override
- public void onPropertyAnimationEnd() {
- if (!areTabsShown()) {
- mTabsPanel.setVisibility(View.INVISIBLE);
- mTabsPanel.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
- } else {
- // Cancel editing mode to return to page content when the TabsPanel closes. We cancel
- // it here because there are graphical glitches if it's canceled while it's visible.
- mBrowserToolbar.cancelEdit();
- }
-
- mTabsPanel.finishTabsAnimation();
-
- mMainLayoutAnimator = null;
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- mDynamicToolbar.onSaveInstanceState(outState);
- outState.putInt(STATE_ABOUT_HOME_TOP_PADDING, mHomeScreenContainer.getPaddingTop());
- }
-
- /**
- * Attempts to switch to an open tab with the given URL.
- * <p>
- * If the tab exists, this method cancels any in-progress editing as well as
- * calling {@link Tabs#selectTab(int)}.
- *
- * @param url of tab to switch to.
- * @param flags to obey: if {@link OnUrlOpenListener.Flags#ALLOW_SWITCH_TO_TAB}
- * is not present, return false.
- * @return true if we successfully switched to a tab, false otherwise.
- */
- private boolean maybeSwitchToTab(String url, EnumSet<OnUrlOpenListener.Flags> flags) {
- if (!flags.contains(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB)) {
- return false;
- }
-
- final Tabs tabs = Tabs.getInstance();
- final Tab tab;
-
- if (AboutPages.isAboutReader(url)) {
- tab = tabs.getFirstReaderTabForUrl(url, tabs.getSelectedTab().isPrivate());
- } else {
- tab = tabs.getFirstTabForUrl(url, tabs.getSelectedTab().isPrivate());
- }
-
- if (tab == null) {
- return false;
- }
-
- return maybeSwitchToTab(tab.getId());
- }
-
- /**
- * Attempts to switch to an open tab with the given unique tab ID.
- * <p>
- * If the tab exists, this method cancels any in-progress editing as well as
- * calling {@link Tabs#selectTab(int)}.
- *
- * @param id of tab to switch to.
- * @return true if we successfully switched to the tab, false otherwise.
- */
- private boolean maybeSwitchToTab(int id) {
- final Tabs tabs = Tabs.getInstance();
- final Tab tab = tabs.getTab(id);
-
- if (tab == null) {
- return false;
- }
-
- final Tab oldTab = tabs.getSelectedTab();
- if (oldTab != null) {
- oldTab.setIsEditing(false);
- }
-
- // Set the target tab to null so it does not get selected (on editing
- // mode exit) in lieu of the tab we are about to select.
- mTargetTabForEditingMode = null;
- tabs.selectTab(tab.getId());
-
- mBrowserToolbar.cancelEdit();
-
- return true;
- }
-
- public void openUrlAndStopEditing(String url) {
- openUrlAndStopEditing(url, null, false);
- }
-
- private void openUrlAndStopEditing(String url, boolean newTab) {
- openUrlAndStopEditing(url, null, newTab);
- }
-
- private void openUrlAndStopEditing(String url, String searchEngine) {
- openUrlAndStopEditing(url, searchEngine, false);
- }
-
- private void openUrlAndStopEditing(String url, String searchEngine, boolean newTab) {
- int flags = Tabs.LOADURL_NONE;
- if (newTab) {
- flags |= Tabs.LOADURL_NEW_TAB;
- if (Tabs.getInstance().getSelectedTab().isPrivate()) {
- flags |= Tabs.LOADURL_PRIVATE;
- }
- }
-
- Tabs.getInstance().loadUrl(url, searchEngine, -1, flags);
-
- mBrowserToolbar.cancelEdit();
- }
-
- private boolean isHomePagerVisible() {
- return (mHomeScreen != null && mHomeScreen.isVisible()
- && mHomeScreenContainer != null && mHomeScreenContainer.getVisibility() == View.VISIBLE);
- }
-
- private boolean isFirstrunVisible() {
- return (mFirstrunAnimationContainer != null && mFirstrunAnimationContainer.isVisible()
- && mHomeScreenContainer != null && mHomeScreenContainer.getVisibility() == View.VISIBLE);
- }
-
- /**
- * Enters editing mode with the current tab's URL. There might be no
- * tabs loaded by the time the user enters editing mode e.g. just after
- * the app starts. In this case, we simply fallback to an empty URL.
- */
- private void enterEditingMode() {
- String url = "";
- String telemetryMsg = "urlbar-empty";
-
- final Tab tab = Tabs.getInstance().getSelectedTab();
- if (tab != null) {
- final String userSearchTerm = tab.getUserRequested();
- final String tabURL = tab.getURL();
-
- // Check to see if there's a user-entered search term,
- // which we save whenever the user performs a search.
- if (!TextUtils.isEmpty(userSearchTerm)) {
- url = userSearchTerm;
- telemetryMsg = "urlbar-userentered";
- } else if (!TextUtils.isEmpty(tabURL)) {
- url = tabURL;
- telemetryMsg = "urlbar-url";
- }
- }
-
- enterEditingMode(url);
- Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.ACTIONBAR, telemetryMsg);
- }
-
- /**
- * Enters editing mode with the specified URL. If a null
- * url is given, the empty String will be used instead.
- */
- private void enterEditingMode(@NonNull String url) {
- hideFirstrunPager(TelemetryContract.Method.ACTIONBAR);
-
- if (mBrowserToolbar.isEditing() || mBrowserToolbar.isAnimating()) {
- return;
- }
-
- final Tab selectedTab = Tabs.getInstance().getSelectedTab();
- final String panelId;
- if (selectedTab != null) {
- mTargetTabForEditingMode = selectedTab.getId();
- panelId = selectedTab.getMostRecentHomePanel();
- } else {
- mTargetTabForEditingMode = null;
- panelId = null;
- }
-
- final PropertyAnimator animator = new PropertyAnimator(250);
- animator.setUseHardwareLayer(false);
-
- mBrowserToolbar.startEditing(url, animator);
-
- showHomePagerWithAnimator(panelId, null, animator);
-
- animator.start();
- Telemetry.startUISession(TelemetryContract.Session.AWESOMESCREEN);
- }
-
- private void commitEditingMode() {
- if (!mBrowserToolbar.isEditing()) {
- return;
- }
-
- Telemetry.stopUISession(TelemetryContract.Session.AWESOMESCREEN,
- TelemetryContract.Reason.COMMIT);
-
- final String url = mBrowserToolbar.commitEdit();
-
- // HACK: We don't know the url that will be loaded when hideHomePager is initially called
- // in BrowserToolbar's onStopEditing listener so on the awesomescreen, hideHomePager will
- // use the url "about:home" and return without taking any action. hideBrowserSearch is
- // then called, but since hideHomePager changes both HomePager and LayerView visibility
- // and exited without taking an action, no Views are displayed and graphical corruption is
- // visible instead.
- //
- // Here we call hideHomePager for the second time with the URL to be loaded so that
- // hideHomePager is called with the correct state for the upcoming page load.
- //
- // Expected to be fixed by bug 915825.
- hideHomePager(url);
- loadUrlOrKeywordSearch(url);
- clearSelectedTabApplicationId();
- }
-
- private void clearSelectedTabApplicationId() {
- final Tab selected = Tabs.getInstance().getSelectedTab();
- if (selected != null) {
- selected.setApplicationId(null);
- }
- }
-
- private void loadUrlOrKeywordSearch(final String url) {
- // Don't do anything if the user entered an empty URL.
- if (TextUtils.isEmpty(url)) {
- return;
- }
-
- // If the URL doesn't look like a search query, just load it.
- if (!StringUtils.isSearchQuery(url, true)) {
- Tabs.getInstance().loadUrl(url, Tabs.LOADURL_USER_ENTERED);
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.ACTIONBAR, "user");
- return;
- }
-
- // Otherwise, check for a bookmark keyword.
- final SharedPreferences sharedPrefs = GeckoSharedPrefs.forProfile(this);
- final BrowserDB db = BrowserDB.from(getProfile());
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- final String keyword;
- final String keywordSearch;
-
- final int index = url.indexOf(" ");
- if (index == -1) {
- keyword = url;
- keywordSearch = "";
- } else {
- keyword = url.substring(0, index);
- keywordSearch = url.substring(index + 1);
- }
-
- final String keywordUrl = db.getUrlForKeyword(getContentResolver(), keyword);
-
- // If there isn't a bookmark keyword, load the url. This may result in a query
- // using the default search engine.
- if (TextUtils.isEmpty(keywordUrl)) {
- Tabs.getInstance().loadUrl(url, Tabs.LOADURL_USER_ENTERED);
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.ACTIONBAR, "user");
- return;
- }
-
- // Otherwise, construct a search query from the bookmark keyword.
- // Replace lower case bookmark keywords with URLencoded search query or
- // replace upper case bookmark keywords with un-encoded search query.
- // This makes it match the same behaviour as on Firefox for the desktop.
- final String searchUrl = keywordUrl.replace("%s", URLEncoder.encode(keywordSearch)).replace("%S", keywordSearch);
-
- Tabs.getInstance().loadUrl(searchUrl, Tabs.LOADURL_USER_ENTERED);
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL,
- TelemetryContract.Method.ACTIONBAR,
- "keyword");
- }
- });
- }
-
- /**
- * Records in telemetry that a search has occurred.
- *
- * @param where where the search was started from
- */
- private static void recordSearch(@NonNull final SharedPreferences prefs, @NonNull final String engineIdentifier,
- @NonNull final TelemetryContract.Method where) {
- // We could include the engine identifier as an extra but we'll
- // just capture that with core ping telemetry (bug 1253319).
- Telemetry.sendUIEvent(TelemetryContract.Event.SEARCH, where);
- SearchCountMeasurements.incrementSearch(prefs, engineIdentifier, where.toString());
- }
-
- /**
- * Store search query in SearchHistoryProvider.
- *
- * @param query
- * a search query to store. We won't store empty queries.
- */
- private void storeSearchQuery(final String query) {
- if (TextUtils.isEmpty(query)) {
- return;
- }
-
- // Filter out URLs and long suggestions
- if (query.length() > 50 || Pattern.matches("^(https?|ftp|file)://.*", query)) {
- return;
- }
-
- final GeckoProfile profile = getProfile();
- // Don't bother storing search queries in guest mode
- if (profile.inGuestMode()) {
- return;
- }
-
- final BrowserDB db = BrowserDB.from(profile);
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- db.getSearches().insert(getContentResolver(), query);
- }
- });
- }
-
- void filterEditingMode(String searchTerm, AutocompleteHandler handler) {
- if (TextUtils.isEmpty(searchTerm)) {
- hideBrowserSearch();
- } else {
- showBrowserSearch();
- mBrowserSearch.filter(searchTerm, handler);
- }
- }
-
- /**
- * Selects the target tab for editing mode. This is expected to be the tab selected on editing
- * mode entry, unless it is subsequently overridden.
- *
- * A background tab may be selected while editing mode is active (e.g. popups), causing the
- * new url to load in the newly selected tab. Call this method on editing mode exit to
- * mitigate this.
- *
- * Note that this method is disabled for new tablets because we can see the selected tab in the
- * tab strip and, when the selected tab changes during editing mode as in this hack, the
- * temporarily selected tab is visible to users.
- */
- private void selectTargetTabForEditingMode() {
- if (HardwareUtils.isTablet()) {
- return;
- }
-
- if (mTargetTabForEditingMode != null) {
- Tabs.getInstance().selectTab(mTargetTabForEditingMode);
- }
-
- mTargetTabForEditingMode = null;
- }
-
- /**
- * Shows or hides the home pager for the given tab.
- */
- private void updateHomePagerForTab(Tab tab) {
- // Don't change the visibility of the home pager if we're in editing mode.
- if (mBrowserToolbar.isEditing()) {
- return;
- }
-
- // History will only store that we were visiting about:home, however the specific panel
- // isn't stored. (We are able to navigate directly to homepanels using an about:home?panel=...
- // URL, but the reverse doesn't apply: manually switching panels doesn't update the URL.)
- // Hence we need to restore the panel, in addition to panel state, here.
- if (isAboutHome(tab)) {
- String panelId = AboutPages.getPanelIdFromAboutHomeUrl(tab.getURL());
- Bundle panelRestoreData = null;
- if (panelId == null) {
- // No panel was specified in the URL. Try loading the most recent
- // home panel for this tab.
- // Note: this isn't necessarily correct. We don't update the URL when we switch tabs.
- // If a user explicitly navigated to about:reader?panel=FOO, and then switches
- // to panel BAR, the history URL still contains FOO, and we restore to FOO. In most
- // cases however we aren't supplying a panel ID in the URL so this code still works
- // for most cases.
- // We can't fix this directly since we can't ignore the panelId if we're explicitly
- // loading a specific panel, and we currently can't distinguish between loading
- // history, and loading new pages, see Bug 1268887
- panelId = tab.getMostRecentHomePanel();
- panelRestoreData = tab.getMostRecentHomePanelData();
- } else if (panelId.equals(HomeConfig.getIdForBuiltinPanelType(PanelType.DEPRECATED_RECENT_TABS))) {
- // Redirect to the Combined History panel.
- panelId = HomeConfig.getIdForBuiltinPanelType(PanelType.COMBINED_HISTORY);
- panelRestoreData = new Bundle();
- // Jump directly to the Recent Tabs subview of the Combined History panel.
- panelRestoreData.putBoolean("goToRecentTabs", true);
- }
- showHomePager(panelId, panelRestoreData);
-
- if (mDynamicToolbar.isEnabled()) {
- mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
- }
- } else {
- hideHomePager();
- }
- }
-
- @Override
- public void onLocaleReady(final String locale) {
- Log.d(LOGTAG, "onLocaleReady: " + locale);
- super.onLocaleReady(locale);
-
- HomePanelsManager.getInstance().onLocaleReady(locale);
-
- if (mMenu != null) {
- mMenu.clear();
- onCreateOptionsMenu(mMenu);
- }
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- Log.d(LOGTAG, "onActivityResult: " + requestCode + ", " + resultCode + ", " + data);
- switch (requestCode) {
- case ACTIVITY_REQUEST_PREFERENCES:
- // We just returned from preferences. If our locale changed,
- // we need to redisplay at this point, and do any other browser-level
- // bookkeeping that we associate with a locale change.
- if (resultCode != GeckoPreferences.RESULT_CODE_LOCALE_DID_CHANGE) {
- Log.d(LOGTAG, "No locale change returning from preferences; nothing to do.");
- return;
- }
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- final LocaleManager localeManager = BrowserLocaleManager.getInstance();
- final Locale locale = localeManager.getCurrentLocale(getApplicationContext());
- Log.d(LOGTAG, "Read persisted locale " + locale);
- if (locale == null) {
- return;
- }
- onLocaleChanged(Locales.getLanguageTag(locale));
- }
- });
- break;
-
- case ACTIVITY_REQUEST_TAB_QUEUE:
- TabQueueHelper.processTabQueuePromptResponse(resultCode, this);
- break;
-
- default:
- for (final BrowserAppDelegate delegate : delegates) {
- delegate.onActivityResult(this, requestCode, resultCode, data);
- }
-
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
-
- private void showFirstrunPager() {
- if (Experiments.isInExperimentLocal(getContext(), Experiments.ONBOARDING3_A)) {
- Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING3_A);
- GeckoSharedPrefs.forProfile(getContext()).edit().putString(Experiments.PREF_ONBOARDING_VERSION, Experiments.ONBOARDING3_A).apply();
- Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING3_A);
- return;
- }
-
- if (mFirstrunAnimationContainer == null) {
- final ViewStub firstrunPagerStub = (ViewStub) findViewById(R.id.firstrun_pager_stub);
- mFirstrunAnimationContainer = (FirstrunAnimationContainer) firstrunPagerStub.inflate();
- mFirstrunAnimationContainer.load(getApplicationContext(), getSupportFragmentManager());
- mFirstrunAnimationContainer.registerOnFinishListener(new FirstrunAnimationContainer.OnFinishListener() {
- @Override
- public void onFinish() {
- if (mFirstrunAnimationContainer.showBrowserHint() &&
- TextUtils.isEmpty(getHomepage())) {
- enterEditingMode();
- }
- }
- });
- }
-
- mHomeScreenContainer.setVisibility(View.VISIBLE);
- }
-
- private void showHomePager(String panelId, Bundle panelRestoreData) {
- showHomePagerWithAnimator(panelId, panelRestoreData, null);
- }
-
- private void showHomePagerWithAnimator(String panelId, Bundle panelRestoreData, PropertyAnimator animator) {
- if (isHomePagerVisible()) {
- // Home pager already visible, make sure it shows the correct panel.
- mHomeScreen.showPanel(panelId, panelRestoreData);
- return;
- }
-
- // This must be called before the dynamic toolbar is set visible because it calls
- // FormAssistPopup.onMetricsChanged, which queues a runnable that undoes the effect of hide.
- // With hide first, onMetricsChanged will return early instead.
- mFormAssistPopup.hide();
- mFindInPageBar.hide();
-
- // Refresh toolbar height to possibly restore the toolbar padding
- refreshToolbarHeight();
-
- // Show the toolbar before hiding about:home so the
- // onMetricsChanged callback still works.
- if (mDynamicToolbar.isEnabled()) {
- mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
- }
-
- if (mHomeScreen == null) {
- if (ActivityStream.isEnabled(this) &&
- !ActivityStream.isHomePanel()) {
- final ViewStub asStub = (ViewStub) findViewById(R.id.activity_stream_stub);
- mHomeScreen = (HomeScreen) asStub.inflate();
- } else {
- final ViewStub homePagerStub = (ViewStub) findViewById(R.id.home_pager_stub);
- mHomeScreen = (HomeScreen) homePagerStub.inflate();
-
- // For now these listeners are HomePager specific. In future we might want
- // to have a more abstracted data storage, with one Bundle containing all
- // relevant restore data.
- mHomeScreen.setOnPanelChangeListener(new HomeScreen.OnPanelChangeListener() {
- @Override
- public void onPanelSelected(String panelId) {
- final Tab currentTab = Tabs.getInstance().getSelectedTab();
- if (currentTab != null) {
- currentTab.setMostRecentHomePanel(panelId);
- }
- }
- });
-
- // Set this listener to persist restore data (via the Tab) every time panel state changes.
- mHomeScreen.setPanelStateChangeListener(new HomeFragment.PanelStateChangeListener() {
- @Override
- public void onStateChanged(Bundle bundle) {
- final Tab currentTab = Tabs.getInstance().getSelectedTab();
- if (currentTab != null) {
- currentTab.setMostRecentHomePanelData(bundle);
- }
- }
-
- @Override
- public void setCachedRecentTabsCount(int count) {
- mCachedRecentTabsCount = count;
- }
-
- @Override
- public int getCachedRecentTabsCount() {
- return mCachedRecentTabsCount;
- }
- });
- }
-
- // Don't show the banner in guest mode.
- if (!Restrictions.isUserRestricted()) {
- final ViewStub homeBannerStub = (ViewStub) findViewById(R.id.home_banner_stub);
- final HomeBanner homeBanner = (HomeBanner) homeBannerStub.inflate();
- mHomeScreen.setBanner(homeBanner);
-
- // Remove the banner from the view hierarchy if it is dismissed.
- homeBanner.setOnDismissListener(new HomeBanner.OnDismissListener() {
- @Override
- public void onDismiss() {
- mHomeScreen.setBanner(null);
- mHomeScreenContainer.removeView(homeBanner);
- }
- });
- }
- }
-
- mHomeScreenContainer.setVisibility(View.VISIBLE);
- mHomeScreen.load(getSupportLoaderManager(),
- getSupportFragmentManager(),
- panelId,
- panelRestoreData,
- animator);
-
- // Hide the web content so it cannot be focused by screen readers.
- hideWebContentOnPropertyAnimationEnd(animator);
- }
-
- private void hideWebContentOnPropertyAnimationEnd(final PropertyAnimator animator) {
- if (animator == null) {
- hideWebContent();
- return;
- }
-
- animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
- @Override
- public void onPropertyAnimationStart() {
- mHideWebContentOnAnimationEnd = true;
- }
-
- @Override
- public void onPropertyAnimationEnd() {
- if (mHideWebContentOnAnimationEnd) {
- hideWebContent();
- }
- }
- });
- }
-
- private void hideWebContent() {
- // The view is set to INVISIBLE, rather than GONE, to avoid
- // the additional requestLayout() call.
- mLayerView.setVisibility(View.INVISIBLE);
- }
-
- /**
- * Hide the Onboarding pager on user action, and don't show any onFinish hints.
- * @param method TelemetryContract method by which action was taken
- * @return boolean of whether pager was visible
- */
- private boolean hideFirstrunPager(TelemetryContract.Method method) {
- if (!isFirstrunVisible()) {
- return false;
- }
-
- Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, method, "firstrun-pane");
-
- // Don't show any onFinish actions when hiding from this Activity.
- mFirstrunAnimationContainer.registerOnFinishListener(null);
- mFirstrunAnimationContainer.hide();
- return true;
- }
-
- /**
- * Hides the HomePager, using the url of the currently selected tab as the url to be
- * loaded.
- */
- private void hideHomePager() {
- final Tab selectedTab = Tabs.getInstance().getSelectedTab();
- final String url = (selectedTab != null) ? selectedTab.getURL() : null;
-
- hideHomePager(url);
- }
-
- /**
- * Hides the HomePager. The given url should be the url of the page to be loaded, or null
- * if a new page is not being loaded.
- */
- private void hideHomePager(final String url) {
- if (!isHomePagerVisible() || AboutPages.isAboutHome(url)) {
- return;
- }
-
- // Prevent race in hiding web content - see declaration for more info.
- mHideWebContentOnAnimationEnd = false;
-
- // Display the previously hidden web content (which prevented screen reader access).
- mLayerView.setVisibility(View.VISIBLE);
- mHomeScreenContainer.setVisibility(View.GONE);
-
- if (mHomeScreen != null) {
- mHomeScreen.unload();
- }
-
- mBrowserToolbar.setNextFocusDownId(R.id.layer_view);
-
- // Refresh toolbar height to possibly restore the toolbar padding
- refreshToolbarHeight();
- }
-
- private void showBrowserSearchAfterAnimation(PropertyAnimator animator) {
- if (animator == null) {
- showBrowserSearch();
- return;
- }
-
- animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
- @Override
- public void onPropertyAnimationStart() {
- }
-
- @Override
- public void onPropertyAnimationEnd() {
- showBrowserSearch();
- }
- });
- }
-
- private void showBrowserSearch() {
- if (mBrowserSearch.getUserVisibleHint()) {
- return;
- }
-
- mBrowserSearchContainer.setVisibility(View.VISIBLE);
-
- // Prevent overdraw by hiding the underlying web content and HomePager View
- hideWebContent();
- mHomeScreenContainer.setVisibility(View.INVISIBLE);
-
- final FragmentManager fm = getSupportFragmentManager();
-
- // In certain situations, showBrowserSearch() can be called immediately after hideBrowserSearch()
- // (see bug 925012). Because of an Android bug (http://code.google.com/p/android/issues/detail?id=61179),
- // calling FragmentTransaction#add immediately after FragmentTransaction#remove won't add the fragment's
- // view to the layout. Calling FragmentManager#executePendingTransactions before re-adding the fragment
- // prevents this issue.
- fm.executePendingTransactions();
-
- Fragment f = fm.findFragmentById(R.id.search_container);
-
- // checking if fragment is already present
- if (f != null) {
- fm.beginTransaction().show(f).commitAllowingStateLoss();
- mBrowserSearch.resetScrollState();
- } else {
- // add fragment if not already present
- fm.beginTransaction().add(R.id.search_container, mBrowserSearch, BROWSER_SEARCH_TAG).commitAllowingStateLoss();
- }
- mBrowserSearch.setUserVisibleHint(true);
-
- // We want to adjust the window size when the keyboard appears to bring the
- // SearchEngineBar above the keyboard. However, adjusting the window size
- // when hiding the keyboard results in graphical glitches where the keyboard was
- // because nothing was being drawn underneath (bug 933422). This can be
- // prevented drawing content under the keyboard (i.e. in the Window).
- //
- // We do this here because there are glitches when unlocking a device with
- // BrowserSearch in the foreground if we use BrowserSearch.onStart/Stop.
- getActivity().getWindow().setBackgroundDrawableResource(android.R.color.white);
- }
-
- private void hideBrowserSearch() {
- if (!mBrowserSearch.getUserVisibleHint()) {
- return;
- }
-
- // To prevent overdraw, the HomePager is hidden when BrowserSearch is displayed:
- // reverse that.
- showHomePager(Tabs.getInstance().getSelectedTab().getMostRecentHomePanel(),
- Tabs.getInstance().getSelectedTab().getMostRecentHomePanelData());
-
- mBrowserSearchContainer.setVisibility(View.INVISIBLE);
-
- getSupportFragmentManager().beginTransaction()
- .hide(mBrowserSearch).commitAllowingStateLoss();
- mBrowserSearch.setUserVisibleHint(false);
-
- getWindow().setBackgroundDrawable(null);
- }
-
- /**
- * Hides certain UI elements (e.g. button toast, tabs panel) when the
- * user touches the main layout.
- */
- private class HideOnTouchListener implements TouchEventInterceptor {
- private boolean mIsHidingTabs;
- private final Rect mTempRect = new Rect();
-
- @Override
- public boolean onInterceptTouchEvent(View view, MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- SnackbarBuilder.dismissCurrentSnackbar();
- }
-
-
-
- // We need to account for scroll state for the touched view otherwise
- // tapping on an "empty" part of the view will still be considered a
- // valid touch event.
- if (view.getScrollX() != 0 || view.getScrollY() != 0) {
- view.getHitRect(mTempRect);
- mTempRect.offset(-view.getScrollX(), -view.getScrollY());
-
- int[] viewCoords = new int[2];
- view.getLocationOnScreen(viewCoords);
-
- int x = (int) event.getRawX() - viewCoords[0];
- int y = (int) event.getRawY() - viewCoords[1];
-
- if (!mTempRect.contains(x, y))
- return false;
- }
-
- // If the tabs panel is showing, hide the tab panel and don't send the event to content.
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN && autoHideTabs()) {
- mIsHidingTabs = true;
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onTouch(View view, MotionEvent event) {
- if (mIsHidingTabs) {
- // Keep consuming events until the gesture finishes.
- int action = event.getActionMasked();
- if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
- mIsHidingTabs = false;
- }
- return true;
- }
- return false;
- }
- }
-
- private static Menu findParentMenu(Menu menu, MenuItem item) {
- final int itemId = item.getItemId();
-
- final int count = (menu != null) ? menu.size() : 0;
- for (int i = 0; i < count; i++) {
- MenuItem menuItem = menu.getItem(i);
- if (menuItem.getItemId() == itemId) {
- return menu;
- }
- if (menuItem.hasSubMenu()) {
- Menu parent = findParentMenu(menuItem.getSubMenu(), item);
- if (parent != null) {
- return parent;
- }
- }
- }
-
- return null;
- }
-
- /**
- * Add the provided item to the provided menu, which should be
- * the root (mMenu).
- */
- private void addAddonMenuItemToMenu(final Menu menu, final MenuItemInfo info) {
- info.added = true;
-
- final Menu destination;
- if (info.parent == 0) {
- destination = menu;
- } else if (info.parent == GECKO_TOOLS_MENU) {
- // The tools menu only exists in our -v11 resources.
- final MenuItem tools = menu.findItem(R.id.tools);
- destination = tools != null ? tools.getSubMenu() : menu;
- } else {
- final MenuItem parent = menu.findItem(info.parent);
- if (parent == null) {
- return;
- }
-
- Menu parentMenu = findParentMenu(menu, parent);
-
- if (!parent.hasSubMenu()) {
- parentMenu.removeItem(parent.getItemId());
- destination = parentMenu.addSubMenu(Menu.NONE, parent.getItemId(), Menu.NONE, parent.getTitle());
- if (parent.getIcon() != null) {
- ((SubMenu) destination).getItem().setIcon(parent.getIcon());
- }
- } else {
- destination = parent.getSubMenu();
- }
- }
-
- final MenuItem item = destination.add(Menu.NONE, info.id, Menu.NONE, info.label);
-
- item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- GeckoAppShell.notifyObservers("Menu:Clicked", Integer.toString(info.id - ADDON_MENU_OFFSET));
- return true;
- }
- });
-
- item.setCheckable(info.checkable);
- item.setChecked(info.checked);
- item.setEnabled(info.enabled);
- item.setVisible(info.visible);
- }
-
- private void addAddonMenuItem(final MenuItemInfo info) {
- if (mAddonMenuItemsCache == null) {
- mAddonMenuItemsCache = new Vector<MenuItemInfo>();
- }
-
- // Mark it as added if the menu was ready.
- info.added = (mMenu != null);
-
- // Always cache so we can rebuild after a locale switch.
- mAddonMenuItemsCache.add(info);
-
- if (mMenu == null) {
- return;
- }
-
- addAddonMenuItemToMenu(mMenu, info);
- }
-
- private void removeAddonMenuItem(int id) {
- // Remove add-on menu item from cache, if available.
- if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
- for (MenuItemInfo item : mAddonMenuItemsCache) {
- if (item.id == id) {
- mAddonMenuItemsCache.remove(item);
- break;
- }
- }
- }
-
- if (mMenu == null)
- return;
-
- final MenuItem menuItem = mMenu.findItem(id);
- if (menuItem != null)
- mMenu.removeItem(id);
- }
-
- private void updateAddonMenuItem(int id, JSONObject options) {
- // Set attribute for the menu item in cache, if available
- if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
- for (MenuItemInfo item : mAddonMenuItemsCache) {
- if (item.id == id) {
- item.label = options.optString("name", item.label);
- item.checkable = options.optBoolean("checkable", item.checkable);
- item.checked = options.optBoolean("checked", item.checked);
- item.enabled = options.optBoolean("enabled", item.enabled);
- item.visible = options.optBoolean("visible", item.visible);
- item.added = (mMenu != null);
- break;
- }
- }
- }
-
- if (mMenu == null) {
- return;
- }
-
- final MenuItem menuItem = mMenu.findItem(id);
- if (menuItem != null) {
- menuItem.setTitle(options.optString("name", menuItem.getTitle().toString()));
- menuItem.setCheckable(options.optBoolean("checkable", menuItem.isCheckable()));
- menuItem.setChecked(options.optBoolean("checked", menuItem.isChecked()));
- menuItem.setEnabled(options.optBoolean("enabled", menuItem.isEnabled()));
- menuItem.setVisible(options.optBoolean("visible", menuItem.isVisible()));
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Sets mMenu = menu.
- super.onCreateOptionsMenu(menu);
-
- // Inform the menu about the action-items bar.
- if (menu instanceof GeckoMenu &&
- HardwareUtils.isTablet()) {
- ((GeckoMenu) menu).setActionItemBarPresenter(mBrowserToolbar);
- }
-
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.browser_app_menu, mMenu);
-
- // Add add-on menu items, if any exist.
- if (mAddonMenuItemsCache != null && !mAddonMenuItemsCache.isEmpty()) {
- for (MenuItemInfo item : mAddonMenuItemsCache) {
- addAddonMenuItemToMenu(mMenu, item);
- }
- }
-
- // Action providers are available only ICS+.
- GeckoMenuItem share = (GeckoMenuItem) mMenu.findItem(R.id.share);
-
- GeckoActionProvider provider = GeckoActionProvider.getForType(GeckoActionProvider.DEFAULT_MIME_TYPE, this);
-
- share.setActionProvider(provider);
-
- return true;
- }
-
- @Override
- public void openOptionsMenu() {
- hideFirstrunPager(TelemetryContract.Method.MENU);
-
- // Disable menu access (for hardware buttons) when the software menu button is inaccessible.
- // Note that the software button is always accessible on new tablet.
- if (mBrowserToolbar.isEditing() && !HardwareUtils.isTablet()) {
- return;
- }
-
- if (ActivityUtils.isFullScreen(this)) {
- return;
- }
-
- if (areTabsShown()) {
- mTabsPanel.showMenu();
- return;
- }
-
- // Scroll custom menu to the top
- if (mMenuPanel != null)
- mMenuPanel.scrollTo(0, 0);
-
- // Scroll menu ListView (potentially in MenuPanel ViewGroup) to top.
- if (mMenu instanceof GeckoMenu) {
- ((GeckoMenu) mMenu).setSelection(0);
- }
-
- if (!mBrowserToolbar.openOptionsMenu())
- super.openOptionsMenu();
-
- if (mDynamicToolbar.isEnabled()) {
- mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
- }
- }
-
- @Override
- public void closeOptionsMenu() {
- if (!mBrowserToolbar.closeOptionsMenu())
- super.closeOptionsMenu();
- }
-
- @Override
- public void setFullScreen(final boolean fullscreen) {
- super.setFullScreen(fullscreen);
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- if (fullscreen) {
- if (mDynamicToolbar.isEnabled()) {
- mDynamicToolbar.setVisible(false, VisibilityTransition.IMMEDIATE);
- mDynamicToolbar.setPinned(true, PinReason.FULL_SCREEN);
- } else {
- setToolbarMargin(0);
- }
- mBrowserChrome.setVisibility(View.GONE);
- } else {
- mBrowserChrome.setVisibility(View.VISIBLE);
- if (mDynamicToolbar.isEnabled()) {
- mDynamicToolbar.setPinned(false, PinReason.FULL_SCREEN);
- mDynamicToolbar.setVisible(true, VisibilityTransition.IMMEDIATE);
- } else {
- setToolbarMargin(mBrowserChrome.getHeight());
- }
- }
- }
- });
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu aMenu) {
- if (aMenu == null)
- return false;
-
- // Hide the tab history panel when hardware menu button is pressed.
- TabHistoryFragment frag = (TabHistoryFragment) getSupportFragmentManager().findFragmentByTag(TAB_HISTORY_FRAGMENT_TAG);
- if (frag != null) {
- frag.dismiss();
- }
-
- if (!GeckoThread.isRunning()) {
- aMenu.findItem(R.id.settings).setEnabled(false);
- aMenu.findItem(R.id.help).setEnabled(false);
- }
-
- Tab tab = Tabs.getInstance().getSelectedTab();
- // Unlike other menu items, the bookmark star is not tinted. See {@link ThemedImageButton#setTintedDrawable}.
- final MenuItem bookmark = aMenu.findItem(R.id.bookmark);
- final MenuItem back = aMenu.findItem(R.id.back);
- final MenuItem forward = aMenu.findItem(R.id.forward);
- final MenuItem share = aMenu.findItem(R.id.share);
- final MenuItem bookmarksList = aMenu.findItem(R.id.bookmarks_list);
- final MenuItem historyList = aMenu.findItem(R.id.history_list);
- final MenuItem saveAsPDF = aMenu.findItem(R.id.save_as_pdf);
- final MenuItem print = aMenu.findItem(R.id.print);
- final MenuItem charEncoding = aMenu.findItem(R.id.char_encoding);
- final MenuItem findInPage = aMenu.findItem(R.id.find_in_page);
- final MenuItem desktopMode = aMenu.findItem(R.id.desktop_mode);
- final MenuItem enterGuestMode = aMenu.findItem(R.id.new_guest_session);
- final MenuItem exitGuestMode = aMenu.findItem(R.id.exit_guest_session);
-
- // Only show the "Quit" menu item on pre-ICS, television devices,
- // or if the user has explicitly enabled the clear on shutdown pref.
- // (We check the pref last to save the pref read.)
- // In ICS+, it's easy to kill an app through the task switcher.
- final boolean visible = HardwareUtils.isTelevision() ||
- !PrefUtils.getStringSet(GeckoSharedPrefs.forProfile(this),
- ClearOnShutdownPref.PREF,
- new HashSet<String>()).isEmpty();
- aMenu.findItem(R.id.quit).setVisible(visible);
-
- // If tab data is unavailable we disable most of the context menu and related items and
- // return early.
- if (tab == null || tab.getURL() == null) {
- bookmark.setEnabled(false);
- back.setEnabled(false);
- forward.setEnabled(false);
- share.setEnabled(false);
- saveAsPDF.setEnabled(false);
- print.setEnabled(false);
- findInPage.setEnabled(false);
-
- // NOTE: Use MenuUtils.safeSetEnabled because some actions might
- // be on the BrowserToolbar context menu.
- MenuUtils.safeSetEnabled(aMenu, R.id.page, false);
- MenuUtils.safeSetEnabled(aMenu, R.id.subscribe, false);
- MenuUtils.safeSetEnabled(aMenu, R.id.add_search_engine, false);
- MenuUtils.safeSetEnabled(aMenu, R.id.add_to_launcher, false);
-
- return true;
- }
-
- // If tab data IS available we need to manually enable items as necessary. They may have
- // been disabled if returning early above, hence every item must be toggled, even if it's
- // always expected to be enabled (e.g. the bookmark star is always enabled, except when
- // we don't have tab data).
-
- final boolean inGuestMode = GeckoProfile.get(this).inGuestMode();
-
- bookmark.setEnabled(true); // Might have been disabled above, ensure it's reenabled
- bookmark.setVisible(!inGuestMode);
- bookmark.setCheckable(true);
- bookmark.setChecked(tab.isBookmark());
- bookmark.setTitle(resolveBookmarkTitleID(tab.isBookmark()));
-
- // We don't use icons on GB builds so not resolving icons might conserve resources.
- bookmark.setIcon(resolveBookmarkIconID(tab.isBookmark()));
-
- back.setEnabled(tab.canDoBack());
- forward.setEnabled(tab.canDoForward());
- desktopMode.setChecked(tab.getDesktopMode());
-
- View backButtonView = MenuItemCompat.getActionView(back);
-
- if (backButtonView != null) {
- backButtonView.setOnLongClickListener(new Button.OnLongClickListener() {
- @Override
- public boolean onLongClick(View view) {
- Tab tab = Tabs.getInstance().getSelectedTab();
- if (tab != null) {
- closeOptionsMenu();
- return tabHistoryController.showTabHistory(tab,
- TabHistoryController.HistoryAction.BACK);
- }
- return false;
- }
- });
- }
-
- View forwardButtonView = MenuItemCompat.getActionView(forward);
-
- if (forwardButtonView != null) {
- forwardButtonView.setOnLongClickListener(new Button.OnLongClickListener() {
- @Override
- public boolean onLongClick(View view) {
- Tab tab = Tabs.getInstance().getSelectedTab();
- if (tab != null) {
- closeOptionsMenu();
- return tabHistoryController.showTabHistory(tab,
- TabHistoryController.HistoryAction.FORWARD);
- }
- return false;
- }
- });
- }
-
- String url = tab.getURL();
- if (AboutPages.isAboutReader(url)) {
- url = ReaderModeUtils.stripAboutReaderUrl(url);
- }
-
- // Disable share menuitem for about:, chrome:, file:, and resource: URIs
- final boolean shareVisible = Restrictions.isAllowed(this, Restrictable.SHARE);
- share.setVisible(shareVisible);
- final boolean shareEnabled = StringUtils.isShareableUrl(url) && shareVisible;
- share.setEnabled(shareEnabled);
- MenuUtils.safeSetEnabled(aMenu, R.id.downloads, Restrictions.isAllowed(this, Restrictable.DOWNLOAD));
-
- // NOTE: Use MenuUtils.safeSetEnabled because some actions might
- // be on the BrowserToolbar context menu.
- MenuUtils.safeSetEnabled(aMenu, R.id.page, !isAboutHome(tab));
- MenuUtils.safeSetEnabled(aMenu, R.id.subscribe, tab.hasFeeds());
- MenuUtils.safeSetEnabled(aMenu, R.id.add_search_engine, tab.hasOpenSearch());
- MenuUtils.safeSetEnabled(aMenu, R.id.add_to_launcher, !isAboutHome(tab));
-
- // This provider also applies to the quick share menu item.
- final GeckoActionProvider provider = ((GeckoMenuItem) share).getGeckoActionProvider();
- if (provider != null) {
- Intent shareIntent = provider.getIntent();
-
- // For efficiency, the provider's intent is only set once
- if (shareIntent == null) {
- shareIntent = new Intent(Intent.ACTION_SEND);
- shareIntent.setType("text/plain");
- provider.setIntent(shareIntent);
- }
-
- // Replace the existing intent's extras
- shareIntent.putExtra(Intent.EXTRA_TEXT, url);
- shareIntent.putExtra(Intent.EXTRA_SUBJECT, tab.getDisplayTitle());
- shareIntent.putExtra(Intent.EXTRA_TITLE, tab.getDisplayTitle());
- shareIntent.putExtra(ShareDialog.INTENT_EXTRA_DEVICES_ONLY, true);
-
- // Clear the existing thumbnail extras so we don't share an old thumbnail.
- shareIntent.removeExtra("share_screenshot_uri");
-
- // Include the thumbnail of the page being shared.
- BitmapDrawable drawable = tab.getThumbnail();
- if (drawable != null) {
- Bitmap thumbnail = drawable.getBitmap();
-
- // Kobo uses a custom intent extra for sharing thumbnails.
- if (Build.MANUFACTURER.equals("Kobo") && thumbnail != null) {
- File cacheDir = getExternalCacheDir();
-
- if (cacheDir != null) {
- File outFile = new File(cacheDir, "thumbnail.png");
-
- try {
- final java.io.FileOutputStream out = new java.io.FileOutputStream(outFile);
- try {
- thumbnail.compress(Bitmap.CompressFormat.PNG, 90, out);
- } finally {
- try {
- out.close();
- } catch (final IOException e) { /* Nothing to do here. */ }
- }
- } catch (FileNotFoundException e) {
- Log.e(LOGTAG, "File not found", e);
- }
-
- shareIntent.putExtra("share_screenshot_uri", Uri.parse(outFile.getPath()));
- }
- }
- }
- }
-
- final boolean privateTabVisible = Restrictions.isAllowed(this, Restrictable.PRIVATE_BROWSING);
- MenuUtils.safeSetVisible(aMenu, R.id.new_private_tab, privateTabVisible);
-
- // Disable PDF generation (save and print) for about:home and xul pages.
- boolean allowPDF = (!(isAboutHome(tab) ||
- tab.getContentType().equals("application/vnd.mozilla.xul+xml") ||
- tab.getContentType().startsWith("video/")));
- saveAsPDF.setEnabled(allowPDF);
- print.setEnabled(allowPDF);
- print.setVisible(Versions.feature19Plus);
-
- // Disable find in page for about:home, since it won't work on Java content.
- findInPage.setEnabled(!isAboutHome(tab));
-
- charEncoding.setVisible(GeckoPreferences.getCharEncodingState());
-
- if (getProfile().inGuestMode()) {
- exitGuestMode.setVisible(true);
- } else {
- enterGuestMode.setVisible(true);
- }
-
- if (!Restrictions.isAllowed(this, Restrictable.GUEST_BROWSING)) {
- MenuUtils.safeSetVisible(aMenu, R.id.new_guest_session, false);
- }
-
- if (!Restrictions.isAllowed(this, Restrictable.INSTALL_EXTENSION)) {
- MenuUtils.safeSetVisible(aMenu, R.id.addons, false);
- }
-
- // Hide panel menu items if the panels themselves are hidden.
- // If we don't know whether the panels are hidden, just show the menu items.
- final SharedPreferences prefs = GeckoSharedPrefs.forProfile(getContext());
- bookmarksList.setVisible(prefs.getBoolean(HomeConfig.PREF_KEY_BOOKMARKS_PANEL_ENABLED, true));
- historyList.setVisible(prefs.getBoolean(HomeConfig.PREF_KEY_HISTORY_PANEL_ENABLED, true));
-
- return true;
- }
-
- private int resolveBookmarkIconID(final boolean isBookmark) {
- if (isBookmark) {
- return R.drawable.star_blue;
- } else {
- return R.drawable.ic_menu_bookmark_add;
- }
- }
-
- private int resolveBookmarkTitleID(final boolean isBookmark) {
- return (isBookmark ? R.string.bookmark_remove : R.string.bookmark);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- Tab tab = null;
- Intent intent = null;
-
- final int itemId = item.getItemId();
-
- // Track the menu action. We don't know much about the context, but we can use this to determine
- // the frequency of use for various actions.
- String extras = getResources().getResourceEntryName(itemId);
- if (TextUtils.equals(extras, "new_private_tab")) {
- // Mask private browsing
- extras = "new_tab";
- }
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.MENU, extras);
-
- mBrowserToolbar.cancelEdit();
-
- if (itemId == R.id.bookmark) {
- tab = Tabs.getInstance().getSelectedTab();
- if (tab != null) {
- final String extra;
- if (AboutPages.isAboutReader(tab.getURL())) {
- extra = "bookmark_reader";
- } else {
- extra = "bookmark";
- }
-
- if (item.isChecked()) {
- Telemetry.sendUIEvent(TelemetryContract.Event.UNSAVE, TelemetryContract.Method.MENU, extra);
- tab.removeBookmark();
- item.setTitle(resolveBookmarkTitleID(false));
- item.setIcon(resolveBookmarkIconID(false));
- } else {
- Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.MENU, extra);
- tab.addBookmark();
- item.setTitle(resolveBookmarkTitleID(true));
- item.setIcon(resolveBookmarkIconID(true));
- }
- }
- return true;
- }
-
- if (itemId == R.id.share) {
- tab = Tabs.getInstance().getSelectedTab();
- if (tab != null) {
- String url = tab.getURL();
- if (url != null) {
- url = ReaderModeUtils.stripAboutReaderUrl(url);
-
- // Context: Sharing via chrome list (no explicit session is active)
- Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "menu");
-
- IntentHelper.openUriExternal(url, "text/plain", "", "", Intent.ACTION_SEND, tab.getDisplayTitle(), false);
- }
- }
- return true;
- }
-
- if (itemId == R.id.reload) {
- tab = Tabs.getInstance().getSelectedTab();
- if (tab != null)
- tab.doReload(false);
- return true;
- }
-
- if (itemId == R.id.back) {
- tab = Tabs.getInstance().getSelectedTab();
- if (tab != null)
- tab.doBack();
- return true;
- }
-
- if (itemId == R.id.forward) {
- tab = Tabs.getInstance().getSelectedTab();
- if (tab != null)
- tab.doForward();
- return true;
- }
-
- if (itemId == R.id.bookmarks_list) {
- final String url = AboutPages.getURLForBuiltinPanelType(PanelType.BOOKMARKS);
- Tabs.getInstance().loadUrl(url);
- return true;
- }
-
- if (itemId == R.id.history_list) {
- final String url = AboutPages.getURLForBuiltinPanelType(PanelType.COMBINED_HISTORY);
- Tabs.getInstance().loadUrl(url);
- return true;
- }
-
- if (itemId == R.id.save_as_pdf) {
- Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.MENU, "pdf");
- GeckoAppShell.notifyObservers("SaveAs:PDF", null);
- return true;
- }
-
- if (itemId == R.id.print) {
- Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.MENU, "print");
- PrintHelper.printPDF(this);
- return true;
- }
-
- if (itemId == R.id.settings) {
- intent = new Intent(this, GeckoPreferences.class);
-
- // We want to know when the Settings activity returns, because
- // we might need to redisplay based on a locale change.
- startActivityForResult(intent, ACTIVITY_REQUEST_PREFERENCES);
- return true;
- }
-
- if (itemId == R.id.help) {
- final String VERSION = AppConstants.MOZ_APP_VERSION;
- final String OS = AppConstants.OS_TARGET;
- final String LOCALE = Locales.getLanguageTag(Locale.getDefault());
-
- final String URL = getResources().getString(R.string.help_link, VERSION, OS, LOCALE);
- Tabs.getInstance().loadUrlInTab(URL);
- return true;
- }
-
- if (itemId == R.id.addons) {
- Tabs.getInstance().loadUrlInTab(AboutPages.ADDONS);
- return true;
- }
-
- if (itemId == R.id.logins) {
- Tabs.getInstance().loadUrlInTab(AboutPages.LOGINS);
- return true;
- }
-
- if (itemId == R.id.downloads) {
- Tabs.getInstance().loadUrlInTab(AboutPages.DOWNLOADS);
- return true;
- }
-
- if (itemId == R.id.char_encoding) {
- GeckoAppShell.notifyObservers("CharEncoding:Get", null);
- return true;
- }
-
- if (itemId == R.id.find_in_page) {
- mFindInPageBar.show();
- return true;
- }
-
- if (itemId == R.id.desktop_mode) {
- Tab selectedTab = Tabs.getInstance().getSelectedTab();
- if (selectedTab == null)
- return true;
- JSONObject args = new JSONObject();
- try {
- args.put("desktopMode", !item.isChecked());
- args.put("tabId", selectedTab.getId());
- } catch (JSONException e) {
- Log.e(LOGTAG, "error building json arguments", e);
- }
- GeckoAppShell.notifyObservers("DesktopMode:Change", args.toString());
- return true;
- }
-
- if (itemId == R.id.new_tab) {
- addTab();
- return true;
- }
-
- if (itemId == R.id.new_private_tab) {
- addPrivateTab();
- return true;
- }
-
- if (itemId == R.id.new_guest_session) {
- showGuestModeDialog(GuestModeDialog.ENTERING);
- return true;
- }
-
- if (itemId == R.id.exit_guest_session) {
- showGuestModeDialog(GuestModeDialog.LEAVING);
- return true;
- }
-
- // We have a few menu items that can also be in the context menu. If
- // we have not already handled the item, give the context menu handler
- // a chance.
- if (onContextItemSelected(item)) {
- return true;
- }
-
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public boolean onMenuItemLongClick(MenuItem item) {
- if (item.getItemId() == R.id.reload) {
- Tab tab = Tabs.getInstance().getSelectedTab();
- if (tab != null) {
- tab.doReload(true);
-
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.MENU, "reload_force");
- }
- return true;
- }
-
- return super.onMenuItemLongClick(item);
- }
-
- public void showGuestModeDialog(final GuestModeDialog type) {
- if ((type == GuestModeDialog.ENTERING) == getProfile().inGuestMode()) {
- // Don't show enter dialog if we are already in guest mode; same with leaving.
- return;
- }
-
- final Prompt ps = new Prompt(this, new Prompt.PromptCallback() {
- @Override
- public void onPromptFinished(String result) {
- try {
- int itemId = new JSONObject(result).getInt("button");
- if (itemId == 0) {
- final Context context = GeckoAppShell.getApplicationContext();
- if (type == GuestModeDialog.ENTERING) {
- GeckoProfile.enterGuestMode(context);
- } else {
- GeckoProfile.leaveGuestMode(context);
- // Now's a good time to make sure we're not displaying the
- // Guest Browsing notification.
- GuestSession.hideNotification(context);
- }
- doRestart();
- }
- } catch (JSONException ex) {
- Log.e(LOGTAG, "Exception reading guest mode prompt result", ex);
- }
- }
- });
-
- Resources res = getResources();
- ps.setButtons(new String[] {
- res.getString(R.string.guest_session_dialog_continue),
- res.getString(R.string.guest_session_dialog_cancel)
- });
-
- int titleString = 0;
- int msgString = 0;
- if (type == GuestModeDialog.ENTERING) {
- titleString = R.string.new_guest_session_title;
- msgString = R.string.new_guest_session_text;
- } else {
- titleString = R.string.exit_guest_session_title;
- msgString = R.string.exit_guest_session_text;
- }
-
- ps.show(res.getString(titleString), res.getString(msgString), null, ListView.CHOICE_MODE_NONE);
- }
-
- /**
- * Handle a long press on the back button
- */
- private boolean handleBackLongPress() {
- // If the tab search history is already shown, do nothing.
- TabHistoryFragment frag = (TabHistoryFragment) getSupportFragmentManager().findFragmentByTag(TAB_HISTORY_FRAGMENT_TAG);
- if (frag != null) {
- return true;
- }
-
- Tab tab = Tabs.getInstance().getSelectedTab();
- if (tab != null && !tab.isEditing()) {
- return tabHistoryController.showTabHistory(tab, TabHistoryController.HistoryAction.ALL);
- }
-
- return false;
- }
-
- /**
- * This will detect if the key pressed is back. If so, will show the history.
- */
- @Override
- public boolean onKeyLongPress(int keyCode, KeyEvent event) {
- // onKeyLongPress is broken in Android N, see onKeyDown() for more information. We add a version
- // check here to match our fallback code in order to avoid handling a long press twice (which
- // could happen if newer versions of android and/or other vendors were to fix this problem).
- if (Versions.preN &&
- keyCode == KeyEvent.KEYCODE_BACK) {
- if (handleBackLongPress()) {
- return true;
- }
-
- }
- return super.onKeyLongPress(keyCode, event);
- }
-
- /*
- * If the app has been launched a certain number of times, and we haven't asked for feedback before,
- * open a new tab with about:feedback when launching the app from the icon shortcut.
- */
- @Override
- protected void onNewIntent(Intent externalIntent) {
- final SafeIntent intent = new SafeIntent(externalIntent);
- String action = intent.getAction();
-
- final boolean isViewAction = Intent.ACTION_VIEW.equals(action);
- final boolean isBookmarkAction = GeckoApp.ACTION_HOMESCREEN_SHORTCUT.equals(action);
- final boolean isTabQueueAction = TabQueueHelper.LOAD_URLS_ACTION.equals(action);
- final boolean isViewMultipleAction = ACTION_VIEW_MULTIPLE.equals(action);
-
- if (mInitialized && (isViewAction || isBookmarkAction)) {
- // Dismiss editing mode if the user is loading a URL from an external app.
- mBrowserToolbar.cancelEdit();
-
- // Hide firstrun-pane if the user is loading a URL from an external app.
- hideFirstrunPager(TelemetryContract.Method.NONE);
-
- if (isBookmarkAction) {
- // GeckoApp.ACTION_HOMESCREEN_SHORTCUT means we're opening a bookmark that
- // was added to Android's homescreen.
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.HOMESCREEN);
- }
- }
-
- showTabQueuePromptIfApplicable(intent);
-
- // GeckoApp will wrap this unsafe external intent in a SafeIntent.
- super.onNewIntent(externalIntent);
-
- if (AppConstants.MOZ_ANDROID_BEAM && NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
- String uri = intent.getDataString();
- mLayerView.loadUri(uri, GeckoView.LOAD_NEW_TAB);
- }
-
- // Only solicit feedback when the app has been launched from the icon shortcut.
- if (GuestSession.NOTIFICATION_INTENT.equals(action)) {
- GuestSession.onNotificationIntentReceived(this);
- }
-
- // If the user has clicked the tab queue notification then load the tabs.
- if (TabQueueHelper.TAB_QUEUE_ENABLED && mInitialized && isTabQueueAction) {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.NOTIFICATION, "tabqueue");
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- openQueuedTabs();
- }
- });
- }
-
- // Custom intent action for opening multiple URLs at once
- if (isViewMultipleAction) {
- openMultipleTabsFromIntent(intent);
- }
-
- for (final BrowserAppDelegate delegate : delegates) {
- delegate.onNewIntent(this, intent);
- }
-
- if (!mInitialized || !Intent.ACTION_MAIN.equals(action)) {
- return;
- }
-
- // Check to see how many times the app has been launched.
- final String keyName = getPackageName() + ".feedback_launch_count";
- final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
-
- // Faster on main thread with an async apply().
- try {
- SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
- int launchCount = settings.getInt(keyName, 0);
- if (launchCount < FEEDBACK_LAUNCH_COUNT) {
- // Increment the launch count and store the new value.
- launchCount++;
- settings.edit().putInt(keyName, launchCount).apply();
-
- // If we've reached our magic number, show the feedback page.
- if (launchCount == FEEDBACK_LAUNCH_COUNT) {
- GeckoAppShell.notifyObservers("Feedback:Show", null);
- }
- }
- } finally {
- StrictMode.setThreadPolicy(savedPolicy);
- }
- }
-
- public void openUrls(List<String> urls) {
- try {
- JSONArray array = new JSONArray();
- for (String url : urls) {
- array.put(url);
- }
-
- JSONObject object = new JSONObject();
- object.put("urls", array);
-
- GeckoAppShell.notifyObservers("Tabs:OpenMultiple", object.toString());
- } catch (JSONException e) {
- Log.e(LOGTAG, "Unable to create JSON for opening multiple URLs");
- }
- }
-
- private void showTabQueuePromptIfApplicable(final SafeIntent intent) {
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- // We only want to show the prompt if the browser has been opened from an external url
- if (TabQueueHelper.TAB_QUEUE_ENABLED && mInitialized
- && Intent.ACTION_VIEW.equals(intent.getAction())
- && !intent.getBooleanExtra(BrowserContract.SKIP_TAB_QUEUE_FLAG, false)
- && TabQueueHelper.shouldShowTabQueuePrompt(BrowserApp.this)) {
- Intent promptIntent = new Intent(BrowserApp.this, TabQueuePrompt.class);
- startActivityForResult(promptIntent, ACTIVITY_REQUEST_TAB_QUEUE);
- }
- }
- });
- }
-
- private void resetFeedbackLaunchCount() {
- SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE);
- settings.edit().putInt(getPackageName() + ".feedback_launch_count", 0).apply();
- }
-
- // HomePager.OnUrlOpenListener
- @Override
- public void onUrlOpen(String url, EnumSet<OnUrlOpenListener.Flags> flags) {
- if (flags.contains(OnUrlOpenListener.Flags.OPEN_WITH_INTENT)) {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setData(Uri.parse(url));
- startActivity(intent);
- } else {
- // By default this listener is used for lists where the offline reader-view icon
- // is shown - hence we need to redirect to the reader-view page by default.
- // However there are some cases where we might not want to use this, e.g.
- // for topsites where we do not indicate that a page is an offline reader-view bookmark too.
- final String pageURL;
- if (!flags.contains(OnUrlOpenListener.Flags.NO_READER_VIEW)) {
- pageURL = SavedReaderViewHelper.getReaderURLIfCached(getContext(), url);
- } else {
- pageURL = url;
- }
-
- if (!maybeSwitchToTab(pageURL, flags)) {
- openUrlAndStopEditing(pageURL);
- clearSelectedTabApplicationId();
- }
- }
- }
-
- // HomePager.OnUrlOpenInBackgroundListener
- @Override
- public void onUrlOpenInBackground(final String url, EnumSet<OnUrlOpenInBackgroundListener.Flags> flags) {
- if (url == null) {
- throw new IllegalArgumentException("url must not be null");
- }
- if (flags == null) {
- throw new IllegalArgumentException("flags must not be null");
- }
-
- // We only use onUrlOpenInBackgroundListener for the homepanel context menus, hence
- // we should always be checking whether we want the readermode version
- final String pageURL = SavedReaderViewHelper.getReaderURLIfCached(getContext(), url);
-
- final boolean isPrivate = flags.contains(OnUrlOpenInBackgroundListener.Flags.PRIVATE);
-
- int loadFlags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_BACKGROUND;
- if (isPrivate) {
- loadFlags |= Tabs.LOADURL_PRIVATE;
- }
-
- final Tab newTab = Tabs.getInstance().loadUrl(pageURL, loadFlags);
-
- // We switch to the desired tab by unique ID, which closes any window
- // for a race between opening the tab and closing it, and switching to
- // it. We could also switch to the Tab explicitly, but we don't want to
- // hold a reference to the Tab itself in the anonymous listener class.
- final int newTabId = newTab.getId();
-
- final SnackbarBuilder.SnackbarCallback callback = new SnackbarBuilder.SnackbarCallback() {
- @Override
- public void onClick(View v) {
- Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.TOAST, "switchtab");
-
- maybeSwitchToTab(newTabId);
- }
- };
-
- final String message = isPrivate ?
- getResources().getString(R.string.new_private_tab_opened) :
- getResources().getString(R.string.new_tab_opened);
- final String buttonMessage = getResources().getString(R.string.switch_button_message);
-
- SnackbarBuilder.builder(this)
- .message(message)
- .duration(Snackbar.LENGTH_LONG)
- .action(buttonMessage)
- .callback(callback)
- .buildAndShow();
- }
-
- // BrowserSearch.OnSearchListener
- @Override
- public void onSearch(SearchEngine engine, final String text, final TelemetryContract.Method method) {
- // Don't store searches that happen in private tabs. This assumes the user can only
- // perform a search inside the currently selected tab, which is true for searches
- // that come from SearchEngineRow.
- if (!Tabs.getInstance().getSelectedTab().isPrivate()) {
- storeSearchQuery(text);
- }
-
- // We don't use SearchEngine.getEngineIdentifier because it can
- // return a custom search engine name, which is a privacy concern.
- final String identifierToRecord = (engine.identifier != null) ? engine.identifier : "other";
- recordSearch(GeckoSharedPrefs.forProfile(this), identifierToRecord, method);
- openUrlAndStopEditing(text, engine.name);
- }
-
- // BrowserSearch.OnEditSuggestionListener
- @Override
- public void onEditSuggestion(String suggestion) {
- mBrowserToolbar.onEditSuggestion(suggestion);
- }
-
- @Override
- public int getLayout() { return R.layout.gecko_app; }
-
- public SearchEngineManager getSearchEngineManager() {
- return mSearchEngineManager;
- }
-
- // For use from tests only.
- @RobocopTarget
- public ReadingListHelper getReadingListHelper() {
- return mReadingListHelper;
- }
-
- /**
- * Launch UI that lets the user update Firefox.
- *
- * This depends on the current channel: Release and Beta both direct to the
- * Google Play Store. If updating is enabled, Aurora, Nightly, and custom
- * builds open about:, which provides an update interface.
- *
- * If updating is not enabled, this simply logs an error.
- *
- * @return true if update UI was launched.
- */
- protected boolean handleUpdaterLaunch() {
- if (AppConstants.RELEASE_OR_BETA) {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setData(Uri.parse("market://details?id=" + getPackageName()));
- startActivity(intent);
- return true;
- }
-
- if (AppConstants.MOZ_UPDATER) {
- Tabs.getInstance().loadUrlInTab(AboutPages.UPDATER);
- return true;
- }
-
- Log.w(LOGTAG, "No candidate updater found; ignoring launch request.");
- return false;
- }
-
- /* Implementing ActionModeCompat.Presenter */
- @Override
- public void startActionModeCompat(final ActionModeCompat.Callback callback) {
- // If actionMode is null, we're not currently showing one. Flip to the action mode view
- if (mActionMode == null) {
- mActionBarFlipper.showNext();
- DynamicToolbarAnimator toolbar = mLayerView.getDynamicToolbarAnimator();
-
- // If the toolbar is dynamic and not currently showing, just slide it in
- if (mDynamicToolbar.isEnabled() && toolbar.getToolbarTranslation() != 0) {
- mDynamicToolbar.setTemporarilyVisible(true, VisibilityTransition.ANIMATE);
- }
- mDynamicToolbar.setPinned(true, PinReason.ACTION_MODE);
-
- } else {
- // Otherwise, we're already showing an action mode. Just finish it and show the new one
- mActionMode.finish();
- }
-
- mActionMode = new ActionModeCompat(BrowserApp.this, callback, mActionBar);
- if (callback.onCreateActionMode(mActionMode, mActionMode.getMenu())) {
- mActionMode.invalidate();
- }
- }
-
- /* Implementing ActionModeCompat.Presenter */
- @Override
- public void endActionModeCompat() {
- if (mActionMode == null) {
- return;
- }
-
- mActionMode.finish();
- mActionMode = null;
- mDynamicToolbar.setPinned(false, PinReason.ACTION_MODE);
-
- mActionBarFlipper.showPrevious();
-
- // Only slide the urlbar out if it was hidden when the action mode started
- // Don't animate hiding it so that there's no flash as we switch back to url mode
- mDynamicToolbar.setTemporarilyVisible(false, VisibilityTransition.IMMEDIATE);
- }
-
- public static interface TabStripInterface {
- public void refresh();
- void setOnTabChangedListener(OnTabAddedOrRemovedListener listener);
- interface OnTabAddedOrRemovedListener {
- void onTabChanged();
- }
- }
-
- @Override
- protected void recordStartupActionTelemetry(final String passedURL, final String action) {
- final TelemetryContract.Method method;
- if (ACTION_HOMESCREEN_SHORTCUT.equals(action)) {
- // This action is also recorded via "loadurl.1" > "homescreen".
- method = TelemetryContract.Method.HOMESCREEN;
- } else if (passedURL == null) {
- Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH, TelemetryContract.Method.HOMESCREEN, "launcher");
- method = TelemetryContract.Method.HOMESCREEN;
- } else {
- // This is action is also recorded via "loadurl.1" > "intent".
- method = TelemetryContract.Method.INTENT;
- }
-
- if (GeckoProfile.get(this).inGuestMode()) {
- Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH, method, "guest");
- } else if (Restrictions.isRestrictedProfile(this)) {
- Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH, method, "restricted");
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/BrowserLocaleManager.java b/mobile/android/base/java/org/mozilla/gecko/BrowserLocaleManager.java
deleted file mode 100644
index c5c041c7a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserLocaleManager.java
+++ /dev/null
@@ -1,439 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.annotation.ReflectionTarget;
-import org.mozilla.gecko.util.GeckoJarReader;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.util.Log;
-
-/**
- * This class manages persistence, application, and otherwise handling of
- * user-specified locales.
- *
- * Of note:
- *
- * * It's a singleton, because its scope extends to that of the application,
- * and definitionally all changes to the locale of the app must go through
- * this.
- * * It's lazy.
- * * It has ties into the Gecko event system, because it has to tell Gecko when
- * to switch locale.
- * * It relies on using the SharedPreferences file owned by the browser (in
- * Fennec's case, "GeckoApp") for performance.
- */
-public class BrowserLocaleManager implements LocaleManager {
- private static final String LOG_TAG = "GeckoLocales";
-
- private static final String EVENT_LOCALE_CHANGED = "Locale:Changed";
- private static final String PREF_LOCALE = "locale";
-
- private static final String FALLBACK_LOCALE_TAG = "en-US";
-
- // These are volatile because we don't impose restrictions
- // over which thread calls our methods.
- private volatile Locale currentLocale;
- private volatile Locale systemLocale = Locale.getDefault();
-
- private final AtomicBoolean inited = new AtomicBoolean(false);
- private boolean systemLocaleDidChange;
- private BroadcastReceiver receiver;
-
- private static final AtomicReference<LocaleManager> instance = new AtomicReference<LocaleManager>();
-
- @ReflectionTarget
- public static LocaleManager getInstance() {
- LocaleManager localeManager = instance.get();
- if (localeManager != null) {
- return localeManager;
- }
-
- localeManager = new BrowserLocaleManager();
- if (instance.compareAndSet(null, localeManager)) {
- return localeManager;
- } else {
- return instance.get();
- }
- }
-
- @Override
- public boolean isEnabled() {
- return AppConstants.MOZ_LOCALE_SWITCHER;
- }
-
- /**
- * Ensure that you call this early in your application startup,
- * and with a context that's sufficiently long-lived (typically
- * the application context).
- *
- * Calling multiple times is harmless.
- */
- @Override
- public void initialize(final Context context) {
- if (!inited.compareAndSet(false, true)) {
- return;
- }
-
- receiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final Locale current = systemLocale;
-
- // We don't trust Locale.getDefault() here, because we make a
- // habit of mutating it! Use the one Android supplies, because
- // that gets regularly reset.
- // The default value of systemLocale is fine, because we haven't
- // yet swizzled Locale during static initialization.
- systemLocale = context.getResources().getConfiguration().locale;
- systemLocaleDidChange = true;
-
- Log.d(LOG_TAG, "System locale changed from " + current + " to " + systemLocale);
- }
- };
- context.registerReceiver(receiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
- }
-
- @Override
- public boolean systemLocaleDidChange() {
- return systemLocaleDidChange;
- }
-
- /**
- * Every time the system gives us a new configuration, it
- * carries the external locale. Fix it.
- */
- @Override
- public void correctLocale(Context context, Resources res, Configuration config) {
- final Locale current = getCurrentLocale(context);
- if (current == null) {
- Log.d(LOG_TAG, "No selected locale. No correction needed.");
- return;
- }
-
- // I know it's tempting to short-circuit here if the config seems to be
- // up-to-date, but the rest is necessary.
-
- config.locale = current;
-
- // The following two lines are heavily commented in case someone
- // decides to chase down performance improvements and decides to
- // question what's going on here.
- // Both lines should be cheap, *but*...
-
- // This is unnecessary for basic string choice, but it almost
- // certainly comes into play when rendering numbers, deciding on RTL,
- // etc. Take it out if you can prove that's not the case.
- Locale.setDefault(current);
-
- // This seems to be a no-op, but every piece of documentation under the
- // sun suggests that it's necessary, and it certainly makes sense.
- res.updateConfiguration(config, null);
- }
-
- /**
- * We can be in one of two states.
- *
- * If the user has not explicitly chosen a Firefox-specific locale, we say
- * we are "mirroring" the system locale.
- *
- * When we are not mirroring, system locale changes do not impact Firefox
- * and are essentially ignored; the user's locale selection is the only
- * thing we care about, and we actively correct incoming configuration
- * changes to reflect the user's chosen locale.
- *
- * By contrast, when we are mirroring, system locale changes cause Firefox
- * to reflect the new system locale, as if the user picked the new locale.
- *
- * If we're currently mirroring the system locale, this method returns the
- * supplied configuration's locale, unless the current activity locale is
- * correct. If we're not currently mirroring, this method updates the
- * configuration object to match the user's currently selected locale, and
- * returns that, unless the current activity locale is correct.
- *
- * If the current activity locale is correct, returns null.
- *
- * The caller is expected to redisplay themselves accordingly.
- *
- * This method is intended to be called from inside
- * <code>onConfigurationChanged(Configuration)</code> as part of a strategy
- * to detect and either apply or undo system locale changes.
- */
- @Override
- public Locale onSystemConfigurationChanged(final Context context, final Resources resources, final Configuration configuration, final Locale currentActivityLocale) {
- if (!isMirroringSystemLocale(context)) {
- correctLocale(context, resources, configuration);
- }
-
- final Locale changed = configuration.locale;
- if (changed.equals(currentActivityLocale)) {
- return null;
- }
-
- return changed;
- }
-
- /**
- * Gecko needs to know the OS locale to compute a useful Accept-Language
- * header. If it changed since last time, send a message to Gecko and
- * persist the new value. If unchanged, returns immediately.
- *
- * @param prefs the SharedPreferences instance to use. Cannot be null.
- * @param osLocale the new locale instance. Safe if null.
- */
- public static void storeAndNotifyOSLocale(final SharedPreferences prefs,
- final Locale osLocale) {
- if (osLocale == null) {
- return;
- }
-
- final String lastOSLocale = prefs.getString("osLocale", null);
- final String osLocaleString = osLocale.toString();
-
- if (osLocaleString.equals(lastOSLocale)) {
- return;
- }
-
- // Store the Java-native form.
- prefs.edit().putString("osLocale", osLocaleString).apply();
-
- // The value we send to Gecko should be a language tag, not
- // a Java locale string.
- final String osLanguageTag = Locales.getLanguageTag(osLocale);
- GeckoAppShell.notifyObservers("Locale:OS", osLanguageTag);
- }
-
- @Override
- public String getAndApplyPersistedLocale(Context context) {
- initialize(context);
-
- final long t1 = android.os.SystemClock.uptimeMillis();
- final String localeCode = getPersistedLocale(context);
- if (localeCode == null) {
- return null;
- }
-
- // Note that we don't tell Gecko about this. We notify Gecko when the
- // locale is set, not when we update Java.
- final String resultant = updateLocale(context, localeCode);
-
- if (resultant == null) {
- // Update the configuration anyway.
- updateConfiguration(context, currentLocale);
- }
-
- final long t2 = android.os.SystemClock.uptimeMillis();
- Log.i(LOG_TAG, "Locale read and update took: " + (t2 - t1) + "ms.");
- return resultant;
- }
-
- /**
- * Returns the set locale if it changed.
- *
- * Always persists and notifies Gecko.
- */
- @Override
- public String setSelectedLocale(Context context, String localeCode) {
- final String resultant = updateLocale(context, localeCode);
-
- // We always persist and notify Gecko, even if nothing seemed to
- // change. This might happen if you're picking a locale that's the same
- // as the current OS locale. The OS locale might change next time we
- // launch, and we need the Gecko pref and persisted locale to have been
- // set by the time that happens.
- persistLocale(context, localeCode);
-
- // Tell Gecko.
- GeckoAppShell.notifyObservers(EVENT_LOCALE_CHANGED, Locales.getLanguageTag(getCurrentLocale(context)));
-
- return resultant;
- }
-
- @Override
- public void resetToSystemLocale(Context context) {
- // Wipe the pref.
- final SharedPreferences settings = getSharedPreferences(context);
- settings.edit().remove(PREF_LOCALE).apply();
-
- // Apply the system locale.
- updateLocale(context, systemLocale);
-
- // Tell Gecko.
- GeckoAppShell.notifyObservers(EVENT_LOCALE_CHANGED, "");
- }
-
- /**
- * This is public to allow for an activity to force the
- * current locale to be applied if necessary (e.g., when
- * a new activity launches).
- */
- @Override
- public void updateConfiguration(Context context, Locale locale) {
- Resources res = context.getResources();
- Configuration config = res.getConfiguration();
-
- // We should use setLocale, but it's unexpectedly missing
- // on real devices.
- config.locale = locale;
- res.updateConfiguration(config, null);
- }
-
- private SharedPreferences getSharedPreferences(Context context) {
- return GeckoSharedPrefs.forApp(context);
- }
-
- /**
- * @return the persisted locale in Java format: "en_US".
- */
- private String getPersistedLocale(Context context) {
- final SharedPreferences settings = getSharedPreferences(context);
- final String locale = settings.getString(PREF_LOCALE, "");
-
- if ("".equals(locale)) {
- return null;
- }
- return locale;
- }
-
- private void persistLocale(Context context, String localeCode) {
- final SharedPreferences settings = getSharedPreferences(context);
- settings.edit().putString(PREF_LOCALE, localeCode).apply();
- }
-
- @Override
- public Locale getCurrentLocale(Context context) {
- if (currentLocale != null) {
- return currentLocale;
- }
-
- final String current = getPersistedLocale(context);
- if (current == null) {
- return null;
- }
- return currentLocale = Locales.parseLocaleCode(current);
- }
-
- /**
- * Updates the Java locale and the Android configuration.
- *
- * Returns the persisted locale if it differed.
- *
- * Does not notify Gecko.
- *
- * @param localeCode a locale string in Java format: "en_US".
- * @return if it differed, a locale string in Java format: "en_US".
- */
- private String updateLocale(Context context, String localeCode) {
- // Fast path.
- final Locale defaultLocale = Locale.getDefault();
- if (defaultLocale.toString().equals(localeCode)) {
- return null;
- }
-
- final Locale locale = Locales.parseLocaleCode(localeCode);
-
- return updateLocale(context, locale);
- }
-
- /**
- * @return the Java locale string: e.g., "en_US".
- */
- private String updateLocale(Context context, final Locale locale) {
- // Fast path.
- if (Locale.getDefault().equals(locale)) {
- return null;
- }
-
- Locale.setDefault(locale);
- currentLocale = locale;
-
- // Update resources.
- updateConfiguration(context, locale);
-
- return locale.toString();
- }
-
- private boolean isMirroringSystemLocale(final Context context) {
- return getPersistedLocale(context) == null;
- }
-
- /**
- * Examines <code>multilocale.json</code>, returning the included list of
- * locale codes.
- *
- * If <code>multilocale.json</code> is not present, returns
- * <code>null</code>. In that case, consider {@link #getFallbackLocaleTag()}.
- *
- * multilocale.json currently looks like this:
- *
- * <code>
- * {"locales": ["en-US", "be", "ca", "cs", "da", "de", "en-GB",
- * "en-ZA", "es-AR", "es-ES", "es-MX", "et", "fi",
- * "fr", "ga-IE", "hu", "id", "it", "ja", "ko",
- * "lt", "lv", "nb-NO", "nl", "pl", "pt-BR",
- * "pt-PT", "ro", "ru", "sk", "sl", "sv-SE", "th",
- * "tr", "uk", "zh-CN", "zh-TW", "en-US"]}
- * </code>
- */
- public static Collection<String> getPackagedLocaleTags(final Context context) {
- final String resPath = "res/multilocale.json";
- final String jarURL = GeckoJarReader.getJarURL(context, resPath);
-
- final String contents = GeckoJarReader.getText(context, jarURL);
- if (contents == null) {
- // GeckoJarReader logs and swallows exceptions.
- return null;
- }
-
- try {
- final JSONObject multilocale = new JSONObject(contents);
- final JSONArray locales = multilocale.getJSONArray("locales");
- if (locales == null) {
- Log.e(LOG_TAG, "No 'locales' array in multilocales.json!");
- return null;
- }
-
- final Set<String> out = new HashSet<String>(locales.length());
- for (int i = 0; i < locales.length(); ++i) {
- // If any item in the array is invalid, this will throw,
- // and the entire clause will fail, being caught below
- // and returning null.
- out.add(locales.getString(i));
- }
-
- return out;
- } catch (JSONException e) {
- Log.e(LOG_TAG, "Unable to parse multilocale.json.", e);
- return null;
- }
- }
-
- /**
- * @return the single default locale baked into this application.
- * Applicable when there is no multilocale.json present.
- */
- @SuppressWarnings("static-method")
- public String getFallbackLocaleTag() {
- return FALLBACK_LOCALE_TAG;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/ChromeCastDisplay.java b/mobile/android/base/java/org/mozilla/gecko/ChromeCastDisplay.java
deleted file mode 100644
index cff6ea643..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/ChromeCastDisplay.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * vim: ts=4 sw=4 expandtab:
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.json.JSONObject;
-import org.json.JSONException;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.EventCallback;
-
-import com.google.android.gms.cast.CastDevice;
-import com.google.android.gms.cast.CastRemoteDisplayLocalService;
-import com.google.android.gms.common.ConnectionResult;
-import com.google.android.gms.common.GooglePlayServicesUtil;
-import com.google.android.gms.common.api.Status;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.support.v7.media.MediaRouter.RouteInfo;
-import android.util.Log;
-
-public class ChromeCastDisplay implements GeckoPresentationDisplay {
-
- static final String REMOTE_DISPLAY_APP_ID = "4574A331";
-
- private static final String LOGTAG = "GeckoChromeCastDisplay";
- private final Context context;
- private final RouteInfo route;
- private CastDevice castDevice;
-
- public ChromeCastDisplay(Context context, RouteInfo route) {
- int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(context);
- if (status != ConnectionResult.SUCCESS) {
- throw new IllegalStateException("Play services are required for Chromecast support (got status code " + status + ")");
- }
-
- this.context = context;
- this.route = route;
- this.castDevice = CastDevice.getFromBundle(route.getExtras());
- }
-
- public JSONObject toJSON() {
- final JSONObject obj = new JSONObject();
- try {
- if (castDevice == null) {
- return null;
- }
- obj.put("uuid", route.getId());
- obj.put("friendlyName", castDevice.getFriendlyName());
- obj.put("type", "chromecast");
- } catch (JSONException ex) {
- Log.d(LOGTAG, "Error building route", ex);
- }
-
- return obj;
- }
-
- @Override
- public void start(final EventCallback callback) {
-
- if (CastRemoteDisplayLocalService.getInstance() != null) {
- Log.d(LOGTAG, "CastRemoteDisplayLocalService already existed.");
- GeckoAppShell.notifyObservers("presentation-view-ready", route.getId());
- callback.sendSuccess("Succeed to start presentation.");
- return;
- }
-
- Intent intent = new Intent(context, RemotePresentationService.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
- PendingIntent notificationPendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
-
- CastRemoteDisplayLocalService.NotificationSettings settings =
- new CastRemoteDisplayLocalService.NotificationSettings.Builder()
- .setNotificationPendingIntent(notificationPendingIntent).build();
-
- CastRemoteDisplayLocalService.startService(
- context,
- RemotePresentationService.class,
- REMOTE_DISPLAY_APP_ID,
- castDevice,
- settings,
- new CastRemoteDisplayLocalService.Callbacks() {
- @Override
- public void onServiceCreated(CastRemoteDisplayLocalService service) {
- ((RemotePresentationService) service).setDeviceId(route.getId());
- }
-
- @Override
- public void onRemoteDisplaySessionStarted(CastRemoteDisplayLocalService service) {
- Log.d(LOGTAG, "Remote presentation launched!");
- callback.sendSuccess("Succeed to start presentation.");
- }
-
- @Override
- public void onRemoteDisplaySessionError(Status errorReason) {
- int code = errorReason.getStatusCode();
- callback.sendError("Fail to start presentation. Error code: " + code);
- }
- });
- }
-
- @Override
- public void stop(EventCallback callback) {
- CastRemoteDisplayLocalService.stopService();
- callback.sendSuccess("Succeed to stop presentation.");
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/ChromeCastPlayer.java b/mobile/android/base/java/org/mozilla/gecko/ChromeCastPlayer.java
deleted file mode 100644
index c531b8c37..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/ChromeCastPlayer.java
+++ /dev/null
@@ -1,509 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import java.io.IOException;
-
-import org.mozilla.gecko.util.EventCallback;
-import org.json.JSONObject;
-import org.json.JSONException;
-
-import com.google.android.gms.cast.Cast.MessageReceivedCallback;
-import com.google.android.gms.cast.ApplicationMetadata;
-import com.google.android.gms.cast.Cast;
-import com.google.android.gms.cast.Cast.ApplicationConnectionResult;
-import com.google.android.gms.cast.CastDevice;
-import com.google.android.gms.cast.CastMediaControlIntent;
-import com.google.android.gms.cast.MediaInfo;
-import com.google.android.gms.cast.MediaMetadata;
-import com.google.android.gms.cast.MediaStatus;
-import com.google.android.gms.cast.RemoteMediaPlayer;
-import com.google.android.gms.cast.RemoteMediaPlayer.MediaChannelResult;
-import com.google.android.gms.common.ConnectionResult;
-import com.google.android.gms.common.api.GoogleApiClient;
-import com.google.android.gms.common.api.ResultCallback;
-import com.google.android.gms.common.api.Status;
-import com.google.android.gms.common.GooglePlayServicesUtil;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.support.v7.media.MediaRouter.RouteInfo;
-import android.util.Log;
-
-/* Implementation of GeckoMediaPlayer for talking to ChromeCast devices */
-class ChromeCastPlayer implements GeckoMediaPlayer {
- private static final boolean SHOW_DEBUG = false;
-
- static final String MIRROR_RECEIVER_APP_ID = "08FF1091";
-
- private final Context context;
- private final RouteInfo route;
- private GoogleApiClient apiClient;
- private RemoteMediaPlayer remoteMediaPlayer;
- private final boolean canMirror;
- private String mSessionId;
- private MirrorChannel mMirrorChannel;
- private boolean mApplicationStarted = false;
-
- // EventCallback which is actually a GeckoEventCallback is sometimes being invoked more
- // than once. That causes the IllegalStateException to be thrown. To prevent a crash,
- // catch the exception and report it as an error to the log.
- private static void sendSuccess(final EventCallback callback, final String msg) {
- try {
- callback.sendSuccess(msg);
- } catch (final IllegalStateException e) {
- Log.e(LOGTAG, "Attempting to invoke callback.sendSuccess more than once.", e);
- }
- }
-
- private static void sendError(final EventCallback callback, final String msg) {
- try {
- callback.sendError(msg);
- } catch (final IllegalStateException e) {
- Log.e(LOGTAG, "Attempting to invoke callback.sendError more than once.", e);
- }
- }
-
- // Callback to start playback of a url on a remote device
- private class VideoPlayCallback implements ResultCallback<ApplicationConnectionResult>,
- RemoteMediaPlayer.OnStatusUpdatedListener,
- RemoteMediaPlayer.OnMetadataUpdatedListener {
- private final String url;
- private final String type;
- private final String title;
- private final EventCallback callback;
-
- public VideoPlayCallback(String url, String type, String title, EventCallback callback) {
- this.url = url;
- this.type = type;
- this.title = title;
- this.callback = callback;
- }
-
- @Override
- public void onStatusUpdated() {
- MediaStatus mediaStatus = remoteMediaPlayer.getMediaStatus();
-
- switch (mediaStatus.getPlayerState()) {
- case MediaStatus.PLAYER_STATE_PLAYING:
- GeckoAppShell.notifyObservers("MediaPlayer:Playing", null);
- break;
- case MediaStatus.PLAYER_STATE_PAUSED:
- GeckoAppShell.notifyObservers("MediaPlayer:Paused", null);
- break;
- case MediaStatus.PLAYER_STATE_IDLE:
- // TODO: Do we want to shutdown when there are errors?
- if (mediaStatus.getIdleReason() == MediaStatus.IDLE_REASON_FINISHED) {
- GeckoAppShell.notifyObservers("Casting:Stop", null);
- }
- break;
- default:
- // TODO: Do we need to handle other status such as buffering / unknown?
- break;
- }
- }
-
- @Override
- public void onMetadataUpdated() { }
-
- @Override
- public void onResult(ApplicationConnectionResult result) {
- Status status = result.getStatus();
- debug("ApplicationConnectionResultCallback.onResult: statusCode" + status.getStatusCode());
- if (status.isSuccess()) {
- remoteMediaPlayer = new RemoteMediaPlayer();
- remoteMediaPlayer.setOnStatusUpdatedListener(this);
- remoteMediaPlayer.setOnMetadataUpdatedListener(this);
- mSessionId = result.getSessionId();
- if (!verifySession(callback)) {
- return;
- }
-
- try {
- Cast.CastApi.setMessageReceivedCallbacks(apiClient, remoteMediaPlayer.getNamespace(), remoteMediaPlayer);
- } catch (IOException e) {
- debug("Exception while creating media channel", e);
- }
-
- startPlayback();
- } else {
- sendError(callback, status.toString());
- }
- }
-
- private void startPlayback() {
- MediaMetadata mediaMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
- mediaMetadata.putString(MediaMetadata.KEY_TITLE, title);
- MediaInfo mediaInfo = new MediaInfo.Builder(url)
- .setContentType(type)
- .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
- .setMetadata(mediaMetadata)
- .build();
- try {
- remoteMediaPlayer.load(apiClient, mediaInfo, true).setResultCallback(new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() {
- @Override
- public void onResult(MediaChannelResult result) {
- if (result.getStatus().isSuccess()) {
- sendSuccess(callback, null);
- debug("Media loaded successfully");
- return;
- }
-
- debug("Media load failed " + result.getStatus());
- sendError(callback, result.getStatus().toString());
- }
- });
-
- return;
- } catch (IllegalStateException e) {
- debug("Problem occurred with media during loading", e);
- } catch (Exception e) {
- debug("Problem opening media during loading", e);
- }
-
- sendError(callback, "");
- }
- }
-
- public ChromeCastPlayer(Context context, RouteInfo route) {
- int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(context);
- if (status != ConnectionResult.SUCCESS) {
- throw new IllegalStateException("Play services are required for Chromecast support (got status code " + status + ")");
- }
-
- this.context = context;
- this.route = route;
- this.canMirror = route.supportsControlCategory(CastMediaControlIntent.categoryForCast(MIRROR_RECEIVER_APP_ID));
- }
-
- /**
- * This dumps everything we can find about the device into JSON. This will hopefully make it
- * easier to filter out duplicate devices from different sources in JS.
- * Returns null if the device can't be found.
- */
- @Override
- public JSONObject toJSON() {
- final JSONObject obj = new JSONObject();
- try {
- final CastDevice device = CastDevice.getFromBundle(route.getExtras());
- if (device == null) {
- return null;
- }
-
- obj.put("uuid", route.getId());
- obj.put("version", device.getDeviceVersion());
- obj.put("friendlyName", device.getFriendlyName());
- obj.put("location", device.getIpAddress().toString());
- obj.put("modelName", device.getModelName());
- obj.put("mirror", canMirror);
- // For now we just assume all of these are Google devices
- obj.put("manufacturer", "Google Inc.");
- } catch (JSONException ex) {
- debug("Error building route", ex);
- }
-
- return obj;
- }
-
- @Override
- public void load(final String title, final String url, final String type, final EventCallback callback) {
- final CastDevice device = CastDevice.getFromBundle(route.getExtras());
- Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions.builder(device, new Cast.Listener() {
- @Override
- public void onApplicationStatusChanged() { }
-
- @Override
- public void onVolumeChanged() { }
-
- @Override
- public void onApplicationDisconnected(int errorCode) { }
- });
-
- apiClient = new GoogleApiClient.Builder(context)
- .addApi(Cast.API, apiOptionsBuilder.build())
- .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
- @Override
- public void onConnected(Bundle connectionHint) {
- // Sometimes apiClient is null here. See bug 1061032
- if (apiClient != null && !apiClient.isConnected()) {
- debug("Connection failed");
- sendError(callback, "Not connected");
- return;
- }
-
- // Launch the media player app and launch this url once its loaded
- try {
- Cast.CastApi.launchApplication(apiClient, CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID, true)
- .setResultCallback(new VideoPlayCallback(url, type, title, callback));
- } catch (Exception e) {
- debug("Failed to launch application", e);
- }
- }
-
- @Override
- public void onConnectionSuspended(int cause) {
- debug("suspended");
- }
- }).build();
-
- apiClient.connect();
- }
-
- @Override
- public void start(final EventCallback callback) {
- // Nothing to be done here
- sendSuccess(callback, null);
- }
-
- @Override
- public void stop(final EventCallback callback) {
- // Nothing to be done here
- sendSuccess(callback, null);
- }
-
- public boolean verifySession(final EventCallback callback) {
- String msg = null;
- if (apiClient == null || !apiClient.isConnected()) {
- msg = "Not connected";
- }
-
- if (mSessionId == null) {
- msg = "No session";
- }
-
- if (msg != null) {
- debug(msg);
- if (callback != null) {
- sendError(callback, msg);
- }
- return false;
- }
-
- return true;
- }
-
- @Override
- public void play(final EventCallback callback) {
- if (!verifySession(callback)) {
- return;
- }
-
- try {
- remoteMediaPlayer.play(apiClient).setResultCallback(new ResultCallback<MediaChannelResult>() {
- @Override
- public void onResult(MediaChannelResult result) {
- Status status = result.getStatus();
- if (!status.isSuccess()) {
- debug("Unable to play: " + status.getStatusCode());
- sendError(callback, status.toString());
- } else {
- sendSuccess(callback, null);
- }
- }
- });
- } catch (IllegalStateException ex) {
- // The media player may throw if the session has been killed. For now, we're just catching this here.
- sendError(callback, "Error playing");
- }
- }
-
- @Override
- public void pause(final EventCallback callback) {
- if (!verifySession(callback)) {
- return;
- }
-
- try {
- remoteMediaPlayer.pause(apiClient).setResultCallback(new ResultCallback<MediaChannelResult>() {
- @Override
- public void onResult(MediaChannelResult result) {
- Status status = result.getStatus();
- if (!status.isSuccess()) {
- debug("Unable to pause: " + status.getStatusCode());
- sendError(callback, status.toString());
- } else {
- sendSuccess(callback, null);
- }
- }
- });
- } catch (IllegalStateException ex) {
- // The media player may throw if the session has been killed. For now, we're just catching this here.
- sendError(callback, "Error pausing");
- }
- }
-
- @Override
- public void end(final EventCallback callback) {
- if (!verifySession(callback)) {
- return;
- }
-
- try {
- Cast.CastApi.stopApplication(apiClient).setResultCallback(new ResultCallback<Status>() {
- @Override
- public void onResult(Status result) {
- if (result.isSuccess()) {
- try {
- Cast.CastApi.removeMessageReceivedCallbacks(apiClient, remoteMediaPlayer.getNamespace());
- remoteMediaPlayer = null;
- mSessionId = null;
- apiClient.disconnect();
- apiClient = null;
-
- if (callback != null) {
- sendSuccess(callback, null);
- }
-
- return;
- } catch (Exception ex) {
- debug("Error ending", ex);
- }
- }
-
- if (callback != null) {
- sendError(callback, result.getStatus().toString());
- }
- }
- });
- } catch (IllegalStateException ex) {
- // The media player may throw if the session has been killed. For now, we're just catching this here.
- sendError(callback, "Error stopping");
- }
- }
-
- class MirrorChannel implements MessageReceivedCallback {
- /**
- * @return custom namespace
- */
- public String getNamespace() {
- return "urn:x-cast:org.mozilla.mirror";
- }
-
- /*
- * Receive message from the receiver app
- */
- @Override
- public void onMessageReceived(CastDevice castDevice, String namespace,
- String message) {
- GeckoAppShell.notifyObservers("MediaPlayer:Response", message);
- }
-
- public void sendMessage(String message) {
- if (apiClient != null && mMirrorChannel != null) {
- try {
- Cast.CastApi.sendMessage(apiClient, mMirrorChannel.getNamespace(), message)
- .setResultCallback(
- new ResultCallback<Status>() {
- @Override
- public void onResult(Status result) {
- }
- });
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception while sending message", e);
- }
- }
- }
- }
- private class MirrorCallback implements ResultCallback<ApplicationConnectionResult> {
- final EventCallback callback;
- MirrorCallback(final EventCallback callback) {
- this.callback = callback;
- }
-
-
- @Override
- public void onResult(ApplicationConnectionResult result) {
- Status status = result.getStatus();
- if (status.isSuccess()) {
- ApplicationMetadata applicationMetadata = result.getApplicationMetadata();
- mSessionId = result.getSessionId();
- String applicationStatus = result.getApplicationStatus();
- boolean wasLaunched = result.getWasLaunched();
- mApplicationStarted = true;
-
- // Create the custom message
- // channel
- mMirrorChannel = new MirrorChannel();
- try {
- Cast.CastApi.setMessageReceivedCallbacks(apiClient,
- mMirrorChannel
- .getNamespace(),
- mMirrorChannel);
- sendSuccess(callback, null);
- } catch (IOException e) {
- Log.e(LOGTAG, "Exception while creating channel", e);
- }
-
- GeckoAppShell.notifyObservers("Casting:Mirror", route.getId());
- } else {
- sendError(callback, status.toString());
- }
- }
- }
-
- @Override
- public void message(String msg, final EventCallback callback) {
- if (mMirrorChannel != null) {
- mMirrorChannel.sendMessage(msg);
- }
- }
-
- @Override
- public void mirror(final EventCallback callback) {
- final CastDevice device = CastDevice.getFromBundle(route.getExtras());
- Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions.builder(device, new Cast.Listener() {
- @Override
- public void onApplicationStatusChanged() { }
-
- @Override
- public void onVolumeChanged() { }
-
- @Override
- public void onApplicationDisconnected(int errorCode) { }
- });
-
- apiClient = new GoogleApiClient.Builder(context)
- .addApi(Cast.API, apiOptionsBuilder.build())
- .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
- @Override
- public void onConnected(Bundle connectionHint) {
- // Sometimes apiClient is null here. See bug 1061032
- if (apiClient == null || !apiClient.isConnected()) {
- return;
- }
-
- // Launch the media player app and launch this url once its loaded
- try {
- Cast.CastApi.launchApplication(apiClient, MIRROR_RECEIVER_APP_ID, true)
- .setResultCallback(new MirrorCallback(callback));
- } catch (Exception e) {
- debug("Failed to launch application", e);
- }
- }
-
- @Override
- public void onConnectionSuspended(int cause) {
- debug("suspended");
- }
- }).build();
-
- apiClient.connect();
- }
-
- private static final String LOGTAG = "GeckoChromeCastPlayer";
- private void debug(String msg, Exception e) {
- if (SHOW_DEBUG) {
- Log.e(LOGTAG, msg, e);
- }
- }
-
- private void debug(String msg) {
- if (SHOW_DEBUG) {
- Log.d(LOGTAG, msg);
- }
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/CrashReporter.java b/mobile/android/base/java/org/mozilla/gecko/CrashReporter.java
deleted file mode 100644
index ce2384a4d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/CrashReporter.java
+++ /dev/null
@@ -1,480 +0,0 @@
-/* -*- Mode: Java; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.InputStreamReader;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.nio.channels.Channels;
-import java.nio.channels.FileChannel;
-import java.util.zip.GZIPOutputStream;
-
-import org.mozilla.gecko.AppConstants.Versions;
-
-import android.annotation.SuppressLint;
-import android.app.AlertDialog;
-import android.app.ProgressDialog;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v7.app.AppCompatActivity;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
-import android.widget.EditText;
-
-@SuppressLint("Registered") // This activity is only registered in the manifest if MOZ_CRASHREPORTER is set
-public class CrashReporter extends AppCompatActivity
-{
- private static final String LOGTAG = "GeckoCrashReporter";
-
- private static final String PASSED_MINI_DUMP_KEY = "minidumpPath";
- private static final String PASSED_MINI_DUMP_SUCCESS_KEY = "minidumpSuccess";
- private static final String MINI_DUMP_PATH_KEY = "upload_file_minidump";
- private static final String PAGE_URL_KEY = "URL";
- private static final String NOTES_KEY = "Notes";
- private static final String SERVER_URL_KEY = "ServerURL";
-
- private static final String CRASH_REPORT_SUFFIX = "/mozilla/Crash Reports/";
- private static final String PENDING_SUFFIX = CRASH_REPORT_SUFFIX + "pending";
- private static final String SUBMITTED_SUFFIX = CRASH_REPORT_SUFFIX + "submitted";
-
- private static final String PREFS_SEND_REPORT = "sendReport";
- private static final String PREFS_INCLUDE_URL = "includeUrl";
- private static final String PREFS_ALLOW_CONTACT = "allowContact";
- private static final String PREFS_CONTACT_EMAIL = "contactEmail";
-
- private Handler mHandler;
- private ProgressDialog mProgressDialog;
- private File mPendingMinidumpFile;
- private File mPendingExtrasFile;
- private HashMap<String, String> mExtrasStringMap;
- private boolean mMinidumpSucceeded;
-
- private boolean moveFile(File inFile, File outFile) {
- Log.i(LOGTAG, "moving " + inFile + " to " + outFile);
- if (inFile.renameTo(outFile))
- return true;
- try {
- outFile.createNewFile();
- Log.i(LOGTAG, "couldn't rename minidump file");
- // so copy it instead
- FileChannel inChannel = new FileInputStream(inFile).getChannel();
- FileChannel outChannel = new FileOutputStream(outFile).getChannel();
- long transferred = inChannel.transferTo(0, inChannel.size(), outChannel);
- inChannel.close();
- outChannel.close();
-
- if (transferred > 0)
- inFile.delete();
- } catch (Exception e) {
- Log.e(LOGTAG, "exception while copying minidump file: ", e);
- return false;
- }
- return true;
- }
-
- private void doFinish() {
- if (mHandler != null) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- finish();
- }
- });
- }
- }
-
- @Override
- public void finish() {
- try {
- if (mProgressDialog.isShowing()) {
- mProgressDialog.dismiss();
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "exception while closing progress dialog: ", e);
- }
- super.finish();
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- // mHandler is created here so runnables can be run on the main thread
- mHandler = new Handler();
- setContentView(R.layout.crash_reporter);
- mProgressDialog = new ProgressDialog(this);
- mProgressDialog.setMessage(getString(R.string.sending_crash_report));
-
- mMinidumpSucceeded = getIntent().getBooleanExtra(PASSED_MINI_DUMP_SUCCESS_KEY, false);
- if (!mMinidumpSucceeded) {
- Log.i(LOGTAG, "Failed to get minidump.");
- }
- String passedMinidumpPath = getIntent().getStringExtra(PASSED_MINI_DUMP_KEY);
- File passedMinidumpFile = new File(passedMinidumpPath);
- File pendingDir = new File(getFilesDir(), PENDING_SUFFIX);
- pendingDir.mkdirs();
- mPendingMinidumpFile = new File(pendingDir, passedMinidumpFile.getName());
- moveFile(passedMinidumpFile, mPendingMinidumpFile);
-
- File extrasFile = new File(passedMinidumpPath.replaceAll("\\.dmp", ".extra"));
- mPendingExtrasFile = new File(pendingDir, extrasFile.getName());
- moveFile(extrasFile, mPendingExtrasFile);
-
- mExtrasStringMap = new HashMap<String, String>();
- readStringsFromFile(mPendingExtrasFile.getPath(), mExtrasStringMap);
-
- // Notify GeckoApp that we've crashed, so it can react appropriately during the next start.
- try {
- File crashFlag = new File(GeckoProfileDirectories.getMozillaDirectory(this), "CRASHED");
- crashFlag.createNewFile();
- } catch (GeckoProfileDirectories.NoMozillaDirectoryException | IOException e) {
- Log.e(LOGTAG, "Cannot set crash flag: ", e);
- }
-
- final CheckBox allowContactCheckBox = (CheckBox) findViewById(R.id.allow_contact);
- final CheckBox includeUrlCheckBox = (CheckBox) findViewById(R.id.include_url);
- final CheckBox sendReportCheckBox = (CheckBox) findViewById(R.id.send_report);
- final EditText commentsEditText = (EditText) findViewById(R.id.comment);
- final EditText emailEditText = (EditText) findViewById(R.id.email);
-
- // Load CrashReporter preferences to avoid redundant user input.
- SharedPreferences prefs = GeckoSharedPrefs.forCrashReporter(this);
- final boolean sendReport = prefs.getBoolean(PREFS_SEND_REPORT, true);
- final boolean includeUrl = prefs.getBoolean(PREFS_INCLUDE_URL, false);
- final boolean allowContact = prefs.getBoolean(PREFS_ALLOW_CONTACT, false);
- final String contactEmail = prefs.getString(PREFS_CONTACT_EMAIL, "");
-
- allowContactCheckBox.setChecked(allowContact);
- includeUrlCheckBox.setChecked(includeUrl);
- sendReportCheckBox.setChecked(sendReport);
- emailEditText.setText(contactEmail);
-
- sendReportCheckBox.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton checkbox, boolean isChecked) {
- commentsEditText.setEnabled(isChecked);
- commentsEditText.requestFocus();
-
- includeUrlCheckBox.setEnabled(isChecked);
- allowContactCheckBox.setEnabled(isChecked);
- emailEditText.setEnabled(isChecked && allowContactCheckBox.isChecked());
- }
- });
-
- allowContactCheckBox.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton checkbox, boolean isChecked) {
- // We need to check isEnabled() here because this listener is
- // fired on rotation -- even when the checkbox is disabled.
- emailEditText.setEnabled(checkbox.isEnabled() && isChecked);
- emailEditText.requestFocus();
- }
- });
-
- emailEditText.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Even if the email EditText is disabled, allow it to be
- // clicked and focused.
- if (sendReportCheckBox.isChecked() && !v.isEnabled()) {
- allowContactCheckBox.setChecked(true);
- v.setEnabled(true);
- v.requestFocus();
- }
- }
- });
- }
-
- @Override
- public void onBackPressed() {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setMessage(R.string.crash_closing_alert);
- builder.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- });
- builder.setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- CrashReporter.this.finish();
- }
- });
- builder.show();
- }
-
- private void backgroundSendReport() {
- final CheckBox sendReportCheckbox = (CheckBox) findViewById(R.id.send_report);
- if (!sendReportCheckbox.isChecked()) {
- doFinish();
- return;
- }
-
- // Persist settings to avoid redundant user input.
- savePrefs();
-
- mProgressDialog.show();
- new Thread(new Runnable() {
- @Override
- public void run() {
- sendReport(mPendingMinidumpFile, mExtrasStringMap, mPendingExtrasFile);
- }
- }, "CrashReporter Thread").start();
- }
-
- private void savePrefs() {
- SharedPreferences.Editor editor = GeckoSharedPrefs.forCrashReporter(this).edit();
-
- final boolean allowContact = ((CheckBox) findViewById(R.id.allow_contact)).isChecked();
- final boolean includeUrl = ((CheckBox) findViewById(R.id.include_url)).isChecked();
- final boolean sendReport = ((CheckBox) findViewById(R.id.send_report)).isChecked();
- final String contactEmail = ((EditText) findViewById(R.id.email)).getText().toString();
-
- editor.putBoolean(PREFS_ALLOW_CONTACT, allowContact);
- editor.putBoolean(PREFS_INCLUDE_URL, includeUrl);
- editor.putBoolean(PREFS_SEND_REPORT, sendReport);
- editor.putString(PREFS_CONTACT_EMAIL, contactEmail);
-
- // A slight performance improvement via async apply() vs. blocking on commit().
- editor.apply();
- }
-
- public void onCloseClick(View v) { // bound via crash_reporter.xml
- backgroundSendReport();
- }
-
- public void onRestartClick(View v) { // bound via crash_reporter.xml
- doRestart();
- backgroundSendReport();
- }
-
- private boolean readStringsFromFile(String filePath, Map<String, String> stringMap) {
- try {
- BufferedReader reader = new BufferedReader(new FileReader(filePath));
- return readStringsFromReader(reader, stringMap);
- } catch (Exception e) {
- Log.e(LOGTAG, "exception while reading strings: ", e);
- return false;
- }
- }
-
- private boolean readStringsFromReader(BufferedReader reader, Map<String, String> stringMap) throws IOException {
- String line;
- while ((line = reader.readLine()) != null) {
- int equalsPos = -1;
- if ((equalsPos = line.indexOf('=')) != -1) {
- String key = line.substring(0, equalsPos);
- String val = unescape(line.substring(equalsPos + 1));
- stringMap.put(key, val);
- }
- }
- reader.close();
- return true;
- }
-
- private String generateBoundary() {
- // Generate some random numbers to fill out the boundary
- int r0 = (int)(Integer.MAX_VALUE * Math.random());
- int r1 = (int)(Integer.MAX_VALUE * Math.random());
- return String.format("---------------------------%08X%08X", r0, r1);
- }
-
- private void sendPart(OutputStream os, String boundary, String name, String data) {
- try {
- os.write(("--" + boundary + "\r\n" +
- "Content-Disposition: form-data; name=\"" + name + "\"\r\n" +
- "\r\n" +
- data + "\r\n"
- ).getBytes());
- } catch (Exception ex) {
- Log.e(LOGTAG, "Exception when sending \"" + name + "\"", ex);
- }
- }
-
- private void sendFile(OutputStream os, String boundary, String name, File file) throws IOException {
- os.write(("--" + boundary + "\r\n" +
- "Content-Disposition: form-data; name=\"" + name + "\"; " +
- "filename=\"" + file.getName() + "\"\r\n" +
- "Content-Type: application/octet-stream\r\n" +
- "\r\n"
- ).getBytes());
- FileChannel fc = new FileInputStream(file).getChannel();
- fc.transferTo(0, fc.size(), Channels.newChannel(os));
- fc.close();
- }
-
- private String readLogcat() {
- final String crashReporterProc = " " + android.os.Process.myPid() + ' ';
- BufferedReader br = null;
- try {
- // get at most the last 400 lines of logcat
- Process proc = Runtime.getRuntime().exec(new String[] {
- "logcat", "-v", "threadtime", "-t", "400", "-d", "*:D"
- });
- StringBuilder sb = new StringBuilder();
- br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
- for (String s = br.readLine(); s != null; s = br.readLine()) {
- if (s.contains(crashReporterProc)) {
- // Don't include logs from the crash reporter's process.
- break;
- }
- sb.append(s).append('\n');
- }
- return sb.toString();
- } catch (Exception e) {
- return "Unable to get logcat: " + e.toString();
- } finally {
- if (br != null) {
- try {
- br.close();
- } catch (Exception e) {
- // ignore
- }
- }
- }
- }
-
- private void sendReport(File minidumpFile, Map<String, String> extras, File extrasFile) {
- Log.i(LOGTAG, "sendReport: " + minidumpFile.getPath());
- final CheckBox includeURLCheckbox = (CheckBox) findViewById(R.id.include_url);
-
- String spec = extras.get(SERVER_URL_KEY);
- if (spec == null) {
- doFinish();
- return;
- }
-
- Log.i(LOGTAG, "server url: " + spec);
- try {
- URL url = new URL(spec);
- HttpURLConnection conn = (HttpURLConnection)url.openConnection();
- conn.setRequestMethod("POST");
- String boundary = generateBoundary();
- conn.setDoOutput(true);
- conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
- conn.setRequestProperty("Content-Encoding", "gzip");
-
- OutputStream os = new GZIPOutputStream(conn.getOutputStream());
- for (String key : extras.keySet()) {
- if (key.equals(PAGE_URL_KEY)) {
- if (includeURLCheckbox.isChecked())
- sendPart(os, boundary, key, extras.get(key));
- } else if (!key.equals(SERVER_URL_KEY) && !key.equals(NOTES_KEY)) {
- sendPart(os, boundary, key, extras.get(key));
- }
- }
-
- // Add some extra information to notes so its displayed by
- // crash-stats.mozilla.org. Remove this when bug 607942 is fixed.
- StringBuilder sb = new StringBuilder();
- sb.append(extras.containsKey(NOTES_KEY) ? extras.get(NOTES_KEY) + "\n" : "");
- if (AppConstants.MOZ_MIN_CPU_VERSION < 7) {
- sb.append("nothumb Build\n");
- }
- sb.append(Build.MANUFACTURER).append(' ')
- .append(Build.MODEL).append('\n')
- .append(Build.FINGERPRINT);
- sendPart(os, boundary, NOTES_KEY, sb.toString());
-
- sendPart(os, boundary, "Min_ARM_Version", Integer.toString(AppConstants.MOZ_MIN_CPU_VERSION));
- sendPart(os, boundary, "Android_Manufacturer", Build.MANUFACTURER);
- sendPart(os, boundary, "Android_Model", Build.MODEL);
- sendPart(os, boundary, "Android_Board", Build.BOARD);
- sendPart(os, boundary, "Android_Brand", Build.BRAND);
- sendPart(os, boundary, "Android_Device", Build.DEVICE);
- sendPart(os, boundary, "Android_Display", Build.DISPLAY);
- sendPart(os, boundary, "Android_Fingerprint", Build.FINGERPRINT);
- sendPart(os, boundary, "Android_APP_ABI", AppConstants.MOZ_APP_ABI);
- sendPart(os, boundary, "Android_CPU_ABI", Build.CPU_ABI);
- sendPart(os, boundary, "Android_MIN_SDK", Integer.toString(AppConstants.Versions.MIN_SDK_VERSION));
- sendPart(os, boundary, "Android_MAX_SDK", Integer.toString(AppConstants.Versions.MAX_SDK_VERSION));
- try {
- sendPart(os, boundary, "Android_CPU_ABI2", Build.CPU_ABI2);
- sendPart(os, boundary, "Android_Hardware", Build.HARDWARE);
- } catch (Exception ex) {
- Log.e(LOGTAG, "Exception while sending SDK version 8 keys", ex);
- }
- sendPart(os, boundary, "Android_Version", Build.VERSION.SDK_INT + " (" + Build.VERSION.CODENAME + ")");
- if (Versions.feature16Plus && includeURLCheckbox.isChecked()) {
- sendPart(os, boundary, "Android_Logcat", readLogcat());
- }
-
- String comment = ((EditText) findViewById(R.id.comment)).getText().toString();
- if (!TextUtils.isEmpty(comment)) {
- sendPart(os, boundary, "Comments", comment);
- }
-
- if (((CheckBox) findViewById(R.id.allow_contact)).isChecked()) {
- String email = ((EditText) findViewById(R.id.email)).getText().toString();
- sendPart(os, boundary, "Email", email);
- }
-
- sendPart(os, boundary, PASSED_MINI_DUMP_SUCCESS_KEY, mMinidumpSucceeded ? "True" : "False");
- sendFile(os, boundary, MINI_DUMP_PATH_KEY, minidumpFile);
- os.write(("\r\n--" + boundary + "--\r\n").getBytes());
- os.flush();
- os.close();
- BufferedReader br = new BufferedReader(
- new InputStreamReader(conn.getInputStream()));
- HashMap<String, String> responseMap = new HashMap<String, String>();
- readStringsFromReader(br, responseMap);
-
- if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
- File submittedDir = new File(getFilesDir(),
- SUBMITTED_SUFFIX);
- submittedDir.mkdirs();
- minidumpFile.delete();
- extrasFile.delete();
- String crashid = responseMap.get("CrashID");
- File file = new File(submittedDir, crashid + ".txt");
- FileOutputStream fos = new FileOutputStream(file);
- fos.write("Crash ID: ".getBytes());
- fos.write(crashid.getBytes());
- fos.close();
- } else {
- Log.i(LOGTAG, "Received failure HTTP response code from server: " + conn.getResponseCode());
- }
- } catch (IOException e) {
- Log.e(LOGTAG, "exception during send: ", e);
- }
-
- doFinish();
- }
-
- private void doRestart() {
- try {
- String action = "android.intent.action.MAIN";
- Intent intent = new Intent(action);
- intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
- AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
- intent.putExtra("didRestart", true);
- Log.i(LOGTAG, intent.toString());
- startActivity(intent);
- } catch (Exception e) {
- Log.e(LOGTAG, "error while trying to restart", e);
- }
- }
-
- private String unescape(String string) {
- return string.replaceAll("\\\\\\\\", "\\").replaceAll("\\\\n", "\n").replaceAll("\\\\t", "\t");
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/CustomEditText.java b/mobile/android/base/java/org/mozilla/gecko/CustomEditText.java
deleted file mode 100644
index 98274b752..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/CustomEditText.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.widget.themed.ThemedEditText;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.View;
-
-public class CustomEditText extends ThemedEditText {
- private OnKeyPreImeListener mOnKeyPreImeListener;
- private OnSelectionChangedListener mOnSelectionChangedListener;
- private OnWindowFocusChangeListener mOnWindowFocusChangeListener;
- private int mHighlightColor;
-
- public CustomEditText(Context context, AttributeSet attrs) {
- super(context, attrs);
- setPrivateMode(false); // Initialize mHighlightColor.
- }
-
- public interface OnKeyPreImeListener {
- public boolean onKeyPreIme(View v, int keyCode, KeyEvent event);
- }
-
- public void setOnKeyPreImeListener(OnKeyPreImeListener listener) {
- mOnKeyPreImeListener = listener;
- }
-
- @Override
- public boolean onKeyPreIme(int keyCode, KeyEvent event) {
- if (mOnKeyPreImeListener != null)
- return mOnKeyPreImeListener.onKeyPreIme(this, keyCode, event);
-
- return false;
- }
-
- public interface OnSelectionChangedListener {
- public void onSelectionChanged(int selStart, int selEnd);
- }
-
- public void setOnSelectionChangedListener(OnSelectionChangedListener listener) {
- mOnSelectionChangedListener = listener;
- }
-
- @Override
- protected void onSelectionChanged(int selStart, int selEnd) {
- if (mOnSelectionChangedListener != null)
- mOnSelectionChangedListener.onSelectionChanged(selStart, selEnd);
-
- super.onSelectionChanged(selStart, selEnd);
- }
-
- public interface OnWindowFocusChangeListener {
- public void onWindowFocusChanged(boolean hasFocus);
- }
-
- public void setOnWindowFocusChangeListener(OnWindowFocusChangeListener listener) {
- mOnWindowFocusChangeListener = listener;
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
- super.onWindowFocusChanged(hasFocus);
- if (mOnWindowFocusChangeListener != null)
- mOnWindowFocusChangeListener.onWindowFocusChanged(hasFocus);
- }
-
- // Provide a getHighlightColor implementation for API level < 16.
- @Override
- public int getHighlightColor() {
- return mHighlightColor;
- }
-
- @Override
- public void setPrivateMode(boolean isPrivate) {
- super.setPrivateMode(isPrivate);
-
- mHighlightColor = ContextCompat.getColor(getContext(), isPrivate
- ? R.color.url_bar_text_highlight_pb : R.color.fennec_ui_orange);
- // android:textColorHighlight cannot support a ColorStateList.
- setHighlightColor(mHighlightColor);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/DataReportingNotification.java b/mobile/android/base/java/org/mozilla/gecko/DataReportingNotification.java
deleted file mode 100644
index 725c25d6e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/DataReportingNotification.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.graphics.Typeface;
-import android.support.v4.app.NotificationCompat;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.TextUtils;
-import android.text.style.StyleSpan;
-
-public class DataReportingNotification {
-
- private static final String LOGTAG = "DataReportNotification";
-
- public static final String ALERT_NAME_DATAREPORTING_NOTIFICATION = "datareporting-notification";
-
- private static final String PREFS_POLICY_NOTIFIED_TIME = "datareporting.policy.dataSubmissionPolicyNotifiedTime";
- private static final String PREFS_POLICY_VERSION = "datareporting.policy.dataSubmissionPolicyVersion";
- private static final int DATA_REPORTING_VERSION = 2;
-
- public static void checkAndNotifyPolicy(Context context) {
- SharedPreferences dataPrefs = GeckoSharedPrefs.forApp(context);
- final int currentVersion = dataPrefs.getInt(PREFS_POLICY_VERSION, -1);
-
- if (currentVersion < 1) {
- // This is a first run, so notify user about data policy.
- notifyDataPolicy(context, dataPrefs);
-
- // If healthreport is enabled, set default preference value.
- if (AppConstants.MOZ_SERVICES_HEALTHREPORT) {
- SharedPreferences.Editor editor = dataPrefs.edit();
- editor.putBoolean(GeckoPreferences.PREFS_HEALTHREPORT_UPLOAD_ENABLED, true);
- editor.apply();
- }
- return;
- }
-
- if (currentVersion == 1) {
- // Redisplay notification only for Beta because version 2 updates Beta policy and update version.
- if (TextUtils.equals("beta", AppConstants.MOZ_UPDATE_CHANNEL)) {
- notifyDataPolicy(context, dataPrefs);
- } else {
- // Silently update the version.
- SharedPreferences.Editor editor = dataPrefs.edit();
- editor.putInt(PREFS_POLICY_VERSION, DATA_REPORTING_VERSION);
- editor.apply();
- }
- return;
- }
-
- if (currentVersion >= DATA_REPORTING_VERSION) {
- // Do nothing, we're at a current (or future) version.
- return;
- }
- }
-
- /**
- * Launch a notification of the data policy, and record notification time and version.
- */
- public static void notifyDataPolicy(Context context, SharedPreferences sharedPrefs) {
- boolean result = false;
- try {
- // Launch main App to launch Data choices when notification is clicked.
- Intent prefIntent = new Intent(GeckoApp.ACTION_LAUNCH_SETTINGS);
- prefIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
-
- GeckoPreferences.setResourceToOpen(prefIntent, "preferences_privacy");
- prefIntent.putExtra(ALERT_NAME_DATAREPORTING_NOTIFICATION, true);
-
- PendingIntent contentIntent = PendingIntent.getActivity(context, 0, prefIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- final Resources resources = context.getResources();
-
- // Create and send notification.
- String notificationTitle = resources.getString(R.string.datareporting_notification_title);
- String notificationSummary;
- if (Versions.preJB) {
- notificationSummary = resources.getString(R.string.datareporting_notification_action);
- } else {
- // Display partial version of Big Style notification for supporting devices.
- notificationSummary = resources.getString(R.string.datareporting_notification_summary);
- }
- String notificationAction = resources.getString(R.string.datareporting_notification_action);
- String notificationBigSummary = resources.getString(R.string.datareporting_notification_summary);
-
- // Make styled ticker text for display in notification bar.
- String tickerString = resources.getString(R.string.datareporting_notification_ticker_text);
- SpannableString tickerText = new SpannableString(tickerString);
- // Bold the notification title of the ticker text, which is the same string as notificationTitle.
- tickerText.setSpan(new StyleSpan(Typeface.BOLD), 0, notificationTitle.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
-
- Notification notification = new NotificationCompat.Builder(context)
- .setContentTitle(notificationTitle)
- .setContentText(notificationSummary)
- .setSmallIcon(R.drawable.ic_status_logo)
- .setAutoCancel(true)
- .setContentIntent(contentIntent)
- .setStyle(new NotificationCompat.BigTextStyle()
- .bigText(notificationBigSummary))
- .addAction(R.drawable.firefox_settings_alert, notificationAction, contentIntent)
- .setTicker(tickerText)
- .build();
-
- NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- int notificationID = ALERT_NAME_DATAREPORTING_NOTIFICATION.hashCode();
- notificationManager.notify(notificationID, notification);
-
- // Record version and notification time.
- SharedPreferences.Editor editor = sharedPrefs.edit();
- long now = System.currentTimeMillis();
- editor.putLong(PREFS_POLICY_NOTIFIED_TIME, now);
- editor.putInt(PREFS_POLICY_VERSION, DATA_REPORTING_VERSION);
- editor.apply();
- result = true;
- } finally {
- // We want to track any errors, so record notification outcome.
- Telemetry.sendUIEvent(TelemetryContract.Event.POLICY_NOTIFICATION_SUCCESS, result);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/DevToolsAuthHelper.java b/mobile/android/base/java/org/mozilla/gecko/DevToolsAuthHelper.java
deleted file mode 100644
index 44aaa14a0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/DevToolsAuthHelper.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.util.Log;
-import org.mozilla.gecko.util.ActivityResultHandler;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.InputOptionsUtils;
-
-/**
- * Supports the DevTools WiFi debugging authentication flow by invoking a QR decoder.
- */
-public class DevToolsAuthHelper {
-
- private static final String LOGTAG = "GeckoDevToolsAuthHelper";
-
- public static void scan(Context context, final EventCallback callback) {
- final Intent intent = InputOptionsUtils.createQRCodeReaderIntent();
-
- intent.putExtra("PROMPT_MESSAGE", context.getString(R.string.devtools_auth_scan_header));
-
- // Check ahead of time if an activity exists for the intent. This
- // avoids a case where we get both an ActivityNotFoundException *and*
- // an activity result when the activity is missing.
- PackageManager pm = context.getPackageManager();
- if (pm.resolveActivity(intent, 0) == null) {
- Log.w(LOGTAG, "PackageManager can't resolve the activity.");
- callback.sendError("PackageManager can't resolve the activity.");
- return;
- }
-
- ActivityHandlerHelper.startIntent(intent, new ActivityResultHandler() {
- @Override
- public void onActivityResult(int resultCode, Intent intent) {
- if (resultCode == Activity.RESULT_OK) {
- String text = intent.getStringExtra("SCAN_RESULT");
- callback.sendSuccess(text);
- } else {
- callback.sendError(resultCode);
- }
- }
- });
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/DoorHangerPopup.java b/mobile/android/base/java/org/mozilla/gecko/DoorHangerPopup.java
deleted file mode 100644
index 9aa3f96a4..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/DoorHangerPopup.java
+++ /dev/null
@@ -1,361 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import java.util.HashSet;
-
-import android.text.TextUtils;
-import android.widget.PopupWindow;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.json.JSONArray;
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.widget.AnchoredPopup;
-import org.mozilla.gecko.widget.DoorHanger;
-
-import android.content.Context;
-import android.util.Log;
-import android.view.View;
-import org.mozilla.gecko.widget.DoorhangerConfig;
-
-public class DoorHangerPopup extends AnchoredPopup
- implements GeckoEventListener,
- Tabs.OnTabsChangedListener,
- PopupWindow.OnDismissListener,
- DoorHanger.OnButtonClickListener {
- private static final String LOGTAG = "GeckoDoorHangerPopup";
-
- // Stores a set of all active DoorHanger notifications. A DoorHanger is
- // uniquely identified by its tabId and value.
- private final HashSet<DoorHanger> mDoorHangers;
-
- // Whether or not the doorhanger popup is disabled.
- private boolean mDisabled;
-
- public DoorHangerPopup(Context context) {
- super(context);
-
- mDoorHangers = new HashSet<DoorHanger>();
-
- GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
- "Doorhanger:Add",
- "Doorhanger:Remove");
- Tabs.registerOnTabsChangedListener(this);
-
- setOnDismissListener(this);
- }
-
- void destroy() {
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
- "Doorhanger:Add",
- "Doorhanger:Remove");
- Tabs.unregisterOnTabsChangedListener(this);
- }
-
- /**
- * Temporarily disables the doorhanger popup. If the popup is disabled,
- * it will not be shown to the user, but it will continue to process
- * calls to add/remove doorhanger notifications.
- */
- void disable() {
- mDisabled = true;
- updatePopup();
- }
-
- /**
- * Re-enables the doorhanger popup.
- */
- void enable() {
- mDisabled = false;
- updatePopup();
- }
-
- @Override
- public void handleMessage(String event, JSONObject geckoObject) {
- try {
- if (event.equals("Doorhanger:Add")) {
- final DoorhangerConfig config = makeConfigFromJSON(geckoObject);
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- addDoorHanger(config);
- }
- });
- } else if (event.equals("Doorhanger:Remove")) {
- final int tabId = geckoObject.getInt("tabID");
- final String value = geckoObject.getString("value");
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- DoorHanger doorHanger = getDoorHanger(tabId, value);
- if (doorHanger == null)
- return;
-
- removeDoorHanger(doorHanger);
- updatePopup();
- }
- });
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
- }
- }
-
- private DoorhangerConfig makeConfigFromJSON(JSONObject json) throws JSONException {
- final int tabId = json.getInt("tabID");
- final String id = json.getString("value");
-
- final String typeString = json.optString("category");
- DoorHanger.Type doorhangerType = DoorHanger.Type.DEFAULT;
- if (DoorHanger.Type.LOGIN.toString().equals(typeString)) {
- doorhangerType = DoorHanger.Type.LOGIN;
- } else if (DoorHanger.Type.GEOLOCATION.toString().equals(typeString)) {
- doorhangerType = DoorHanger.Type.GEOLOCATION;
- } else if (DoorHanger.Type.DESKTOPNOTIFICATION2.toString().equals(typeString)) {
- doorhangerType = DoorHanger.Type.DESKTOPNOTIFICATION2;
- } else if (DoorHanger.Type.WEBRTC.toString().equals(typeString)) {
- doorhangerType = DoorHanger.Type.WEBRTC;
- } else if (DoorHanger.Type.VIBRATION.toString().equals(typeString)) {
- doorhangerType = DoorHanger.Type.VIBRATION;
- }
-
- final DoorhangerConfig config = new DoorhangerConfig(tabId, id, doorhangerType, this);
-
- config.setMessage(json.getString("message"));
- config.setOptions(json.getJSONObject("options"));
-
- final JSONArray buttonArray = json.getJSONArray("buttons");
- int numButtons = buttonArray.length();
- if (numButtons > 2) {
- Log.e(LOGTAG, "Doorhanger can have a maximum of two buttons!");
- numButtons = 2;
- }
-
- for (int i = 0; i < numButtons; i++) {
- final JSONObject buttonJSON = buttonArray.getJSONObject(i);
- final boolean isPositive = buttonJSON.optBoolean("positive", false);
- config.setButton(buttonJSON.getString("label"), buttonJSON.getInt("callback"), isPositive);
- }
-
- return config;
- }
-
- // This callback is automatically executed on the UI thread.
- @Override
- public void onTabChanged(final Tab tab, final Tabs.TabEvents msg, final String data) {
- switch (msg) {
- case CLOSED:
- // Remove any doorhangers for a tab when it's closed (make
- // a temporary set to avoid a ConcurrentModificationException)
- removeTabDoorHangers(tab.getId(), true);
- break;
-
- case LOCATION_CHANGE:
- // Only remove doorhangers if the popup is hidden or if we're navigating to a new URL
- if (!isShowing() || !data.equals(tab.getURL()))
- removeTabDoorHangers(tab.getId(), false);
-
- // Update the popup if the location change was on the current tab
- if (Tabs.getInstance().isSelectedTab(tab))
- updatePopup();
- break;
-
- case SELECTED:
- // Always update the popup when a new tab is selected. This will cover cases
- // where a different tab was closed, since we always need to select a new tab.
- updatePopup();
- break;
- }
- }
-
- /**
- * Adds a doorhanger.
- *
- * This method must be called on the UI thread.
- */
- void addDoorHanger(DoorhangerConfig config) {
- final int tabId = config.getTabId();
- // Don't add a doorhanger for a tab that doesn't exist
- if (Tabs.getInstance().getTab(tabId) == null) {
- return;
- }
-
- // Replace the doorhanger if it already exists
- DoorHanger oldDoorHanger = getDoorHanger(tabId, config.getId());
- if (oldDoorHanger != null) {
- removeDoorHanger(oldDoorHanger);
- }
-
- if (!mInflated) {
- init();
- }
-
- final DoorHanger newDoorHanger = DoorHanger.Get(mContext, config);
-
- mDoorHangers.add(newDoorHanger);
- mContent.addView(newDoorHanger);
-
- // Only update the popup if we're adding a notification to the selected tab
- if (tabId == Tabs.getInstance().getSelectedTab().getId())
- updatePopup();
- }
-
-
- /*
- * DoorHanger.OnButtonClickListener implementation
- */
- @Override
- public void onButtonClick(JSONObject response, DoorHanger doorhanger) {
- GeckoAppShell.notifyObservers("Doorhanger:Reply", response.toString());
- removeDoorHanger(doorhanger);
- updatePopup();
- }
-
- /**
- * Gets a doorhanger.
- *
- * This method must be called on the UI thread.
- */
- DoorHanger getDoorHanger(int tabId, String value) {
- for (DoorHanger dh : mDoorHangers) {
- if (dh.getTabId() == tabId && dh.getIdentifier().equals(value))
- return dh;
- }
-
- // If there's no doorhanger for the given tabId and value, return null
- return null;
- }
-
- /**
- * Removes a doorhanger.
- *
- * This method must be called on the UI thread.
- */
- void removeDoorHanger(final DoorHanger doorHanger) {
- mDoorHangers.remove(doorHanger);
- mContent.removeView(doorHanger);
- }
-
- /**
- * Removes doorhangers for a given tab.
- * @param tabId identifier of the tab to remove doorhangers from
- * @param forceRemove boolean for force-removing tabs. If true, all doorhangers associated
- * with the tab specified are removed; if false, only remove the doorhangers
- * that are not persistent, as specified by the doorhanger options.
- *
- * This method must be called on the UI thread.
- */
- void removeTabDoorHangers(int tabId, boolean forceRemove) {
- // Make a temporary set to avoid a ConcurrentModificationException
- HashSet<DoorHanger> doorHangersToRemove = new HashSet<DoorHanger>();
- for (DoorHanger dh : mDoorHangers) {
- // Only remove transient doorhangers for the given tab
- if (dh.getTabId() == tabId
- && (forceRemove || (!forceRemove && dh.shouldRemove(isShowing())))) {
- doorHangersToRemove.add(dh);
- }
- }
-
- for (DoorHanger dh : doorHangersToRemove) {
- removeDoorHanger(dh);
- }
- }
-
- /**
- * Updates the popup state.
- *
- * This method must be called on the UI thread.
- */
- void updatePopup() {
- // Bail if the selected tab is null, if there are no active doorhangers,
- // if we haven't inflated the layout yet (this can happen if updatePopup()
- // is called before the runnable from addDoorHanger() runs), or if the
- // doorhanger popup is temporarily disabled.
- Tab tab = Tabs.getInstance().getSelectedTab();
- if (tab == null || mDoorHangers.size() == 0 || !mInflated || mDisabled) {
- dismiss();
- return;
- }
-
- // Show doorhangers for the selected tab
- int tabId = tab.getId();
- boolean shouldShowPopup = false;
- DoorHanger firstDoorhanger = null;
- for (DoorHanger dh : mDoorHangers) {
- if (dh.getTabId() == tabId) {
- dh.setVisibility(View.VISIBLE);
- shouldShowPopup = true;
- if (firstDoorhanger == null) {
- firstDoorhanger = dh;
- } else {
- dh.hideTitle();
- }
- } else {
- dh.setVisibility(View.GONE);
- }
- }
-
- // Dismiss the popup if there are no doorhangers to show for this tab
- if (!shouldShowPopup) {
- dismiss();
- return;
- }
-
- showDividers();
-
- final String baseDomain = tab.getBaseDomain();
-
- if (TextUtils.isEmpty(baseDomain)) {
- firstDoorhanger.hideTitle();
- } else {
- firstDoorhanger.showTitle(tab.getFavicon(), baseDomain);
- }
-
- if (isShowing()) {
- show();
- return;
- }
-
- setFocusable(true);
-
- show();
- }
-
- //Show all inter-DoorHanger dividers (ie. Dividers on all visible DoorHangers except the last one)
- private void showDividers() {
- int count = mContent.getChildCount();
- DoorHanger lastVisibleDoorHanger = null;
-
- for (int i = 0; i < count; i++) {
- DoorHanger dh = (DoorHanger) mContent.getChildAt(i);
- dh.showDivider();
- if (dh.getVisibility() == View.VISIBLE) {
- lastVisibleDoorHanger = dh;
- }
- }
- if (lastVisibleDoorHanger != null) {
- lastVisibleDoorHanger.hideDivider();
- }
- }
-
- @Override
- public void onDismiss() {
- final int tabId = Tabs.getInstance().getSelectedTab().getId();
- removeTabDoorHangers(tabId, true);
- }
-
- @Override
- public void dismiss() {
- // If the popup is focusable while it is hidden, we run into crashes
- // on pre-ICS devices when the popup gets focus before it is shown.
- setFocusable(false);
- super.dismiss();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/DownloadsIntegration.java b/mobile/android/base/java/org/mozilla/gecko/DownloadsIntegration.java
deleted file mode 100644
index ff3ac6110..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/DownloadsIntegration.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.annotation.WrapForJNI;
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.permissions.Permissions;
-import org.mozilla.gecko.util.NativeEventListener;
-import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.gecko.util.EventCallback;
-
-import java.io.File;
-import java.lang.IllegalArgumentException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import android.app.DownloadManager;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.media.MediaScannerConnection;
-import android.media.MediaScannerConnection.MediaScannerConnectionClient;
-import android.net.Uri;
-import android.os.Environment;
-import android.text.TextUtils;
-import android.util.Log;
-
-public class DownloadsIntegration implements NativeEventListener
-{
- private static final String LOGTAG = "GeckoDownloadsIntegration";
-
- private static final List<String> UNKNOWN_MIME_TYPES;
- static {
- final ArrayList<String> tempTypes = new ArrayList<>(3);
- tempTypes.add("unknown/unknown"); // This will be used as a default mime type for unknown files
- tempTypes.add("application/unknown");
- tempTypes.add("application/octet-stream"); // Github uses this for APK files
- UNKNOWN_MIME_TYPES = Collections.unmodifiableList(tempTypes);
- }
-
- private static final String DOWNLOAD_REMOVE = "Download:Remove";
-
- private DownloadsIntegration() {
- EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener)this, DOWNLOAD_REMOVE);
- }
-
- private static DownloadsIntegration sInstance;
-
- private static class Download {
- final File file;
- final long id;
-
- final private static int UNKNOWN_ID = -1;
-
- public Download(final String path) {
- this(path, UNKNOWN_ID);
- }
-
- public Download(final String path, final long id) {
- file = new File(path);
- this.id = id;
- }
-
- public static Download fromJSON(final NativeJSObject obj) {
- final String path = obj.getString("path");
- return new Download(path);
- }
-
- public static Download fromCursor(final Cursor c) {
- final String path = c.getString(c.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_FILENAME));
- final long id = c.getLong(c.getColumnIndexOrThrow(DownloadManager.COLUMN_ID));
- return new Download(path, id);
- }
-
- public boolean equals(final Download download) {
- return file.equals(download.file);
- }
- }
-
- public static void init() {
- if (sInstance == null) {
- sInstance = new DownloadsIntegration();
- }
- }
-
- @Override
- public void handleMessage(final String event, final NativeJSObject message,
- final EventCallback callback) {
- if (DOWNLOAD_REMOVE.equals(event)) {
- final Download d = Download.fromJSON(message);
- removeDownload(d);
- }
- }
-
- private static boolean useSystemDownloadManager() {
- if (!AppConstants.ANDROID_DOWNLOADS_INTEGRATION) {
- return false;
- }
-
- int state = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
- try {
- state = GeckoAppShell.getContext().getPackageManager().getApplicationEnabledSetting("com.android.providers.downloads");
- } catch (IllegalArgumentException e) {
- // Download Manager package does not exist
- return false;
- }
-
- return (PackageManager.COMPONENT_ENABLED_STATE_ENABLED == state ||
- PackageManager.COMPONENT_ENABLED_STATE_DEFAULT == state);
- }
-
- @WrapForJNI(calledFrom = "gecko")
- public static String getTemporaryDownloadDirectory() {
- Context context = GeckoAppShell.getApplicationContext();
-
- if (Permissions.has(context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
- // We do have the STORAGE permission, so we can save the file directly to the public
- // downloads directory.
- return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
- .getAbsolutePath();
- } else {
- // Without the permission we are going to start to download the file to the cache
- // directory. Later in the process we will ask for the permission and the download
- // process will move the file to the actual downloads directory. If we do not get the
- // permission then the download will be cancelled.
- return context.getCacheDir().getAbsolutePath();
- }
- }
-
-
- @WrapForJNI(calledFrom = "gecko")
- public static void scanMedia(final String aFile, String aMimeType) {
- String mimeType = aMimeType;
- if (UNKNOWN_MIME_TYPES.contains(mimeType)) {
- // If this is a generic undefined mimetype, erase it so that we can try to determine
- // one from the file extension below.
- mimeType = "";
- }
-
- // If the platform didn't give us a mimetype, try to guess one from the filename
- if (TextUtils.isEmpty(mimeType)) {
- final int extPosition = aFile.lastIndexOf(".");
- if (extPosition > 0 && extPosition < aFile.length() - 1) {
- mimeType = GeckoAppShell.getMimeTypeFromExtension(aFile.substring(extPosition + 1));
- }
- }
-
- // addCompletedDownload will throw if it received any null parameters. Use aMimeType or a default
- // if we still don't have one.
- if (TextUtils.isEmpty(mimeType)) {
- if (TextUtils.isEmpty(aMimeType)) {
- mimeType = UNKNOWN_MIME_TYPES.get(0);
- } else {
- mimeType = aMimeType;
- }
- }
-
- if (useSystemDownloadManager()) {
- final File f = new File(aFile);
- final DownloadManager dm = (DownloadManager) GeckoAppShell.getContext().getSystemService(Context.DOWNLOAD_SERVICE);
- dm.addCompletedDownload(f.getName(),
- f.getName(),
- true, // Media scanner should scan this
- mimeType,
- f.getAbsolutePath(),
- Math.max(1, f.length()), // Some versions of Android require downloads to be at least length 1
- false); // Don't show a notification.
- } else {
- final Context context = GeckoAppShell.getContext();
- final GeckoMediaScannerClient client = new GeckoMediaScannerClient(context, aFile, mimeType);
- client.connect();
- }
- }
-
- public static void removeDownload(final Download download) {
- if (!useSystemDownloadManager()) {
- return;
- }
-
- final DownloadManager dm = (DownloadManager) GeckoAppShell.getContext().getSystemService(Context.DOWNLOAD_SERVICE);
-
- Cursor c = null;
- try {
- c = dm.query((new DownloadManager.Query()).setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL));
- if (c == null || !c.moveToFirst()) {
- return;
- }
-
- do {
- final Download d = Download.fromCursor(c);
- // Try hard as we can to verify this download is the one we think it is
- if (download.equals(d)) {
- dm.remove(d.id);
- }
- } while (c.moveToNext());
- } finally {
- if (c != null) {
- c.close();
- }
- }
- }
-
- private static final class GeckoMediaScannerClient implements MediaScannerConnectionClient {
- private final String mFile;
- private final String mMimeType;
- private MediaScannerConnection mScanner;
-
- public GeckoMediaScannerClient(Context context, String file, String mimeType) {
- mFile = file;
- mMimeType = mimeType;
- mScanner = new MediaScannerConnection(context, this);
- }
-
- public void connect() {
- mScanner.connect();
- }
-
- @Override
- public void onMediaScannerConnected() {
- mScanner.scanFile(mFile, mMimeType);
- }
-
- @Override
- public void onScanCompleted(String path, Uri uri) {
- if (path.equals(mFile)) {
- mScanner.disconnect();
- mScanner = null;
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/DynamicToolbar.java b/mobile/android/base/java/org/mozilla/gecko/DynamicToolbar.java
deleted file mode 100644
index 28f542d5c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/DynamicToolbar.java
+++ /dev/null
@@ -1,218 +0,0 @@
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.PrefsHelper.PrefHandlerBase;
-import org.mozilla.gecko.gfx.DynamicToolbarAnimator.PinReason;
-import org.mozilla.gecko.gfx.LayerView;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.os.Build;
-import android.os.Bundle;
-import android.util.Log;
-
-public class DynamicToolbar {
- private static final String LOGTAG = "DynamicToolbar";
-
- private static final String STATE_ENABLED = "dynamic_toolbar";
- private static final String CHROME_PREF = "browser.chrome.dynamictoolbar";
-
- // DynamicToolbar is enabled iff prefEnabled is true *and* accessibilityEnabled is false,
- // so it is disabled by default on startup. We do not enable it until we explicitly get
- // the pref from Gecko telling us to turn it on.
- private volatile boolean prefEnabled;
- private boolean accessibilityEnabled;
- // On some device we have to force-disable the dynamic toolbar because of
- // bugs in the Android code. See bug 1231554.
- private final boolean forceDisabled;
-
- private final PrefsHelper.PrefHandler prefObserver;
- private LayerView layerView;
- private OnEnabledChangedListener enabledChangedListener;
- private boolean temporarilyVisible;
-
- public enum VisibilityTransition {
- IMMEDIATE,
- ANIMATE
- }
-
- /**
- * Listener for changes to the dynamic toolbar's enabled state.
- */
- public interface OnEnabledChangedListener {
- /**
- * This callback is executed on the UI thread.
- */
- public void onEnabledChanged(boolean enabled);
- }
-
- public DynamicToolbar() {
- // Listen to the dynamic toolbar pref
- prefObserver = new PrefHandler();
- PrefsHelper.addObserver(new String[] { CHROME_PREF }, prefObserver);
- forceDisabled = isForceDisabled();
- if (forceDisabled) {
- Log.i(LOGTAG, "Force-disabling dynamic toolbar for " + Build.MODEL + " (" + Build.DEVICE + "/" + Build.PRODUCT + ")");
- }
- }
-
- public static boolean isForceDisabled() {
- // Force-disable dynamic toolbar on the variants of the Galaxy Note 10.1
- // and Note 8.0 running Android 4.1.2. (Bug 1231554). This includes
- // the following model numbers:
- // GT-N8000, GT-N8005, GT-N8010, GT-N8013, GT-N8020
- // GT-N5100, GT-N5110, GT-N5120
- if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN
- && (Build.MODEL.startsWith("GT-N80") ||
- Build.MODEL.startsWith("GT-N51"))) {
- return true;
- }
- // Also disable variants of the Galaxy Note 4 on Android 5.0.1 (Bug 1301593)
- if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP
- && (Build.MODEL.startsWith("SM-N910"))) {
- return true;
- }
- return false;
- }
-
- public void destroy() {
- PrefsHelper.removeObserver(prefObserver);
- }
-
- public void setLayerView(LayerView layerView) {
- ThreadUtils.assertOnUiThread();
-
- this.layerView = layerView;
- }
-
- public void setEnabledChangedListener(OnEnabledChangedListener listener) {
- ThreadUtils.assertOnUiThread();
-
- enabledChangedListener = listener;
- }
-
- public void onSaveInstanceState(Bundle outState) {
- ThreadUtils.assertOnUiThread();
-
- outState.putBoolean(STATE_ENABLED, prefEnabled);
- }
-
- public void onRestoreInstanceState(Bundle savedInstanceState) {
- ThreadUtils.assertOnUiThread();
-
- if (savedInstanceState != null) {
- prefEnabled = savedInstanceState.getBoolean(STATE_ENABLED);
- }
- }
-
- public boolean isEnabled() {
- ThreadUtils.assertOnUiThread();
-
- if (forceDisabled) {
- return false;
- }
-
- return prefEnabled && !accessibilityEnabled;
- }
-
- public void setAccessibilityEnabled(boolean enabled) {
- ThreadUtils.assertOnUiThread();
-
- if (accessibilityEnabled == enabled) {
- return;
- }
-
- // Disable the dynamic toolbar when accessibility features are enabled,
- // and re-read the preference when they're disabled.
- accessibilityEnabled = enabled;
- if (prefEnabled) {
- triggerEnabledListener();
- }
- }
-
- public void setVisible(boolean visible, VisibilityTransition transition) {
- ThreadUtils.assertOnUiThread();
-
- if (layerView == null) {
- return;
- }
-
- // Don't hide the ActionBar/Toolbar, if it's pinned open by TextSelection.
- if (visible == false &&
- layerView.getDynamicToolbarAnimator().isPinnedBy(PinReason.ACTION_MODE)) {
- return;
- }
-
- final boolean isImmediate = transition == VisibilityTransition.IMMEDIATE;
- if (visible) {
- layerView.getDynamicToolbarAnimator().showToolbar(isImmediate);
- } else {
- layerView.getDynamicToolbarAnimator().hideToolbar(isImmediate);
- }
- }
-
- public void setTemporarilyVisible(boolean visible, VisibilityTransition transition) {
- ThreadUtils.assertOnUiThread();
-
- if (layerView == null) {
- return;
- }
-
- if (visible == temporarilyVisible) {
- // nothing to do
- return;
- }
-
- temporarilyVisible = visible;
- final boolean isImmediate = transition == VisibilityTransition.IMMEDIATE;
- if (visible) {
- layerView.getDynamicToolbarAnimator().showToolbar(isImmediate);
- } else {
- layerView.getDynamicToolbarAnimator().hideToolbar(isImmediate);
- }
- }
-
- public void persistTemporaryVisibility() {
- ThreadUtils.assertOnUiThread();
-
- if (temporarilyVisible) {
- temporarilyVisible = false;
- setVisible(true, VisibilityTransition.IMMEDIATE);
- }
- }
-
- public void setPinned(boolean pinned, PinReason reason) {
- ThreadUtils.assertOnUiThread();
- if (layerView == null) {
- return;
- }
-
- layerView.getDynamicToolbarAnimator().setPinned(pinned, reason);
- }
-
- private void triggerEnabledListener() {
- if (enabledChangedListener != null) {
- enabledChangedListener.onEnabledChanged(isEnabled());
- }
- }
-
- private class PrefHandler extends PrefHandlerBase {
- @Override
- public void prefValue(String pref, boolean value) {
- if (value == prefEnabled) {
- return;
- }
-
- prefEnabled = value;
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- // If accessibility is enabled, the dynamic toolbar is
- // forced to be off.
- if (!accessibilityEnabled) {
- triggerEnabledListener();
- }
- }
- });
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/EditBookmarkDialog.java b/mobile/android/base/java/org/mozilla/gecko/EditBookmarkDialog.java
deleted file mode 100644
index 38c38a9eb..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/EditBookmarkDialog.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.BrowserContract.Bookmarks;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.UIAsyncTask;
-
-import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.app.AlertDialog;
-import android.content.DialogInterface;
-import android.database.Cursor;
-import android.support.design.widget.Snackbar;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.EditText;
-
-/**
- * A dialog that allows editing a bookmarks url, title, or keywords
- * <p>
- * Invoked by calling one of the {@link org.mozilla.gecko.EditBookmarkDialog#show(String)}
- * methods.
- */
-public class EditBookmarkDialog {
- private final Context mContext;
-
- public EditBookmarkDialog(Context context) {
- mContext = context;
- }
-
- /**
- * A private struct to make it easier to pass bookmark data across threads
- */
- private class Bookmark {
- final int id;
- final String title;
- final String url;
- final String keyword;
-
- public Bookmark(int aId, String aTitle, String aUrl, String aKeyword) {
- id = aId;
- title = aTitle;
- url = aUrl;
- keyword = aKeyword;
- }
- }
-
- /**
- * This text watcher to enable or disable the OK button if the dialog contains
- * valid information. This class is overridden to do data checking on different fields.
- * By itself, it always enables the button.
- *
- * Callers can also assign a paired partner to the TextWatcher, and callers will check
- * that both are enabled before enabling the ok button.
- */
- private class EditBookmarkTextWatcher implements TextWatcher {
- // A stored reference to the dialog containing the text field being watched
- protected AlertDialog mDialog;
-
- // A stored text watcher to do the real verification of a field
- protected EditBookmarkTextWatcher mPairedTextWatcher;
-
- // Whether or not the ok button should be enabled.
- protected boolean mEnabled = true;
-
- public EditBookmarkTextWatcher(AlertDialog aDialog) {
- mDialog = aDialog;
- }
-
- public void setPairedTextWatcher(EditBookmarkTextWatcher aTextWatcher) {
- mPairedTextWatcher = aTextWatcher;
- }
-
- public boolean isEnabled() {
- return mEnabled;
- }
-
- // Textwatcher interface
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- // Disable if the we're disabled or the paired partner is disabled
- boolean enabled = mEnabled && (mPairedTextWatcher == null || mPairedTextWatcher.isEnabled());
- mDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(enabled);
- }
-
- @Override
- public void afterTextChanged(Editable s) {}
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
- }
-
- /**
- * A version of the EditBookmarkTextWatcher for the url field of the dialog.
- * Only checks if the field is empty or not.
- */
- private class LocationTextWatcher extends EditBookmarkTextWatcher {
- public LocationTextWatcher(AlertDialog aDialog) {
- super(aDialog);
- }
-
- // Disables the ok button if the location field is empty.
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- mEnabled = (s.toString().trim().length() > 0);
- super.onTextChanged(s, start, before, count);
- }
- }
-
- /**
- * A version of the EditBookmarkTextWatcher for the keyword field of the dialog.
- * Checks if the field has any (non leading or trailing) spaces.
- */
- private class KeywordTextWatcher extends EditBookmarkTextWatcher {
- public KeywordTextWatcher(AlertDialog aDialog) {
- super(aDialog);
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- // Disable if the keyword contains spaces
- mEnabled = (s.toString().trim().indexOf(' ') == -1);
- super.onTextChanged(s, start, before, count);
- }
- }
-
- /**
- * Show the Edit bookmark dialog for a particular url. If the url is bookmarked multiple times
- * this will just edit the first instance it finds.
- *
- * @param url The url of the bookmark to edit. The dialog will look up other information like the id,
- * current title, or keywords associated with this url. If the url isn't bookmarked, the
- * dialog will fail silently. If the url is bookmarked multiple times, this will only show
- * information about the first it finds.
- */
- public void show(final String url) {
- final ContentResolver cr = mContext.getContentResolver();
- final BrowserDB db = BrowserDB.from(mContext);
- (new UIAsyncTask.WithoutParams<Bookmark>(ThreadUtils.getBackgroundHandler()) {
- @Override
- public Bookmark doInBackground() {
- final Cursor cursor = db.getBookmarkForUrl(cr, url);
- if (cursor == null) {
- return null;
- }
-
- Bookmark bookmark = null;
- try {
- cursor.moveToFirst();
- bookmark = new Bookmark(cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID)),
- cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.TITLE)),
- cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.URL)),
- cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.KEYWORD)));
- } finally {
- cursor.close();
- }
- return bookmark;
- }
-
- @Override
- public void onPostExecute(Bookmark bookmark) {
- if (bookmark == null) {
- return;
- }
-
- show(bookmark.id, bookmark.title, bookmark.url, bookmark.keyword);
- }
- }).execute();
- }
-
- /**
- * Show the Edit bookmark dialog for a set of data. This will show the dialog whether
- * a bookmark with this url exists or not, but the results will NOT be saved if the id
- * is not a valid bookmark id.
- *
- * @param id The id of the bookmark to change. If there is no bookmark with this ID, the dialog
- * will fail silently.
- * @param title The initial title to show in the dialog
- * @param url The initial url to show in the dialog
- * @param keyword The initial keyword to show in the dialog
- */
- public void show(final int id, final String title, final String url, final String keyword) {
- final Context context = mContext;
-
- AlertDialog.Builder editPrompt = new AlertDialog.Builder(context);
- final View editView = LayoutInflater.from(context).inflate(R.layout.bookmark_edit, null);
- editPrompt.setTitle(R.string.bookmark_edit_title);
- editPrompt.setView(editView);
-
- final EditText nameText = ((EditText) editView.findViewById(R.id.edit_bookmark_name));
- final EditText locationText = ((EditText) editView.findViewById(R.id.edit_bookmark_location));
- final EditText keywordText = ((EditText) editView.findViewById(R.id.edit_bookmark_keyword));
- nameText.setText(title);
- locationText.setText(url);
- keywordText.setText(keyword);
-
- final BrowserDB db = BrowserDB.from(mContext);
- editPrompt.setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- (new UIAsyncTask.WithoutParams<Void>(ThreadUtils.getBackgroundHandler()) {
- @Override
- public Void doInBackground() {
- String newUrl = locationText.getText().toString().trim();
- String newKeyword = keywordText.getText().toString().trim();
-
- db.updateBookmark(context.getContentResolver(), id, newUrl, nameText.getText().toString(), newKeyword);
- return null;
- }
-
- @Override
- public void onPostExecute(Void result) {
- SnackbarBuilder.builder((Activity) context)
- .message(R.string.bookmark_updated)
- .duration(Snackbar.LENGTH_LONG)
- .buildAndShow();
- }
- }).execute();
- }
- });
-
- editPrompt.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- // do nothing
- }
- });
-
- final AlertDialog dialog = editPrompt.create();
-
- // Create our TextWatchers
- LocationTextWatcher locationTextWatcher = new LocationTextWatcher(dialog);
- KeywordTextWatcher keywordTextWatcher = new KeywordTextWatcher(dialog);
-
- // Cross reference the TextWatchers
- locationTextWatcher.setPairedTextWatcher(keywordTextWatcher);
- keywordTextWatcher.setPairedTextWatcher(locationTextWatcher);
-
- // Add the TextWatcher Listeners
- locationText.addTextChangedListener(locationTextWatcher);
- keywordText.addTextChangedListener(keywordTextWatcher);
-
- dialog.show();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/Experiments.java b/mobile/android/base/java/org/mozilla/gecko/Experiments.java
deleted file mode 100644
index e71bb4c52..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/Experiments.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.content.Context;
-
-import android.util.Log;
-import android.text.TextUtils;
-
-import com.keepsafe.switchboard.Preferences;
-import com.keepsafe.switchboard.SwitchBoard;
-
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * This class should reflect the experiment names found in the Switchboard experiments config here:
- * https://github.com/mozilla-services/switchboard-experiments
- */
-public class Experiments {
- private static final String LOGTAG = "GeckoExperiments";
-
- // Show a system notification linking to a "What's New" page on app update.
- public static final String WHATSNEW_NOTIFICATION = "whatsnew-notification";
-
- // Subscribe to known, bookmarked sites and show a notification if new content is available.
- public static final String CONTENT_NOTIFICATIONS_12HRS = "content-notifications-12hrs";
- public static final String CONTENT_NOTIFICATIONS_8AM = "content-notifications-8am";
- public static final String CONTENT_NOTIFICATIONS_5PM = "content-notifications-5pm";
-
- // Onboarding: "Features and Story". These experiments are determined
- // on the client, they are not part of the server config.
- public static final String ONBOARDING3_A = "onboarding3-a"; // Control: No first run
- public static final String ONBOARDING3_B = "onboarding3-b"; // 4 static Feature + 1 dynamic slides
- public static final String ONBOARDING3_C = "onboarding3-c"; // Differentiating features slides
-
- // Synchronizing the catalog of downloadable content from Kinto
- public static final String DOWNLOAD_CONTENT_CATALOG_SYNC = "download-content-catalog-sync";
-
- // Promotion for "Add to homescreen"
- public static final String PROMOTE_ADD_TO_HOMESCREEN = "promote-add-to-homescreen";
-
- public static final String PREF_ONBOARDING_VERSION = "onboarding_version";
-
- // Promotion to bookmark reader-view items after entering reader view three times (Bug 1247689)
- public static final String TRIPLE_READERVIEW_BOOKMARK_PROMPT = "triple-readerview-bookmark-prompt";
-
- // Only show origin in URL bar instead of full URL (Bug 1236431)
- public static final String URLBAR_SHOW_ORIGIN_ONLY = "urlbar-show-origin-only";
-
- // Show name of organization (EV cert) instead of full URL in URL bar (Bug 1249594).
- public static final String URLBAR_SHOW_EV_CERT_OWNER = "urlbar-show-ev-cert-owner";
-
- // Play HLS videos in a VideoView (Bug 1313391)
- public static final String HLS_VIDEO_PLAYBACK = "hls-video-playback";
-
- // Make new activity stream panel available (to replace top sites) (Bug 1313316)
- public static final String ACTIVITY_STREAM = "activity-stream";
-
- /**
- * Returns if a user is in certain local experiment.
- * @param experiment Name of experiment to look up
- * @return returns value for experiment or false if experiment does not exist.
- */
- public static boolean isInExperimentLocal(Context context, String experiment) {
- if (SwitchBoard.isInBucket(context, 0, 20)) {
- return Experiments.ONBOARDING3_A.equals(experiment);
- } else if (SwitchBoard.isInBucket(context, 20, 60)) {
- return Experiments.ONBOARDING3_B.equals(experiment);
- } else if (SwitchBoard.isInBucket(context, 60, 100)) {
- return Experiments.ONBOARDING3_C.equals(experiment);
- } else {
- return false;
- }
- }
-
- /**
- * Returns list of all active experiments, remote and local.
- * @return List of experiment names Strings
- */
- public static List<String> getActiveExperiments(Context c) {
- final List<String> experiments = new LinkedList<>();
- experiments.addAll(SwitchBoard.getActiveExperiments(c));
-
- // Add onboarding version.
- final String onboardingExperiment = GeckoSharedPrefs.forProfile(c).getString(Experiments.PREF_ONBOARDING_VERSION, null);
- if (!TextUtils.isEmpty(onboardingExperiment)) {
- experiments.add(onboardingExperiment);
- }
-
- return experiments;
- }
-
- /**
- * Sets an override to force an experiment to be enabled or disabled. This value
- * will be read and used before reading the switchboard server configuration.
- *
- * @param c Context
- * @param experimentName Experiment name
- * @param isEnabled Whether or not the experiment should be enabled
- */
- public static void setOverride(Context c, String experimentName, boolean isEnabled) {
- Log.d(LOGTAG, "setOverride: " + experimentName + " = " + isEnabled);
- Preferences.setOverrideValue(c, experimentName, isEnabled);
- }
-
- /**
- * Clears the override value for an experiment.
- *
- * @param c Context
- * @param experimentName Experiment name
- */
- public static void clearOverride(Context c, String experimentName) {
- Log.d(LOGTAG, "clearOverride: " + experimentName);
- Preferences.clearOverrideValue(c, experimentName);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/FilePicker.java b/mobile/android/base/java/org/mozilla/gecko/FilePicker.java
deleted file mode 100644
index 8ac5428a4..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/FilePicker.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.util.GeckoEventListener;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.Parcelable;
-import android.provider.MediaStore;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-public class FilePicker implements GeckoEventListener {
- private static final String LOGTAG = "GeckoFilePicker";
- private static FilePicker sFilePicker;
- private final Context context;
-
- public interface ResultHandler {
- public void gotFile(String filename);
- }
-
- public static void init(Context context) {
- if (sFilePicker == null) {
- sFilePicker = new FilePicker(context.getApplicationContext());
- }
- }
-
- protected FilePicker(Context context) {
- this.context = context;
- EventDispatcher.getInstance().registerGeckoThreadListener(this, "FilePicker:Show");
- }
-
- @Override
- public void handleMessage(String event, final JSONObject message) {
- if (event.equals("FilePicker:Show")) {
- String mimeType = "*/*";
- final String mode = message.optString("mode");
- final int tabId = message.optInt("tabId", -1);
- final String title = message.optString("title");
-
- if ("mimeType".equals(mode))
- mimeType = message.optString("mimeType");
- else if ("extension".equals(mode))
- mimeType = GeckoAppShell.getMimeTypeFromExtensions(message.optString("extensions"));
-
- showFilePickerAsync(title, mimeType, new ResultHandler() {
- @Override
- public void gotFile(String filename) {
- try {
- message.put("file", filename);
- } catch (JSONException ex) {
- Log.i(LOGTAG, "Can't add filename to message " + filename);
- }
-
-
- GeckoAppShell.notifyObservers("FilePicker:Result", message.toString());
- }
- }, tabId);
- }
- }
-
- private void addActivities(Intent intent, HashMap<String, Intent> intents, HashMap<String, Intent> filters) {
- PackageManager pm = context.getPackageManager();
- List<ResolveInfo> lri = pm.queryIntentActivities(intent, 0);
- for (ResolveInfo ri : lri) {
- ComponentName cn = new ComponentName(ri.activityInfo.applicationInfo.packageName, ri.activityInfo.name);
- if (filters != null && !filters.containsKey(cn.toString())) {
- Intent rintent = new Intent(intent);
- rintent.setComponent(cn);
- intents.put(cn.toString(), rintent);
- }
- }
- }
-
- private Intent getIntent(String mimeType) {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.setType(mimeType);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- return intent;
- }
-
- private List<Intent> getIntentsForFilePicker(final String mimeType,
- final FilePickerResultHandler fileHandler) {
- // The base intent to use for the file picker. Even if this is an implicit intent, Android will
- // still show a list of Activities that match this action/type.
- Intent baseIntent;
- // A HashMap of Activities the base intent will show in the chooser. This is used
- // to filter activities from other intents so that we don't show duplicates.
- HashMap<String, Intent> baseIntents = new HashMap<String, Intent>();
- // A list of other activities to shwo in the picker (and the intents to launch them).
- HashMap<String, Intent> intents = new HashMap<String, Intent> ();
-
- if ("audio/*".equals(mimeType)) {
- // For audio the only intent is the mimetype
- baseIntent = getIntent(mimeType);
- addActivities(baseIntent, baseIntents, null);
- } else if ("image/*".equals(mimeType)) {
- // For images the base is a capture intent
- baseIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
- baseIntent.putExtra(MediaStore.EXTRA_OUTPUT,
- Uri.fromFile(new File(Environment.getExternalStorageDirectory(),
- fileHandler.generateImageName())));
- addActivities(baseIntent, baseIntents, null);
-
- // We also add the mimetype intent
- addActivities(getIntent(mimeType), intents, baseIntents);
- } else if ("video/*".equals(mimeType)) {
- // For videos the base is a capture intent
- baseIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
- addActivities(baseIntent, baseIntents, null);
-
- // We also add the mimetype intent
- addActivities(getIntent(mimeType), intents, baseIntents);
- } else {
- baseIntent = getIntent("*/*");
- addActivities(baseIntent, baseIntents, null);
-
- Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
- intent.putExtra(MediaStore.EXTRA_OUTPUT,
- Uri.fromFile(new File(Environment.getExternalStorageDirectory(),
- fileHandler.generateImageName())));
- addActivities(intent, intents, baseIntents);
- intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
- addActivities(intent, intents, baseIntents);
- }
-
- // If we didn't find any activities, we fall back to the */* mimetype intent
- if (baseIntents.size() == 0 && intents.size() == 0) {
- intents.clear();
-
- baseIntent = getIntent("*/*");
- addActivities(baseIntent, baseIntents, null);
- }
-
- ArrayList<Intent> vals = new ArrayList<Intent>(intents.values());
- vals.add(0, baseIntent);
- return vals;
- }
-
- private String getFilePickerTitle(String mimeType) {
- if (mimeType.equals("audio/*")) {
- return context.getString(R.string.filepicker_audio_title);
- } else if (mimeType.equals("image/*")) {
- return context.getString(R.string.filepicker_image_title);
- } else if (mimeType.equals("video/*")) {
- return context.getString(R.string.filepicker_video_title);
- } else {
- return context.getString(R.string.filepicker_title);
- }
- }
-
- private interface IntentHandler {
- public void gotIntent(Intent intent);
- }
-
- /* Gets an intent that can open a particular mimetype. Will show a prompt with a list
- * of Activities that can handle the mietype. Asynchronously calls the handler when
- * one of the intents is selected. If the caller passes in null for the handler, will still
- * prompt for the activity, but will throw away the result.
- */
- private void getFilePickerIntentAsync(String title,
- final String mimeType,
- final FilePickerResultHandler fileHandler,
- final IntentHandler handler) {
- List<Intent> intents = getIntentsForFilePicker(mimeType, fileHandler);
-
- if (intents.size() == 0) {
- Log.i(LOGTAG, "no activities for the file picker!");
- handler.gotIntent(null);
- return;
- }
-
- Intent base = intents.remove(0);
-
- if (intents.size() == 0) {
- handler.gotIntent(base);
- return;
- }
-
- if (TextUtils.isEmpty(title)) {
- title = getFilePickerTitle(mimeType);
- }
- Intent chooser = Intent.createChooser(base, title);
- chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents.toArray(new Parcelable[intents.size()]));
- handler.gotIntent(chooser);
- }
-
- /* Allows the user to pick an activity to load files from using a list prompt. Then opens the activity and
- * sends the file returned to the passed in handler. If a null handler is passed in, will still
- * pick and launch the file picker, but will throw away the result.
- */
- protected void showFilePickerAsync(final String title, final String mimeType, final ResultHandler handler, final int tabId) {
- final FilePickerResultHandler fileHandler = new FilePickerResultHandler(handler, context, tabId);
- getFilePickerIntentAsync(title, mimeType, fileHandler, new IntentHandler() {
- @Override
- public void gotIntent(Intent intent) {
- if (handler == null) {
- return;
- }
-
- if (intent == null) {
- handler.gotFile("");
- return;
- }
-
- ActivityHandlerHelper.startIntent(intent, fileHandler);
- }
- });
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/FilePickerResultHandler.java b/mobile/android/base/java/org/mozilla/gecko/FilePickerResultHandler.java
deleted file mode 100644
index 7629ea546..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/FilePickerResultHandler.java
+++ /dev/null
@@ -1,282 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.mozilla.gecko.util.ActivityResultHandler;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.Process;
-import android.provider.MediaStore;
-import android.provider.OpenableColumns;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.app.LoaderManager.LoaderCallbacks;
-import android.support.v4.content.CursorLoader;
-import android.support.v4.content.Loader;
-import android.text.TextUtils;
-import android.text.format.Time;
-import android.util.Log;
-
-class FilePickerResultHandler implements ActivityResultHandler {
- private static final String LOGTAG = "GeckoFilePickerResultHandler";
- private static final String UPLOADS_DIR = "uploads";
-
- private final FilePicker.ResultHandler handler;
- private final int tabId;
- private final File cacheDir;
-
- // this code is really hacky and doesn't belong anywhere so I'm putting it here for now
- // until I can come up with a better solution.
- private String mImageName = "";
-
- /* Use this constructor to asynchronously listen for results */
- public FilePickerResultHandler(final FilePicker.ResultHandler handler, final Context context, final int tabId) {
- this.tabId = tabId;
- this.cacheDir = new File(context.getCacheDir(), UPLOADS_DIR);
- this.handler = handler;
- }
-
- void sendResult(String res) {
- if (handler != null) {
- handler.gotFile(res);
- }
- }
-
- @Override
- public void onActivityResult(int resultCode, Intent intent) {
- if (resultCode != Activity.RESULT_OK) {
- sendResult("");
- return;
- }
-
- // Camera results won't return an Intent. Use the file name we passed to the original intent.
- // In Android M, camera results return an empty Intent rather than null.
- if (intent == null || (intent.getAction() == null && intent.getData() == null)) {
- if (mImageName != null) {
- File file = new File(Environment.getExternalStorageDirectory(), mImageName);
- sendResult(file.getAbsolutePath());
- } else {
- sendResult("");
- }
- return;
- }
-
- Uri uri = intent.getData();
- if (uri == null) {
- sendResult("");
- return;
- }
-
- // Some file pickers may return a file uri
- if ("file".equals(uri.getScheme())) {
- String path = uri.getPath();
- sendResult(path == null ? "" : path);
- return;
- }
-
- final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
- final LoaderManager lm = fa.getSupportLoaderManager();
-
- // Finally, Video pickers and some file pickers may return a content provider.
- final ContentResolver cr = fa.getContentResolver();
- final Cursor cursor = cr.query(uri, new String[] { MediaStore.Video.Media.DATA }, null, null, null);
- if (cursor != null) {
- try {
- // Try a query to make sure the expected columns exist
- int index = cursor.getColumnIndex(MediaStore.Video.Media.DATA);
- if (index >= 0) {
- lm.initLoader(intent.hashCode(), null, new VideoLoaderCallbacks(uri));
- return;
- }
- } catch (Exception ex) {
- // We'll try a different loader below
- } finally {
- cursor.close();
- }
- }
-
- lm.initLoader(uri.hashCode(), null, new FileLoaderCallbacks(uri, cacheDir, tabId));
- }
-
- public String generateImageName() {
- Time now = new Time();
- now.setToNow();
- mImageName = now.format("%Y-%m-%d %H.%M.%S") + ".jpg";
- return mImageName;
- }
-
- private class VideoLoaderCallbacks implements LoaderCallbacks<Cursor> {
- final private Uri uri;
- public VideoLoaderCallbacks(Uri uri) {
- this.uri = uri;
- }
-
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
- return new CursorLoader(fa,
- uri,
- new String[] { MediaStore.Video.Media.DATA },
- null, // selection
- null, // selectionArgs
- null); // sortOrder
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
- if (cursor.moveToFirst()) {
- String res = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA));
-
- // Some pickers (the KitKat Documents one for instance) won't return a temporary file here.
- // Fall back to the normal FileLoader if we didn't find anything.
- if (TextUtils.isEmpty(res)) {
- tryFileLoaderCallback();
- return;
- }
-
- sendResult(res);
- } else {
- tryFileLoaderCallback();
- }
- }
-
- private void tryFileLoaderCallback() {
- final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
- final LoaderManager lm = fa.getSupportLoaderManager();
- lm.initLoader(uri.hashCode(), null, new FileLoaderCallbacks(uri, cacheDir, tabId));
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) { }
- }
-
- /**
- * This class's only dependency on FilePickerResultHandler is sendResult.
- */
- private class FileLoaderCallbacks implements LoaderCallbacks<Cursor>,
- Tabs.OnTabsChangedListener {
- private final Uri uri;
- private final File cacheDir;
- private final int tabId;
- String tempFile;
-
- public FileLoaderCallbacks(Uri uri, File cacheDir, int tabId) {
- this.uri = uri;
- this.cacheDir = cacheDir;
- this.tabId = tabId;
- }
-
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
- return new CursorLoader(fa,
- uri,
- new String[] { OpenableColumns.DISPLAY_NAME },
- null, // selection
- null, // selectionArgs
- null); // sortOrder
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
- if (cursor.moveToFirst()) {
- String name = cursor.getString(0);
- // tmp filenames must be at least 3 characters long. Add a prefix to make sure that happens
- String fileName = "tmp_" + Process.myPid() + "-";
- String fileExt;
- int period;
-
- final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
- final ContentResolver cr = fa.getContentResolver();
-
- // Generate an extension if we don't already have one
- if (name == null || (period = name.lastIndexOf('.')) == -1) {
- String mimeType = cr.getType(uri);
- fileExt = "." + GeckoAppShell.getExtensionFromMimeType(mimeType);
- } else {
- fileExt = name.substring(period);
- fileName += name.substring(0, period);
- }
-
- // Now write the data to the temp file
- FileOutputStream fos = null;
- try {
- cacheDir.mkdir();
-
- File file = File.createTempFile(fileName, fileExt, cacheDir);
- fos = new FileOutputStream(file);
- InputStream is = cr.openInputStream(uri);
- byte[] buf = new byte[4096];
- int len = is.read(buf);
- while (len != -1) {
- fos.write(buf, 0, len);
- len = is.read(buf);
- }
- fos.close();
- is.close();
- tempFile = file.getAbsolutePath();
- sendResult((tempFile == null) ? "" : tempFile);
-
- if (tabId > -1 && !TextUtils.isEmpty(tempFile)) {
- Tabs.registerOnTabsChangedListener(this);
- }
- } catch (IOException ex) {
- Log.i(LOGTAG, "Error writing file", ex);
- } finally {
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) { /* not much to do here */ }
- }
- }
- } else {
- sendResult("");
- }
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) { }
-
- /*Tabs.OnTabsChangedListener*/
- // This cleans up our temp file. If it doesn't run, we just hope that Android
- // will eventually does the cleanup for us.
- @Override
- public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
- if ((tab == null) || (tab.getId() != tabId)) {
- return;
- }
-
- if (msg == Tabs.TabEvents.LOCATION_CHANGE ||
- msg == Tabs.TabEvents.CLOSED) {
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- File f = new File(tempFile);
- f.delete();
- }
- });
-
- // Tabs' listener array is safe to modify during use: its
- // iteration pattern is based on snapshots.
- Tabs.unregisterOnTabsChangedListener(this);
- }
- }
- }
-
-}
-
diff --git a/mobile/android/base/java/org/mozilla/gecko/FindInPageBar.java b/mobile/android/base/java/org/mozilla/gecko/FindInPageBar.java
deleted file mode 100644
index efa04a04e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/FindInPageBar.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.GeckoRequest;
-import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import org.json.JSONObject;
-
-import android.content.Context;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-public class FindInPageBar extends LinearLayout implements TextWatcher, View.OnClickListener, GeckoEventListener {
- private static final String LOGTAG = "GeckoFindInPageBar";
- private static final String REQUEST_ID = "FindInPageBar";
-
- private final Context mContext;
- private CustomEditText mFindText;
- private TextView mStatusText;
- private boolean mInflated;
-
- public FindInPageBar(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- setFocusable(true);
- }
-
- public void inflateContent() {
- LayoutInflater inflater = LayoutInflater.from(mContext);
- View content = inflater.inflate(R.layout.find_in_page_content, this);
-
- content.findViewById(R.id.find_prev).setOnClickListener(this);
- content.findViewById(R.id.find_next).setOnClickListener(this);
- content.findViewById(R.id.find_close).setOnClickListener(this);
-
- // Capture clicks on the rest of the view to prevent them from
- // leaking into other views positioned below.
- content.setOnClickListener(this);
-
- mFindText = (CustomEditText) content.findViewById(R.id.find_text);
- mFindText.addTextChangedListener(this);
- mFindText.setOnKeyPreImeListener(new CustomEditText.OnKeyPreImeListener() {
- @Override
- public boolean onKeyPreIme(View v, int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- hide();
- return true;
- }
- return false;
- }
- });
-
- mStatusText = (TextView) content.findViewById(R.id.find_status);
-
- mInflated = true;
- GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
- "FindInPage:MatchesCountResult",
- "TextSelection:Data");
- }
-
- public void show() {
- if (!mInflated)
- inflateContent();
-
- setVisibility(VISIBLE);
- mFindText.requestFocus();
-
- // handleMessage() receives response message and determines initial state of softInput
- GeckoAppShell.notifyObservers("TextSelection:Get", REQUEST_ID);
- GeckoAppShell.notifyObservers("FindInPage:Opened", null);
- }
-
- public void hide() {
- if (!mInflated || getVisibility() == View.GONE) {
- // There's nothing to hide yet.
- return;
- }
-
- // Always clear the Find string, primarily for privacy.
- mFindText.setText("");
-
- // Only close the IMM if its EditText is the one with focus.
- if (mFindText.isFocused()) {
- getInputMethodManager(mFindText).hideSoftInputFromWindow(mFindText.getWindowToken(), 0);
- }
-
- // Close the FIPB / FindHelper state.
- setVisibility(GONE);
- GeckoAppShell.notifyObservers("FindInPage:Closed", null);
- }
-
- private InputMethodManager getInputMethodManager(View view) {
- Context context = view.getContext();
- return (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
- }
-
- public void onDestroy() {
- if (!mInflated) {
- return;
- }
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
- "FindInPage:MatchesCountResult",
- "TextSelection:Data");
- }
-
- private void onMatchesCountResult(final int total, final int current, final int limit, final String searchString) {
- if (total == -1) {
- updateResult(Integer.toString(limit) + "+");
- } else if (total > 0) {
- updateResult(Integer.toString(current) + "/" + Integer.toString(total));
- } else if (TextUtils.isEmpty(searchString)) {
- updateResult("");
- } else {
- // We display 0/0, when there were no
- // matches found, or if matching has been turned off by setting
- // pref accessibility.typeaheadfind.matchesCountLimit to 0.
- updateResult("0/0");
- }
- }
-
- private void updateResult(final String statusText) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- mStatusText.setVisibility(statusText.isEmpty() ? View.GONE : View.VISIBLE);
- mStatusText.setText(statusText);
- }
- });
- }
-
- // TextWatcher implementation
-
- @Override
- public void afterTextChanged(Editable s) {
- sendRequestToFinderHelper("FindInPage:Find", s.toString());
- }
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- // ignore
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- // ignore
- }
-
- // View.OnClickListener implementation
-
- @Override
- public void onClick(View v) {
- final int viewId = v.getId();
-
- String extras = getResources().getResourceEntryName(viewId);
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, extras);
-
- if (viewId == R.id.find_prev) {
- sendRequestToFinderHelper("FindInPage:Prev", mFindText.getText().toString());
- getInputMethodManager(mFindText).hideSoftInputFromWindow(mFindText.getWindowToken(), 0);
- return;
- }
-
- if (viewId == R.id.find_next) {
- sendRequestToFinderHelper("FindInPage:Next", mFindText.getText().toString());
- getInputMethodManager(mFindText).hideSoftInputFromWindow(mFindText.getWindowToken(), 0);
- return;
- }
-
- if (viewId == R.id.find_close) {
- hide();
- }
- }
-
- // GeckoEventListener implementation
-
- @Override
- public void handleMessage(String event, JSONObject message) {
- if (event.equals("FindInPage:MatchesCountResult")) {
- onMatchesCountResult(message.optInt("total", 0),
- message.optInt("current", 0),
- message.optInt("limit", 0),
- message.optString("searchString"));
- return;
- }
-
- if (!event.equals("TextSelection:Data") || !REQUEST_ID.equals(message.optString("requestId"))) {
- return;
- }
-
- final String text = message.optString("text");
-
- // Populate an initial find string, virtual keyboard not required.
- if (!TextUtils.isEmpty(text)) {
- // Populate initial selection
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- mFindText.setText(text);
- }
- });
- return;
- }
-
- // Show the virtual keyboard.
- if (mFindText.hasWindowFocus()) {
- getInputMethodManager(mFindText).showSoftInput(mFindText, 0);
- } else {
- // showSoftInput won't work until after the window is focused.
- mFindText.setOnWindowFocusChangeListener(new CustomEditText.OnWindowFocusChangeListener() {
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
- if (!hasFocus)
- return;
-
- mFindText.setOnWindowFocusChangeListener(null);
- getInputMethodManager(mFindText).showSoftInput(mFindText, 0);
- }
- });
- }
- }
-
- /**
- * Request find operation, and update matchCount results (current count and total).
- */
- private void sendRequestToFinderHelper(final String request, final String searchString) {
- GeckoAppShell.sendRequestToGecko(new GeckoRequest(request, searchString) {
- @Override
- public void onResponse(NativeJSObject nativeJSObject) {
- // We don't care about the return value, because `onMatchesCountResult`
- // does the heavy lifting.
- }
-
- @Override
- public void onError(NativeJSObject error) {
- // Gecko didn't respond due to state change, javascript error, etc.
- Log.d(LOGTAG, "No response from Gecko on request to match string: [" +
- searchString + "]");
- updateResult("");
- }
- });
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/FormAssistPopup.java b/mobile/android/base/java/org/mozilla/gecko/FormAssistPopup.java
deleted file mode 100644
index 5c7f932c0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/FormAssistPopup.java
+++ /dev/null
@@ -1,459 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.animation.ViewHelper;
-import org.mozilla.gecko.gfx.FloatSize;
-import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.widget.SwipeDismissListViewTouchListener;
-import org.mozilla.gecko.widget.SwipeDismissListViewTouchListener.OnDismissCallback;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.PointF;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ArrayAdapter;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.RelativeLayout;
-import android.widget.RelativeLayout.LayoutParams;
-import android.widget.TextView;
-
-import java.util.Arrays;
-import java.util.Collection;
-
-public class FormAssistPopup extends RelativeLayout implements GeckoEventListener {
- private final Context mContext;
- private final Animation mAnimation;
-
- private ListView mAutoCompleteList;
- private RelativeLayout mValidationMessage;
- private TextView mValidationMessageText;
- private ImageView mValidationMessageArrow;
- private ImageView mValidationMessageArrowInverted;
-
- private double mX;
- private double mY;
- private double mW;
- private double mH;
-
- private enum PopupType {
- AUTOCOMPLETE,
- VALIDATIONMESSAGE;
- }
- private PopupType mPopupType;
-
- private static final int MAX_VISIBLE_ROWS = 5;
-
- private static int sAutoCompleteMinWidth;
- private static int sAutoCompleteRowHeight;
- private static int sValidationMessageHeight;
- private static int sValidationTextMarginTop;
- private static LayoutParams sValidationTextLayoutNormal;
- private static LayoutParams sValidationTextLayoutInverted;
-
- private static final String LOGTAG = "GeckoFormAssistPopup";
-
- // The blocklist is so short that ArrayList is probably cheaper than HashSet.
- private static final Collection<String> sInputMethodBlocklist = Arrays.asList(
- InputMethods.METHOD_GOOGLE_JAPANESE_INPUT, // bug 775850
- InputMethods.METHOD_OPENWNN_PLUS, // bug 768108
- InputMethods.METHOD_SIMEJI, // bug 768108
- InputMethods.METHOD_SWYPE, // bug 755909
- InputMethods.METHOD_SWYPE_BETA // bug 755909
- );
-
- public FormAssistPopup(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
-
- mAnimation = AnimationUtils.loadAnimation(context, R.anim.grow_fade_in);
- mAnimation.setDuration(75);
-
- setFocusable(false);
-
- GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
- "FormAssist:AutoComplete",
- "FormAssist:ValidationMessage",
- "FormAssist:Hide");
- }
-
- void destroy() {
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
- "FormAssist:AutoComplete",
- "FormAssist:ValidationMessage",
- "FormAssist:Hide");
- }
-
- @Override
- public void handleMessage(String event, JSONObject message) {
- try {
- if (event.equals("FormAssist:AutoComplete")) {
- handleAutoCompleteMessage(message);
- } else if (event.equals("FormAssist:ValidationMessage")) {
- handleValidationMessage(message);
- } else if (event.equals("FormAssist:Hide")) {
- handleHideMessage(message);
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
- }
- }
-
- private void handleAutoCompleteMessage(JSONObject message) throws JSONException {
- final JSONArray suggestions = message.getJSONArray("suggestions");
- final JSONObject rect = message.getJSONObject("rect");
- final boolean isEmpty = message.getBoolean("isEmpty");
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- showAutoCompleteSuggestions(suggestions, rect, isEmpty);
- }
- });
- }
-
- private void handleValidationMessage(JSONObject message) throws JSONException {
- final String validationMessage = message.getString("validationMessage");
- final JSONObject rect = message.getJSONObject("rect");
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- showValidationMessage(validationMessage, rect);
- }
- });
- }
-
- private void handleHideMessage(JSONObject message) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- hide();
- }
- });
- }
-
- private void showAutoCompleteSuggestions(JSONArray suggestions, JSONObject rect, boolean isEmpty) {
- final String inputMethod = InputMethods.getCurrentInputMethod(mContext);
- if (!isEmpty && sInputMethodBlocklist.contains(inputMethod)) {
- // Don't display the form auto-complete popup after the user starts typing
- // to avoid confusing somes IME. See bug 758820 and bug 632744.
- hide();
- return;
- }
-
- if (mAutoCompleteList == null) {
- LayoutInflater inflater = LayoutInflater.from(mContext);
- mAutoCompleteList = (ListView) inflater.inflate(R.layout.autocomplete_list, null);
-
- mAutoCompleteList.setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parentView, View view, int position, long id) {
- // Use the value stored with the autocomplete view, not the label text,
- // since they can be different.
- TextView textView = (TextView) view;
- String value = (String) textView.getTag();
- broadcastGeckoEvent("FormAssist:AutoComplete", value);
- hide();
- }
- });
-
- // Create a ListView-specific touch listener. ListViews are given special treatment because
- // by default they handle touches for their list items... i.e. they're in charge of drawing
- // the pressed state (the list selector), handling list item clicks, etc.
- final SwipeDismissListViewTouchListener touchListener = new SwipeDismissListViewTouchListener(mAutoCompleteList, new OnDismissCallback() {
- @Override
- public void onDismiss(ListView listView, final int position) {
- // Use the value stored with the autocomplete view, not the label text,
- // since they can be different.
- AutoCompleteListAdapter adapter = (AutoCompleteListAdapter) listView.getAdapter();
- Pair<String, String> item = adapter.getItem(position);
-
- // Remove the item from form history.
- broadcastGeckoEvent("FormAssist:Remove", item.second);
-
- // Update the list
- adapter.remove(item);
- adapter.notifyDataSetChanged();
- positionAndShowPopup();
- }
- });
- mAutoCompleteList.setOnTouchListener(touchListener);
-
- // Setting this scroll listener is required to ensure that during ListView scrolling,
- // we don't look for swipes.
- mAutoCompleteList.setOnScrollListener(touchListener.makeScrollListener());
-
- // Setting this recycler listener is required to make sure animated views are reset.
- mAutoCompleteList.setRecyclerListener(touchListener.makeRecyclerListener());
-
- addView(mAutoCompleteList);
- }
-
- AutoCompleteListAdapter adapter = new AutoCompleteListAdapter(mContext, R.layout.autocomplete_list_item);
- adapter.populateSuggestionsList(suggestions);
- mAutoCompleteList.setAdapter(adapter);
-
- if (setGeckoPositionData(rect, true)) {
- positionAndShowPopup();
- }
- }
-
- private void showValidationMessage(String validationMessage, JSONObject rect) {
- if (mValidationMessage == null) {
- LayoutInflater inflater = LayoutInflater.from(mContext);
- mValidationMessage = (RelativeLayout) inflater.inflate(R.layout.validation_message, null);
-
- addView(mValidationMessage);
- mValidationMessageText = (TextView) mValidationMessage.findViewById(R.id.validation_message_text);
-
- sValidationTextMarginTop = (int) (mContext.getResources().getDimension(R.dimen.validation_message_margin_top));
-
- sValidationTextLayoutNormal = new LayoutParams(mValidationMessageText.getLayoutParams());
- sValidationTextLayoutNormal.setMargins(0, sValidationTextMarginTop, 0, 0);
-
- sValidationTextLayoutInverted = new LayoutParams((ViewGroup.MarginLayoutParams) sValidationTextLayoutNormal);
- sValidationTextLayoutInverted.setMargins(0, 0, 0, 0);
-
- mValidationMessageArrow = (ImageView) mValidationMessage.findViewById(R.id.validation_message_arrow);
- mValidationMessageArrowInverted = (ImageView) mValidationMessage.findViewById(R.id.validation_message_arrow_inverted);
- }
-
- mValidationMessageText.setText(validationMessage);
-
- // We need to set the text as selected for the marquee text to work.
- mValidationMessageText.setSelected(true);
-
- if (setGeckoPositionData(rect, false)) {
- positionAndShowPopup();
- }
- }
-
- private boolean setGeckoPositionData(JSONObject rect, boolean isAutoComplete) {
- try {
- mX = rect.getDouble("x");
- mY = rect.getDouble("y");
- mW = rect.getDouble("w");
- mH = rect.getDouble("h");
- } catch (JSONException e) {
- // Bail if we can't get the correct dimensions for the popup.
- Log.e(LOGTAG, "Error getting FormAssistPopup dimensions", e);
- return false;
- }
-
- mPopupType = (isAutoComplete ?
- PopupType.AUTOCOMPLETE : PopupType.VALIDATIONMESSAGE);
- return true;
- }
-
- private void positionAndShowPopup() {
- positionAndShowPopup(GeckoAppShell.getLayerView().getViewportMetrics());
- }
-
- private void positionAndShowPopup(ImmutableViewportMetrics aMetrics) {
- ThreadUtils.assertOnUiThread();
-
- // Don't show the form assist popup when using fullscreen VKB
- InputMethodManager imm =
- (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
- if (imm.isFullscreenMode()) {
- return;
- }
-
- // Hide/show the appropriate popup contents
- if (mAutoCompleteList != null) {
- mAutoCompleteList.setVisibility((mPopupType == PopupType.AUTOCOMPLETE) ? VISIBLE : GONE);
- }
- if (mValidationMessage != null) {
- mValidationMessage.setVisibility((mPopupType == PopupType.AUTOCOMPLETE) ? GONE : VISIBLE);
- }
-
- if (sAutoCompleteMinWidth == 0) {
- Resources res = mContext.getResources();
- sAutoCompleteMinWidth = (int) (res.getDimension(R.dimen.autocomplete_min_width));
- sAutoCompleteRowHeight = (int) (res.getDimension(R.dimen.autocomplete_row_height));
- sValidationMessageHeight = (int) (res.getDimension(R.dimen.validation_message_height));
- }
-
- float zoom = aMetrics.zoomFactor;
-
- // These values correspond to the input box for which we want to
- // display the FormAssistPopup.
- int left = (int) (mX * zoom - aMetrics.viewportRectLeft);
- int top = (int) (mY * zoom - aMetrics.viewportRectTop + GeckoAppShell.getLayerView().getSurfaceTranslation());
- int width = (int) (mW * zoom);
- int height = (int) (mH * zoom);
-
- int popupWidth = LayoutParams.MATCH_PARENT;
- int popupLeft = left < 0 ? 0 : left;
-
- FloatSize viewport = aMetrics.getSize();
-
- // For autocomplete suggestions, if the input is smaller than the screen-width,
- // shrink the popup's width. Otherwise, keep it as MATCH_PARENT.
- if ((mPopupType == PopupType.AUTOCOMPLETE) && (left + width) < viewport.width) {
- popupWidth = left < 0 ? left + width : width;
-
- // Ensure the popup has a minimum width.
- if (popupWidth < sAutoCompleteMinWidth) {
- popupWidth = sAutoCompleteMinWidth;
-
- // Move the popup to the left if there isn't enough room for it.
- if ((popupLeft + popupWidth) > viewport.width) {
- popupLeft = (int) (viewport.width - popupWidth);
- }
- }
- }
-
- int popupHeight;
- if (mPopupType == PopupType.AUTOCOMPLETE) {
- // Limit the amount of visible rows.
- int rows = mAutoCompleteList.getAdapter().getCount();
- if (rows > MAX_VISIBLE_ROWS) {
- rows = MAX_VISIBLE_ROWS;
- }
-
- popupHeight = sAutoCompleteRowHeight * rows;
- } else {
- popupHeight = sValidationMessageHeight;
- }
-
- int popupTop = top + height;
-
- if (mPopupType == PopupType.VALIDATIONMESSAGE) {
- mValidationMessageText.setLayoutParams(sValidationTextLayoutNormal);
- mValidationMessageArrow.setVisibility(VISIBLE);
- mValidationMessageArrowInverted.setVisibility(GONE);
- }
-
- // If the popup doesn't fit below the input box, shrink its height, or
- // see if we can place it above the input instead.
- if ((popupTop + popupHeight) > viewport.height) {
- // Find where the maximum space is, and put the popup there.
- if ((viewport.height - popupTop) > top) {
- // Shrink the height to fit it below the input box.
- popupHeight = (int) (viewport.height - popupTop);
- } else {
- if (popupHeight < top) {
- // No shrinking needed to fit on top.
- popupTop = (top - popupHeight);
- } else {
- // Shrink to available space on top.
- popupTop = 0;
- popupHeight = top;
- }
-
- if (mPopupType == PopupType.VALIDATIONMESSAGE) {
- mValidationMessageText.setLayoutParams(sValidationTextLayoutInverted);
- mValidationMessageArrow.setVisibility(GONE);
- mValidationMessageArrowInverted.setVisibility(VISIBLE);
- }
- }
- }
-
- LayoutParams layoutParams = new LayoutParams(popupWidth, popupHeight);
- layoutParams.setMargins(popupLeft, popupTop, 0, 0);
- setLayoutParams(layoutParams);
- requestLayout();
-
- if (!isShown()) {
- setVisibility(VISIBLE);
- startAnimation(mAnimation);
- }
- }
-
- public void hide() {
- if (isShown()) {
- setVisibility(GONE);
- broadcastGeckoEvent("FormAssist:Hidden", null);
- }
- }
-
- void onTranslationChanged() {
- ThreadUtils.assertOnUiThread();
- if (!isShown()) {
- return;
- }
- positionAndShowPopup();
- }
-
- void onMetricsChanged(final ImmutableViewportMetrics aMetrics) {
- if (!isShown()) {
- return;
- }
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- positionAndShowPopup(aMetrics);
- }
- });
- }
-
- private static void broadcastGeckoEvent(String eventName, String eventData) {
- GeckoAppShell.notifyObservers(eventName, eventData);
- }
-
- private class AutoCompleteListAdapter extends ArrayAdapter<Pair<String, String>> {
- private final LayoutInflater mInflater;
- private final int mTextViewResourceId;
-
- public AutoCompleteListAdapter(Context context, int textViewResourceId) {
- super(context, textViewResourceId);
-
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mTextViewResourceId = textViewResourceId;
- }
-
- // This method takes an array of autocomplete suggestions with label/value properties
- // and adds label/value Pair objects to the array that backs the adapter.
- public void populateSuggestionsList(JSONArray suggestions) {
- try {
- for (int i = 0; i < suggestions.length(); i++) {
- JSONObject suggestion = suggestions.getJSONObject(i);
- String label = suggestion.getString("label");
- String value = suggestion.getString("value");
- add(new Pair<String, String>(label, value));
- }
- } catch (JSONException e) {
- Log.e(LOGTAG, "JSONException", e);
- }
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = mInflater.inflate(mTextViewResourceId, null);
- }
-
- Pair<String, String> item = getItem(position);
- TextView itemView = (TextView) convertView;
-
- // Set the text with the suggestion label
- itemView.setText(item.first);
-
- // Set a tag with the suggestion value
- itemView.setTag(item.second);
-
- return convertView;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoActivity.java b/mobile/android/base/java/org/mozilla/gecko/GeckoActivity.java
deleted file mode 100644
index 774ca6024..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoActivity.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.support.v7.app.AppCompatActivity;
-
-public abstract class GeckoActivity extends AppCompatActivity implements GeckoActivityStatus {
- // has this activity recently started another Gecko activity?
- private boolean mGeckoActivityOpened;
-
- /**
- * Display any resources that show strings or encompass locale-specific
- * representations.
- *
- * onLocaleReady must always be called on the UI thread.
- */
- public void onLocaleReady(final String locale) {
- }
-
- @Override
- public void onPause() {
- super.onPause();
-
- if (getApplication() instanceof GeckoApplication) {
- ((GeckoApplication) getApplication()).onActivityPause(this);
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- if (getApplication() instanceof GeckoApplication) {
- ((GeckoApplication) getApplication()).onActivityResume(this);
- mGeckoActivityOpened = false;
- }
- }
-
- @Override
- public void onCreate(android.os.Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (AppConstants.MOZ_ANDROID_ANR_REPORTER) {
- ANRReporter.register(getApplicationContext());
- }
- }
-
- @Override
- public void onDestroy() {
- if (AppConstants.MOZ_ANDROID_ANR_REPORTER) {
- ANRReporter.unregister();
- }
- super.onDestroy();
- }
-
- @Override
- public void startActivity(Intent intent) {
- mGeckoActivityOpened = checkIfGeckoActivity(intent);
- super.startActivity(intent);
- }
-
- @Override
- public void startActivityForResult(Intent intent, int request) {
- mGeckoActivityOpened = checkIfGeckoActivity(intent);
- super.startActivityForResult(intent, request);
- }
-
- private static boolean checkIfGeckoActivity(Intent intent) {
- // Whenever we call our own activity, the component and its package name is set.
- // If we call an activity from another package, or an open intent (leaving android to resolve)
- // component has a different package name or it is null.
- ComponentName component = intent.getComponent();
- return (component != null &&
- AppConstants.ANDROID_PACKAGE_NAME.equals(component.getPackageName()));
- }
-
- @Override
- public boolean isGeckoActivityOpened() {
- return mGeckoActivityOpened;
- }
-
- public boolean isApplicationInBackground() {
- return ((GeckoApplication) getApplication()).isApplicationInBackground();
- }
-
- @Override
- public void onLowMemory() {
- MemoryMonitor.getInstance().onLowMemory();
- super.onLowMemory();
- }
-
- @Override
- public void onTrimMemory(int level) {
- MemoryMonitor.getInstance().onTrimMemory(level);
- super.onTrimMemory(level);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoActivityStatus.java b/mobile/android/base/java/org/mozilla/gecko/GeckoActivityStatus.java
deleted file mode 100644
index ce6b8abd0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoActivityStatus.java
+++ /dev/null
@@ -1,10 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-public interface GeckoActivityStatus {
- public boolean isGeckoActivityOpened();
- public boolean isFinishing(); // typically from android.app.Activity
-};
diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
deleted file mode 100644
index 05fa2bbf8..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ /dev/null
@@ -1,2878 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.UrlAnnotations;
-import org.mozilla.gecko.gfx.BitmapUtils;
-import org.mozilla.gecko.gfx.FullScreenState;
-import org.mozilla.gecko.gfx.LayerView;
-import org.mozilla.gecko.health.HealthRecorder;
-import org.mozilla.gecko.health.SessionInformation;
-import org.mozilla.gecko.health.StubbedHealthRecorder;
-import org.mozilla.gecko.home.HomeConfig.PanelType;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.menu.GeckoMenu;
-import org.mozilla.gecko.menu.GeckoMenuInflater;
-import org.mozilla.gecko.menu.MenuPanel;
-import org.mozilla.gecko.notifications.NotificationClient;
-import org.mozilla.gecko.notifications.NotificationHelper;
-import org.mozilla.gecko.util.IntentUtils;
-import org.mozilla.gecko.mozglue.SafeIntent;
-import org.mozilla.gecko.mozglue.GeckoLoader;
-import org.mozilla.gecko.permissions.Permissions;
-import org.mozilla.gecko.preferences.ClearOnShutdownPref;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.prompts.PromptService;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.tabqueue.TabQueueHelper;
-import org.mozilla.gecko.text.FloatingToolbarTextSelection;
-import org.mozilla.gecko.text.TextSelection;
-import org.mozilla.gecko.updater.UpdateServiceHelper;
-import org.mozilla.gecko.util.ActivityResultHandler;
-import org.mozilla.gecko.util.ActivityUtils;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.FileUtils;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.GeckoRequest;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.NativeEventListener;
-import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.gecko.util.PrefUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Configuration;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.hardware.Sensor;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.StrictMode;
-import android.provider.ContactsContract;
-import android.provider.MediaStore.Images.Media;
-import android.support.annotation.WorkerThread;
-import android.support.design.widget.Snackbar;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Base64;
-import android.util.Log;
-import android.util.SparseBooleanArray;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.OrientationEventListener;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.Window;
-import android.widget.AbsoluteLayout;
-import android.widget.AdapterView;
-import android.widget.Button;
-import android.widget.FrameLayout;
-import android.widget.ListView;
-import android.widget.RelativeLayout;
-import android.widget.SimpleAdapter;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.ref.WeakReference;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-public abstract class GeckoApp
- extends GeckoActivity
- implements
- ContextGetter,
- GeckoAppShell.GeckoInterface,
- GeckoEventListener,
- GeckoMenu.Callback,
- GeckoMenu.MenuPresenter,
- NativeEventListener,
- Tabs.OnTabsChangedListener,
- ViewTreeObserver.OnGlobalLayoutListener {
-
- private static final String LOGTAG = "GeckoApp";
- private static final long ONE_DAY_MS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS);
-
- public static final String ACTION_ALERT_CALLBACK = "org.mozilla.gecko.ALERT_CALLBACK";
- public static final String ACTION_HOMESCREEN_SHORTCUT = "org.mozilla.gecko.BOOKMARK";
- public static final String ACTION_DEBUG = "org.mozilla.gecko.DEBUG";
- public static final String ACTION_LAUNCH_SETTINGS = "org.mozilla.gecko.SETTINGS";
- public static final String ACTION_LOAD = "org.mozilla.gecko.LOAD";
- public static final String ACTION_INIT_PW = "org.mozilla.gecko.INIT_PW";
- public static final String ACTION_SWITCH_TAB = "org.mozilla.gecko.SWITCH_TAB";
-
- public static final String INTENT_REGISTER_STUMBLER_LISTENER = "org.mozilla.gecko.STUMBLER_REGISTER_LOCAL_LISTENER";
-
- public static final String EXTRA_STATE_BUNDLE = "stateBundle";
-
- public static final String LAST_SELECTED_TAB = "lastSelectedTab";
-
- public static final String PREFS_ALLOW_STATE_BUNDLE = "allowStateBundle";
- public static final String PREFS_VERSION_CODE = "versionCode";
- public static final String PREFS_WAS_STOPPED = "wasStopped";
- public static final String PREFS_CRASHED_COUNT = "crashedCount";
- public static final String PREFS_CLEANUP_TEMP_FILES = "cleanupTempFiles";
-
- public static final String SAVED_STATE_IN_BACKGROUND = "inBackground";
- public static final String SAVED_STATE_PRIVATE_SESSION = "privateSession";
-
- // Delay before running one-time "cleanup" tasks that may be needed
- // after a version upgrade.
- private static final int CLEANUP_DEFERRAL_SECONDS = 15;
-
- private static boolean sAlreadyLoaded;
-
- private static WeakReference<GeckoApp> lastActiveGeckoApp;
-
- protected RelativeLayout mRootLayout;
- protected RelativeLayout mMainLayout;
-
- protected RelativeLayout mGeckoLayout;
- private OrientationEventListener mCameraOrientationEventListener;
- public List<GeckoAppShell.AppStateListener> mAppStateListeners = new LinkedList<GeckoAppShell.AppStateListener>();
- protected MenuPanel mMenuPanel;
- protected Menu mMenu;
- protected boolean mIsRestoringActivity;
-
- /** Tells if we're aborting app launch, e.g. if this is an unsupported device configuration. */
- protected boolean mIsAbortingAppLaunch;
-
- private PromptService mPromptService;
- protected TextSelection mTextSelection;
-
- protected DoorHangerPopup mDoorHangerPopup;
- protected FormAssistPopup mFormAssistPopup;
-
-
- protected GeckoView mLayerView;
- private AbsoluteLayout mPluginContainer;
-
- private FullScreenHolder mFullScreenPluginContainer;
- private View mFullScreenPluginView;
-
- private final HashMap<String, PowerManager.WakeLock> mWakeLocks = new HashMap<String, PowerManager.WakeLock>();
-
- protected boolean mLastSessionCrashed;
- protected boolean mShouldRestore;
- private boolean mSessionRestoreParsingFinished = false;
-
- private EventDispatcher eventDispatcher;
-
- private int lastSelectedTabId = -1;
-
- private static final class LastSessionParser extends SessionParser {
- private JSONArray tabs;
- private JSONObject windowObject;
- private boolean isExternalURL;
-
- private boolean selectNextTab;
- private boolean tabsWereSkipped;
- private boolean tabsWereProcessed;
-
- public LastSessionParser(JSONArray tabs, JSONObject windowObject, boolean isExternalURL) {
- this.tabs = tabs;
- this.windowObject = windowObject;
- this.isExternalURL = isExternalURL;
- }
-
- public boolean allTabsSkipped() {
- return tabsWereSkipped && !tabsWereProcessed;
- }
-
- @Override
- public void onTabRead(final SessionTab sessionTab) {
- if (sessionTab.isAboutHomeWithoutHistory()) {
- // This is a tab pointing to about:home with no history. We won't restore
- // this tab. If we end up restoring no tabs then the browser will decide
- // whether it needs to open about:home or a different 'homepage'. If we'd
- // always restore about:home only tabs then we'd never open the homepage.
- // See bug 1261008.
-
- if (sessionTab.isSelected()) {
- // Unfortunately this tab is the selected tab. Let's just try to select
- // the first tab. If we haven't restored any tabs so far then remember
- // to select the next tab that gets restored.
-
- if (!Tabs.getInstance().selectLastTab()) {
- selectNextTab = true;
- }
- }
-
- // Do not restore this tab.
- tabsWereSkipped = true;
- return;
- }
-
- tabsWereProcessed = true;
-
- JSONObject tabObject = sessionTab.getTabObject();
-
- int flags = Tabs.LOADURL_NEW_TAB;
- flags |= ((isExternalURL || !sessionTab.isSelected()) ? Tabs.LOADURL_DELAY_LOAD : 0);
- flags |= (tabObject.optBoolean("desktopMode") ? Tabs.LOADURL_DESKTOP : 0);
- flags |= (tabObject.optBoolean("isPrivate") ? Tabs.LOADURL_PRIVATE : 0);
-
- final Tab tab = Tabs.getInstance().loadUrl(sessionTab.getUrl(), flags);
-
- if (selectNextTab) {
- // We did not restore the selected tab previously. Now let's select this tab.
- Tabs.getInstance().selectTab(tab.getId());
- selectNextTab = false;
- }
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- tab.updateTitle(sessionTab.getTitle());
- }
- });
-
- try {
- tabObject.put("tabId", tab.getId());
- } catch (JSONException e) {
- Log.e(LOGTAG, "JSON error", e);
- }
- tabs.put(tabObject);
- }
-
- @Override
- public void onClosedTabsRead(final JSONArray closedTabData) throws JSONException {
- windowObject.put("closedTabs", closedTabData);
- }
- };
-
- protected boolean mInitialized;
- protected boolean mWindowFocusInitialized;
- private Telemetry.Timer mJavaUiStartupTimer;
- private Telemetry.Timer mGeckoReadyStartupTimer;
-
- private String mPrivateBrowsingSession;
-
- private volatile HealthRecorder mHealthRecorder;
- private volatile Locale mLastLocale;
-
- protected Intent mRestartIntent;
-
- private boolean mWasFirstTabShownAfterActivityUnhidden;
-
- abstract public int getLayout();
-
- protected void processTabQueue() {};
-
- protected void openQueuedTabs() {};
-
- @SuppressWarnings("serial")
- class SessionRestoreException extends Exception {
- public SessionRestoreException(Exception e) {
- super(e);
- }
-
- public SessionRestoreException(String message) {
- super(message);
- }
- }
-
- void toggleChrome(final boolean aShow) { }
-
- void focusChrome() { }
-
- @Override
- public Context getContext() {
- return this;
- }
-
- @Override
- public SharedPreferences getSharedPreferences() {
- return GeckoSharedPrefs.forApp(this);
- }
-
- @Override
- public Activity getActivity() {
- return this;
- }
-
- @Override
- public void addAppStateListener(GeckoAppShell.AppStateListener listener) {
- mAppStateListeners.add(listener);
- }
-
- @Override
- public void removeAppStateListener(GeckoAppShell.AppStateListener listener) {
- mAppStateListeners.remove(listener);
- }
-
- @Override
- public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
- // When a tab is closed, it is always unselected first.
- // When a tab is unselected, another tab is always selected first.
- switch (msg) {
- case UNSELECTED:
- break;
-
- case LOCATION_CHANGE:
- // We only care about location change for the selected tab.
- if (!Tabs.getInstance().isSelectedTab(tab))
- break;
- // Fall through...
- case SELECTED:
- invalidateOptionsMenu();
- if (mFormAssistPopup != null)
- mFormAssistPopup.hide();
- break;
-
- case DESKTOP_MODE_CHANGE:
- if (Tabs.getInstance().isSelectedTab(tab))
- invalidateOptionsMenu();
- break;
- }
- }
-
- public void refreshChrome() { }
-
- @Override
- public void invalidateOptionsMenu() {
- if (mMenu == null) {
- return;
- }
-
- onPrepareOptionsMenu(mMenu);
-
- super.invalidateOptionsMenu();
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- mMenu = menu;
-
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.gecko_app_menu, mMenu);
- return true;
- }
-
- @Override
- public MenuInflater getMenuInflater() {
- return new GeckoMenuInflater(this);
- }
-
- public MenuPanel getMenuPanel() {
- if (mMenuPanel == null) {
- onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, null);
- invalidateOptionsMenu();
- }
- return mMenuPanel;
- }
-
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- return onOptionsItemSelected(item);
- }
-
- @Override
- public boolean onMenuItemLongClick(MenuItem item) {
- return false;
- }
-
- @Override
- public void openMenu() {
- openOptionsMenu();
- }
-
- @Override
- public void showMenu(final View menu) {
- // On devices using the custom menu, focus is cleared from the menu when its tapped.
- // Close and then reshow it to avoid these issues. See bug 794581 and bug 968182.
- closeMenu();
-
- // Post the reshow code back to the UI thread to avoid some optimizations Android
- // has put in place for menus that hide/show themselves quickly. See bug 985400.
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- mMenuPanel.removeAllViews();
- mMenuPanel.addView(menu);
- openOptionsMenu();
- }
- });
- }
-
- @Override
- public void closeMenu() {
- closeOptionsMenu();
- }
-
- @Override
- public View onCreatePanelView(int featureId) {
- if (featureId == Window.FEATURE_OPTIONS_PANEL) {
- if (mMenuPanel == null) {
- mMenuPanel = new MenuPanel(this, null);
- } else {
- // Prepare the panel every time before showing the menu.
- onPreparePanel(featureId, mMenuPanel, mMenu);
- }
-
- return mMenuPanel;
- }
-
- return super.onCreatePanelView(featureId);
- }
-
- @Override
- public boolean onCreatePanelMenu(int featureId, Menu menu) {
- if (featureId == Window.FEATURE_OPTIONS_PANEL) {
- if (mMenuPanel == null) {
- mMenuPanel = (MenuPanel) onCreatePanelView(featureId);
- }
-
- GeckoMenu gMenu = new GeckoMenu(this, null);
- gMenu.setCallback(this);
- gMenu.setMenuPresenter(this);
- menu = gMenu;
- mMenuPanel.addView(gMenu);
-
- return onCreateOptionsMenu(menu);
- }
-
- return super.onCreatePanelMenu(featureId, menu);
- }
-
- @Override
- public boolean onPreparePanel(int featureId, View view, Menu menu) {
- if (featureId == Window.FEATURE_OPTIONS_PANEL) {
- return onPrepareOptionsMenu(menu);
- }
-
- return super.onPreparePanel(featureId, view, menu);
- }
-
- @Override
- public boolean onMenuOpened(int featureId, Menu menu) {
- // exit full-screen mode whenever the menu is opened
- if (mLayerView != null && mLayerView.isFullScreen()) {
- GeckoAppShell.notifyObservers("FullScreen:Exit", null);
- }
-
- if (featureId == Window.FEATURE_OPTIONS_PANEL) {
- if (mMenu == null) {
- // getMenuPanel() will force the creation of the menu as well
- MenuPanel panel = getMenuPanel();
- onPreparePanel(featureId, panel, mMenu);
- }
-
- // Scroll custom menu to the top
- if (mMenuPanel != null)
- mMenuPanel.scrollTo(0, 0);
-
- return true;
- }
-
- return super.onMenuOpened(featureId, menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (item.getItemId() == R.id.quit) {
- // Make sure the Guest Browsing notification goes away when we quit.
- GuestSession.hideNotification(this);
-
- final SharedPreferences prefs = GeckoSharedPrefs.forProfile(this);
- final Set<String> clearSet =
- PrefUtils.getStringSet(prefs, ClearOnShutdownPref.PREF, new HashSet<String>());
-
- final JSONObject clearObj = new JSONObject();
- for (String clear : clearSet) {
- try {
- clearObj.put(clear, true);
- } catch (JSONException ex) {
- Log.e(LOGTAG, "Error adding clear object " + clear, ex);
- }
- }
-
- final JSONObject res = new JSONObject();
- try {
- res.put("sanitize", clearObj);
- } catch (JSONException ex) {
- Log.e(LOGTAG, "Error adding sanitize object", ex);
- }
-
- // If the user has opted out of session restore, and does want to clear history
- // we also want to prevent the current session info from being saved.
- if (clearObj.has("private.data.history")) {
- final String sessionRestore = getSessionRestorePreference(getSharedPreferences());
- try {
- res.put("dontSaveSession", "quit".equals(sessionRestore));
- } catch (JSONException ex) {
- Log.e(LOGTAG, "Error adding session restore data", ex);
- }
- }
-
- GeckoAppShell.notifyObservers("Browser:Quit", res.toString());
- // We don't call doShutdown() here because this creates a race condition which can
- // cause the clearing of private data to fail. Instead, we shut down the UI only after
- // we're done sanitizing.
- return true;
- }
-
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public void onOptionsMenuClosed(Menu menu) {
- mMenuPanel.removeAllViews();
- mMenuPanel.addView((GeckoMenu) mMenu);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- // Handle hardware menu key presses separately so that we can show a custom menu in some cases.
- if (keyCode == KeyEvent.KEYCODE_MENU) {
- openOptionsMenu();
- return true;
- }
-
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
-
- outState.putBoolean(SAVED_STATE_IN_BACKGROUND, isApplicationInBackground());
- outState.putString(SAVED_STATE_PRIVATE_SESSION, mPrivateBrowsingSession);
- outState.putInt(LAST_SELECTED_TAB, lastSelectedTabId);
- }
-
- @Override
- protected void onRestoreInstanceState(final Bundle inState) {
- lastSelectedTabId = inState.getInt(LAST_SELECTED_TAB);
- }
-
- public void addTab() { }
-
- public void addPrivateTab() { }
-
- public void showNormalTabs() { }
-
- public void showPrivateTabs() { }
-
- public void hideTabs() { }
-
- /**
- * Close the tab UI indirectly (not as the result of a direct user
- * action). This does not force the UI to close; for example in Firefox
- * tablet mode it will remain open unless the user explicitly closes it.
- *
- * @return True if the tab UI was hidden.
- */
- public boolean autoHideTabs() { return false; }
-
- @Override
- public boolean areTabsShown() { return false; }
-
- @Override
- public void handleMessage(final String event, final NativeJSObject message,
- final EventCallback callback) {
- if ("Accessibility:Ready".equals(event)) {
- GeckoAccessibility.updateAccessibilitySettings(this);
-
- } else if ("Bookmark:Insert".equals(event)) {
- final String url = message.getString("url");
- final String title = message.getString("title");
- final Context context = this;
- final BrowserDB db = BrowserDB.from(getProfile());
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- final boolean bookmarkAdded = db.addBookmark(getContentResolver(), title, url);
- final int resId = bookmarkAdded ? R.string.bookmark_added : R.string.bookmark_already_added;
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- SnackbarBuilder.builder(GeckoApp.this)
- .message(resId)
- .duration(Snackbar.LENGTH_LONG)
- .buildAndShow();
- }
- });
- }
- });
-
- } else if ("Contact:Add".equals(event)) {
- final String email = message.optString("email", null);
- final String phone = message.optString("phone", null);
- if (email != null) {
- Uri contactUri = Uri.parse(email);
- Intent i = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT, contactUri);
- startActivity(i);
- } else if (phone != null) {
- Uri contactUri = Uri.parse(phone);
- Intent i = new Intent(ContactsContract.Intents.SHOW_OR_CREATE_CONTACT, contactUri);
- startActivity(i);
- } else {
- // something went wrong.
- Log.e(LOGTAG, "Received Contact:Add message with no email nor phone number");
- }
-
- } else if ("DevToolsAuth:Scan".equals(event)) {
- DevToolsAuthHelper.scan(this, callback);
-
- } else if ("DOMFullScreen:Start".equals(event)) {
- // Local ref to layerView for thread safety
- LayerView layerView = mLayerView;
- if (layerView != null) {
- layerView.setFullScreenState(message.getBoolean("rootElement")
- ? FullScreenState.ROOT_ELEMENT : FullScreenState.NON_ROOT_ELEMENT);
- }
-
- } else if ("DOMFullScreen:Stop".equals(event)) {
- // Local ref to layerView for thread safety
- LayerView layerView = mLayerView;
- if (layerView != null) {
- layerView.setFullScreenState(FullScreenState.NONE);
- }
-
- } else if ("Image:SetAs".equals(event)) {
- String src = message.getString("url");
- setImageAs(src);
-
- } else if ("Locale:Set".equals(event)) {
- setLocale(message.getString("locale"));
-
- } else if ("Permissions:Data".equals(event)) {
- final NativeJSObject[] permissions = message.getObjectArray("permissions");
- showSiteSettingsDialog(permissions);
-
- } else if ("PrivateBrowsing:Data".equals(event)) {
- mPrivateBrowsingSession = message.optString("session", null);
-
- } else if ("Session:StatePurged".equals(event)) {
- onStatePurged();
-
- } else if ("Sanitize:Finished".equals(event)) {
- if (message.getBoolean("shutdown")) {
- // Gecko is shutting down and has called our sanitize handlers,
- // so we can start exiting, too.
- doShutdown();
- }
-
- } else if ("Share:Text".equals(event)) {
- final String text = message.getString("text");
- final Tab tab = Tabs.getInstance().getSelectedTab();
- String title = "";
- if (tab != null) {
- title = tab.getDisplayTitle();
- }
- IntentHelper.openUriExternal(text, "text/plain", "", "", Intent.ACTION_SEND, title, false);
-
- // Context: Sharing via chrome list (no explicit session is active)
- Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "text");
-
- } else if ("Snackbar:Show".equals(event)) {
- SnackbarBuilder.builder(this)
- .fromEvent(message)
- .callback(callback)
- .buildAndShow();
-
- } else if ("SystemUI:Visibility".equals(event)) {
- setSystemUiVisible(message.getBoolean("visible"));
-
- } else if ("ToggleChrome:Focus".equals(event)) {
- focusChrome();
-
- } else if ("ToggleChrome:Hide".equals(event)) {
- toggleChrome(false);
-
- } else if ("ToggleChrome:Show".equals(event)) {
- toggleChrome(true);
-
- } else if ("Update:Check".equals(event)) {
- UpdateServiceHelper.checkForUpdate(this);
- } else if ("Update:Download".equals(event)) {
- UpdateServiceHelper.downloadUpdate(this);
- } else if ("Update:Install".equals(event)) {
- UpdateServiceHelper.applyUpdate(this);
- } else if ("RuntimePermissions:Prompt".equals(event)) {
- String[] permissions = message.getStringArray("permissions");
- if (callback == null || permissions == null) {
- return;
- }
-
- Permissions.from(this)
- .withPermissions(permissions)
- .andFallback(new Runnable() {
- @Override
- public void run() {
- callback.sendSuccess(false);
- }
- })
- .run(new Runnable() {
- @Override
- public void run() {
- callback.sendSuccess(true);
- }
- });
- }
- }
-
- @Override
- public void handleMessage(String event, JSONObject message) {
- try {
- if (event.equals("Gecko:Ready")) {
- mGeckoReadyStartupTimer.stop();
- geckoConnected();
-
- // This method is already running on the background thread, so we
- // know that mHealthRecorder will exist. That doesn't stop us being
- // paranoid.
- // This method is cheap, so don't spawn a new runnable.
- final HealthRecorder rec = mHealthRecorder;
- if (rec != null) {
- rec.recordGeckoStartupTime(mGeckoReadyStartupTimer.getElapsed());
- }
-
- GeckoApplication.get().onDelayedStartup();
-
- } else if (event.equals("Gecko:Exited")) {
- // Gecko thread exited first; let GeckoApp die too.
- doShutdown();
- return;
-
- } else if (event.equals("Accessibility:Event")) {
- GeckoAccessibility.sendAccessibilityEvent(message);
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
- }
- }
-
- void onStatePurged() { }
-
- /**
- * @param permissions
- * Array of JSON objects to represent site permissions.
- * Example: { type: "offline-app", setting: "Store Offline Data", value: "Allow" }
- */
- private void showSiteSettingsDialog(final NativeJSObject[] permissions) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(R.string.site_settings_title);
-
- final ArrayList<HashMap<String, String>> itemList =
- new ArrayList<HashMap<String, String>>();
- for (final NativeJSObject permObj : permissions) {
- final HashMap<String, String> map = new HashMap<String, String>();
- map.put("setting", permObj.getString("setting"));
- map.put("value", permObj.getString("value"));
- itemList.add(map);
- }
-
- // setMultiChoiceItems doesn't support using an adapter, so we're creating a hack with
- // setSingleChoiceItems and changing the choiceMode below when we create the dialog
- builder.setSingleChoiceItems(new SimpleAdapter(
- GeckoApp.this,
- itemList,
- R.layout.site_setting_item,
- new String[] { "setting", "value" },
- new int[] { R.id.setting, R.id.value }
- ), -1, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) { }
- });
-
- builder.setPositiveButton(R.string.site_settings_clear, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- ListView listView = ((AlertDialog) dialog).getListView();
- SparseBooleanArray checkedItemPositions = listView.getCheckedItemPositions();
-
- // An array of the indices of the permissions we want to clear
- JSONArray permissionsToClear = new JSONArray();
- for (int i = 0; i < checkedItemPositions.size(); i++)
- if (checkedItemPositions.get(i))
- permissionsToClear.put(i);
-
- GeckoAppShell.notifyObservers("Permissions:Clear", permissionsToClear.toString());
- }
- });
-
- builder.setNegativeButton(R.string.site_settings_cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- dialog.cancel();
- }
- });
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- AlertDialog dialog = builder.create();
- dialog.show();
-
- final ListView listView = dialog.getListView();
- if (listView != null) {
- listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
- }
-
- final Button clearButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
- clearButton.setEnabled(false);
-
- dialog.getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
- if (listView.getCheckedItemCount() == 0) {
- clearButton.setEnabled(false);
- } else {
- clearButton.setEnabled(true);
- }
- }
- });
- }
- });
- }
-
-
-
- /* package */ void addFullScreenPluginView(View view) {
- if (mFullScreenPluginView != null) {
- Log.w(LOGTAG, "Already have a fullscreen plugin view");
- return;
- }
-
- setFullScreen(true);
-
- view.setWillNotDraw(false);
- if (view instanceof SurfaceView) {
- ((SurfaceView) view).setZOrderOnTop(true);
- }
-
- mFullScreenPluginContainer = new FullScreenHolder(this);
-
- FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT,
- Gravity.CENTER);
- mFullScreenPluginContainer.addView(view, layoutParams);
-
-
- FrameLayout decor = (FrameLayout)getWindow().getDecorView();
- decor.addView(mFullScreenPluginContainer, layoutParams);
-
- mFullScreenPluginView = view;
- }
-
- @Override
- public void addPluginView(final View view) {
-
- if (ThreadUtils.isOnUiThread()) {
- addFullScreenPluginView(view);
- } else {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- addFullScreenPluginView(view);
- }
- });
- }
- }
-
- /* package */ void removeFullScreenPluginView(View view) {
- if (mFullScreenPluginView == null) {
- Log.w(LOGTAG, "Don't have a fullscreen plugin view");
- return;
- }
-
- if (mFullScreenPluginView != view) {
- Log.w(LOGTAG, "Passed view is not the current full screen view");
- return;
- }
-
- mFullScreenPluginContainer.removeView(mFullScreenPluginView);
-
- // We need do do this on the next iteration in order to avoid
- // a deadlock, see comment below in FullScreenHolder
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- mLayerView.showSurface();
- }
- });
-
- FrameLayout decor = (FrameLayout)getWindow().getDecorView();
- decor.removeView(mFullScreenPluginContainer);
-
- mFullScreenPluginView = null;
-
- GeckoScreenOrientation.getInstance().unlock();
- setFullScreen(false);
- }
-
- @Override
- public void removePluginView(final View view) {
- if (ThreadUtils.isOnUiThread()) {
- removePluginView(view);
- } else {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- removeFullScreenPluginView(view);
- }
- });
- }
- }
-
- // This method starts downloading an image synchronously and displays the Chooser activity to set the image as wallpaper.
- private void setImageAs(final String aSrc) {
- boolean isDataURI = aSrc.startsWith("data:");
- Bitmap image = null;
- InputStream is = null;
- ByteArrayOutputStream os = null;
- try {
- if (isDataURI) {
- int dataStart = aSrc.indexOf(",");
- byte[] buf = Base64.decode(aSrc.substring(dataStart + 1), Base64.DEFAULT);
- image = BitmapUtils.decodeByteArray(buf);
- } else {
- int byteRead;
- byte[] buf = new byte[4192];
- os = new ByteArrayOutputStream();
- URL url = new URL(aSrc);
- is = url.openStream();
-
- // Cannot read from same stream twice. Also, InputStream from
- // URL does not support reset. So converting to byte array.
-
- while ((byteRead = is.read(buf)) != -1) {
- os.write(buf, 0, byteRead);
- }
- byte[] imgBuffer = os.toByteArray();
- image = BitmapUtils.decodeByteArray(imgBuffer);
- }
- if (image != null) {
- // Some devices don't have a DCIM folder and the Media.insertImage call will fail.
- File dcimDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
-
- if (!dcimDir.mkdirs() && !dcimDir.isDirectory()) {
- SnackbarBuilder.builder(this)
- .message(R.string.set_image_path_fail)
- .duration(Snackbar.LENGTH_LONG)
- .buildAndShow();
- return;
- }
- String path = Media.insertImage(getContentResolver(), image, null, null);
- if (path == null) {
- SnackbarBuilder.builder(this)
- .message(R.string.set_image_path_fail)
- .duration(Snackbar.LENGTH_LONG)
- .buildAndShow();
- return;
- }
- final Intent intent = new Intent(Intent.ACTION_ATTACH_DATA);
- intent.addCategory(Intent.CATEGORY_DEFAULT);
- intent.setData(Uri.parse(path));
-
- // Removes the image from storage once the chooser activity ends.
- Intent chooser = Intent.createChooser(intent, getString(R.string.set_image_chooser_title));
- ActivityResultHandler handler = new ActivityResultHandler() {
- @Override
- public void onActivityResult (int resultCode, Intent data) {
- getContentResolver().delete(intent.getData(), null, null);
- }
- };
- ActivityHandlerHelper.startIntentForActivity(this, chooser, handler);
- } else {
- SnackbarBuilder.builder(this)
- .message(R.string.set_image_fail)
- .duration(Snackbar.LENGTH_LONG)
- .buildAndShow();
- }
- } catch (OutOfMemoryError ome) {
- Log.e(LOGTAG, "Out of Memory when converting to byte array", ome);
- } catch (IOException ioe) {
- Log.e(LOGTAG, "I/O Exception while setting wallpaper", ioe);
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException ioe) {
- Log.w(LOGTAG, "I/O Exception while closing stream", ioe);
- }
- }
- if (os != null) {
- try {
- os.close();
- } catch (IOException ioe) {
- Log.w(LOGTAG, "I/O Exception while closing stream", ioe);
- }
- }
- }
- }
-
- private int getBitmapSampleSize(BitmapFactory.Options options, int idealWidth, int idealHeight) {
- int width = options.outWidth;
- int height = options.outHeight;
- int inSampleSize = 1;
- if (height > idealHeight || width > idealWidth) {
- if (width > height) {
- inSampleSize = Math.round((float)height / idealHeight);
- } else {
- inSampleSize = Math.round((float)width / idealWidth);
- }
- }
- return inSampleSize;
- }
-
- public void requestRender() {
- mLayerView.requestRender();
- }
-
- @Override
- public void setFullScreen(final boolean fullscreen) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- ActivityUtils.setFullScreen(GeckoApp.this, fullscreen);
- }
- });
- }
-
- /**
- * Check and start the Java profiler if MOZ_PROFILER_STARTUP env var is specified.
- **/
- protected static void earlyStartJavaSampler(SafeIntent intent) {
- String env = intent.getStringExtra("env0");
- for (int i = 1; env != null; i++) {
- if (env.startsWith("MOZ_PROFILER_STARTUP=")) {
- if (!env.endsWith("=")) {
- GeckoJavaSampler.start(10, 1000);
- Log.d(LOGTAG, "Profiling Java on startup");
- }
- break;
- }
- env = intent.getStringExtra("env" + i);
- }
- }
-
- /**
- * Called when the activity is first created.
- *
- * Here we initialize all of our profile settings, Firefox Health Report,
- * and other one-shot constructions.
- **/
- @Override
- public void onCreate(Bundle savedInstanceState) {
- GeckoAppShell.ensureCrashHandling();
-
- eventDispatcher = new EventDispatcher();
-
- // Enable Android Strict Mode for developers' local builds (the "default" channel).
- if ("default".equals(AppConstants.MOZ_UPDATE_CHANNEL)) {
- enableStrictMode();
- }
-
- if (!HardwareUtils.isSupportedSystem()) {
- // This build does not support the Android version of the device: Show an error and finish the app.
- mIsAbortingAppLaunch = true;
- super.onCreate(savedInstanceState);
- showSDKVersionError();
- finish();
- return;
- }
-
- // The clock starts...now. Better hurry!
- mJavaUiStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_JAVAUI");
- mGeckoReadyStartupTimer = new Telemetry.UptimeTimer("FENNEC_STARTUP_TIME_GECKOREADY");
-
- final SafeIntent intent = new SafeIntent(getIntent());
-
- earlyStartJavaSampler(intent);
-
- // GeckoLoader wants to dig some environment variables out of the
- // incoming intent, so pass it in here. GeckoLoader will do its
- // business later and dispose of the reference.
- GeckoLoader.setLastIntent(intent);
-
- // Workaround for <http://code.google.com/p/android/issues/detail?id=20915>.
- try {
- Class.forName("android.os.AsyncTask");
- } catch (ClassNotFoundException e) { }
-
- MemoryMonitor.getInstance().init(getApplicationContext());
-
- // GeckoAppShell is tightly coupled to us, rather than
- // the app context, because various parts of Fennec (e.g.,
- // GeckoScreenOrientation) use GAS to access the Activity in
- // the guise of fetching a Context.
- // When that's fixed, `this` can change to
- // `(GeckoApplication) getApplication()` here.
- GeckoAppShell.setContextGetter(this);
- GeckoAppShell.setGeckoInterface(this);
-
- // Tell Stumbler to register a local broadcast listener to listen for preference intents.
- // We do this via intents since we can't easily access Stumbler directly,
- // as it might be compiled outside of Fennec.
- getApplicationContext().sendBroadcast(
- new Intent(INTENT_REGISTER_STUMBLER_LISTENER)
- );
-
- // Did the OS locale change while we were backgrounded? If so,
- // we need to die so that Gecko will re-init add-ons that touch
- // the UI.
- // This is using a sledgehammer to crack a nut, but it'll do for
- // now.
- // Our OS locale pref will be detected as invalid after the
- // restart, and will be propagated to Gecko accordingly, so there's
- // no need to touch that here.
- if (BrowserLocaleManager.getInstance().systemLocaleDidChange()) {
- Log.i(LOGTAG, "System locale changed. Restarting.");
- doRestart();
- return;
- }
-
- if (sAlreadyLoaded) {
- // This happens when the GeckoApp activity is destroyed by Android
- // without killing the entire application (see Bug 769269).
- mIsRestoringActivity = true;
- Telemetry.addToHistogram("FENNEC_RESTORING_ACTIVITY", 1);
-
- } else {
- final String action = intent.getAction();
- final String args = intent.getStringExtra("args");
-
- sAlreadyLoaded = true;
- GeckoThread.init(/* profile */ null, args, action,
- /* debugging */ ACTION_DEBUG.equals(action));
-
- // Speculatively pre-fetch the profile in the background.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- getProfile();
- }
- });
-
- final String uri = getURIFromIntent(intent);
- if (!TextUtils.isEmpty(uri)) {
- // Start a speculative connection as soon as Gecko loads.
- GeckoThread.speculativeConnect(uri);
- }
- }
-
- // GeckoThread has to register for "Gecko:Ready" first, so GeckoApp registers
- // for events after initializing GeckoThread but before launching it.
-
- getAppEventDispatcher().registerGeckoThreadListener((GeckoEventListener)this,
- "Gecko:Ready",
- "Gecko:Exited",
- "Accessibility:Event");
-
- getAppEventDispatcher().registerGeckoThreadListener((NativeEventListener)this,
- "Accessibility:Ready",
- "Bookmark:Insert",
- "Contact:Add",
- "DevToolsAuth:Scan",
- "DOMFullScreen:Start",
- "DOMFullScreen:Stop",
- "Image:SetAs",
- "Locale:Set",
- "Permissions:Data",
- "PrivateBrowsing:Data",
- "RuntimePermissions:Prompt",
- "Sanitize:Finished",
- "Session:StatePurged",
- "Share:Text",
- "Snackbar:Show",
- "SystemUI:Visibility",
- "ToggleChrome:Focus",
- "ToggleChrome:Hide",
- "ToggleChrome:Show",
- "Update:Check",
- "Update:Download",
- "Update:Install");
-
- GeckoThread.launch();
-
- Bundle stateBundle = IntentUtils.getBundleExtraSafe(getIntent(), EXTRA_STATE_BUNDLE);
- if (stateBundle != null) {
- // Use the state bundle if it was given as an intent extra. This is
- // only intended to be used internally via Robocop, so a boolean
- // is read from a private shared pref to prevent other apps from
- // injecting states.
- final SharedPreferences prefs = getSharedPreferences();
- if (prefs.getBoolean(PREFS_ALLOW_STATE_BUNDLE, false)) {
- prefs.edit().remove(PREFS_ALLOW_STATE_BUNDLE).apply();
- savedInstanceState = stateBundle;
- }
- } else if (savedInstanceState != null) {
- // Bug 896992 - This intent has already been handled; reset the intent.
- setIntent(new Intent(Intent.ACTION_MAIN));
- }
-
- super.onCreate(savedInstanceState);
-
- GeckoScreenOrientation.getInstance().update(getResources().getConfiguration().orientation);
-
- setContentView(getLayout());
-
- // Set up Gecko layout.
- mRootLayout = (RelativeLayout) findViewById(R.id.root_layout);
- mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
- mMainLayout = (RelativeLayout) findViewById(R.id.main_layout);
- mLayerView = (GeckoView) findViewById(R.id.layer_view);
-
- Tabs.getInstance().attachToContext(this, mLayerView);
-
- // Use global layout state change to kick off additional initialization
- mMainLayout.getViewTreeObserver().addOnGlobalLayoutListener(this);
-
- if (Versions.preMarshmallow) {
- mTextSelection = new ActionBarTextSelection(this);
- } else {
- mTextSelection = new FloatingToolbarTextSelection(this, mLayerView);
- }
- mTextSelection.create();
-
- // Determine whether we should restore tabs.
- mLastSessionCrashed = updateCrashedState();
- mShouldRestore = getSessionRestoreState(savedInstanceState);
- if (mShouldRestore && savedInstanceState != null) {
- boolean wasInBackground =
- savedInstanceState.getBoolean(SAVED_STATE_IN_BACKGROUND, false);
-
- // Don't log OOM-kills if only one activity was destroyed. (For example
- // from "Don't keep activities" on ICS)
- if (!wasInBackground && !mIsRestoringActivity) {
- Telemetry.addToHistogram("FENNEC_WAS_KILLED", 1);
- }
-
- mPrivateBrowsingSession = savedInstanceState.getString(SAVED_STATE_PRIVATE_SESSION);
- }
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- // If we are doing a restore, read the session data so we can send it to Gecko later.
- String restoreMessage = null;
- if (!mIsRestoringActivity && mShouldRestore) {
- final boolean isExternalURL = invokedWithExternalURL(getIntentURI(new SafeIntent(getIntent())));
- try {
- // restoreSessionTabs() will create simple tab stubs with the
- // URL and title for each page, but we also need to restore
- // session history. restoreSessionTabs() will inject the IDs
- // of the tab stubs into the JSON data (which holds the session
- // history). This JSON data is then sent to Gecko so session
- // history can be restored for each tab.
- restoreMessage = restoreSessionTabs(isExternalURL, false);
- } catch (SessionRestoreException e) {
- // If mShouldRestore was set to false in restoreSessionTabs(), this means
- // either that we intentionally skipped all tabs read from the session file,
- // or else that the file was syntactically valid, but didn't contain any
- // tabs (e.g. because the user cleared history), therefore we don't need
- // to switch to the backup copy.
- if (mShouldRestore) {
- Log.e(LOGTAG, "An error occurred during restore, switching to backup file", e);
- // To be on the safe side, we will always attempt to restore from the backup
- // copy if we end up here.
- // Since we will also hit this situation regularly during first run though,
- // we'll only report it in telemetry if we failed to restore despite the
- // file existing, which means it's very probably damaged.
- if (getProfile().sessionFileExists()) {
- Telemetry.addToHistogram("FENNEC_SESSIONSTORE_DAMAGED_SESSION_FILE", 1);
- }
- try {
- restoreMessage = restoreSessionTabs(isExternalURL, true);
- Telemetry.addToHistogram("FENNEC_SESSIONSTORE_RESTORING_FROM_BACKUP", 1);
- } catch (SessionRestoreException ex) {
- if (!mShouldRestore) {
- // Restoring only "failed" because the backup copy was deliberately empty, too.
- Telemetry.addToHistogram("FENNEC_SESSIONSTORE_RESTORING_FROM_BACKUP", 1);
- } else {
- // Restoring the backup failed, too, so do a normal startup.
- Log.e(LOGTAG, "An error occurred during restore", ex);
- mShouldRestore = false;
- }
- }
- }
- }
- }
-
- synchronized (GeckoApp.this) {
- mSessionRestoreParsingFinished = true;
- GeckoApp.this.notifyAll();
- }
-
- // If we are doing a restore, send the parsed session data to Gecko.
- if (!mIsRestoringActivity) {
- GeckoAppShell.notifyObservers("Session:Restore", restoreMessage);
- }
-
- // Make sure sessionstore.old is either updated or deleted as necessary.
- getProfile().updateSessionFile(mShouldRestore);
- }
- });
-
- // Perform background initialization.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- final SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
-
- // Wait until now to set this, because we'd rather throw an exception than
- // have a caller of BrowserLocaleManager regress startup.
- final LocaleManager localeManager = BrowserLocaleManager.getInstance();
- localeManager.initialize(getApplicationContext());
-
- SessionInformation previousSession = SessionInformation.fromSharedPrefs(prefs);
- if (previousSession.wasKilled()) {
- Telemetry.addToHistogram("FENNEC_WAS_KILLED", 1);
- }
-
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(GeckoAppShell.PREFS_OOM_EXCEPTION, false);
-
- // Put a flag to check if we got a normal `onSaveInstanceState`
- // on exit, or if we were suddenly killed (crash or native OOM).
- editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
-
- editor.apply();
-
- // The lifecycle of mHealthRecorder is "shortly after onCreate"
- // through "onDestroy" -- essentially the same as the lifecycle
- // of the activity itself.
- final String profilePath = getProfile().getDir().getAbsolutePath();
- final EventDispatcher dispatcher = getAppEventDispatcher();
-
- // This is the locale prior to fixing it up.
- final Locale osLocale = Locale.getDefault();
-
- // Both of these are Java-format locale strings: "en_US", not "en-US".
- final String osLocaleString = osLocale.toString();
- String appLocaleString = localeManager.getAndApplyPersistedLocale(GeckoApp.this);
- Log.d(LOGTAG, "OS locale is " + osLocaleString + ", app locale is " + appLocaleString);
-
- if (appLocaleString == null) {
- appLocaleString = osLocaleString;
- }
-
- mHealthRecorder = GeckoApp.this.createHealthRecorder(GeckoApp.this,
- profilePath,
- dispatcher,
- osLocaleString,
- appLocaleString,
- previousSession);
-
- final String uiLocale = appLocaleString;
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- GeckoApp.this.onLocaleReady(uiLocale);
- }
- });
-
- // We use per-profile prefs here, because we're tracking against
- // a Gecko pref. The same applies to the locale switcher!
- BrowserLocaleManager.storeAndNotifyOSLocale(GeckoSharedPrefs.forProfile(GeckoApp.this), osLocale);
- }
- });
-
- IntentHelper.init(this);
- }
-
- @Override
- public void onStart() {
- super.onStart();
- if (mIsAbortingAppLaunch) {
- return;
- }
-
- mWasFirstTabShownAfterActivityUnhidden = false; // onStart indicates we were hidden.
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- // Overriding here is not necessary, but we do this so we don't
- // forget to add the abort if we override this method later.
- if (mIsAbortingAppLaunch) {
- return;
- }
- }
-
- /**
- * At this point, the resource system and the rest of the browser are
- * aware of the locale.
- *
- * Now we can display strings!
- *
- * You can think of this as being something like a second phase of onCreate,
- * where you can do string-related operations. Use this in place of embedding
- * strings in view XML.
- *
- * By contrast, onConfigurationChanged does some locale operations, but is in
- * response to device changes.
- */
- @Override
- public void onLocaleReady(final String locale) {
- if (!ThreadUtils.isOnUiThread()) {
- throw new RuntimeException("onLocaleReady must always be called from the UI thread.");
- }
-
- final Locale loc = Locales.parseLocaleCode(locale);
- if (loc.equals(mLastLocale)) {
- Log.d(LOGTAG, "New locale same as old; onLocaleReady has nothing to do.");
- }
-
- // The URL bar hint needs to be populated.
- TextView urlBar = (TextView) findViewById(R.id.url_bar_title);
- if (urlBar != null) {
- final String hint = getResources().getString(R.string.url_bar_default_text);
- urlBar.setHint(hint);
- } else {
- Log.d(LOGTAG, "No URL bar in GeckoApp. Not loading localized hint string.");
- }
-
- mLastLocale = loc;
-
- // Allow onConfigurationChanged to take care of the rest.
- // We don't call this.onConfigurationChanged, because (a) that does
- // work that's unnecessary after this locale action, and (b) it can
- // cause a loop! See Bug 1011008, Comment 12.
- super.onConfigurationChanged(getResources().getConfiguration());
- }
-
- protected void initializeChrome() {
- mDoorHangerPopup = new DoorHangerPopup(this);
- mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container);
- mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup);
- }
-
- /**
- * Loads the initial tab at Fennec startup. If we don't restore tabs, this
- * tab will be about:home, or the homepage if the user has set one.
- * If we've temporarily disabled restoring to break out of a crash loop, we'll show
- * the Recent Tabs folder of the Combined History panel, so the user can manually
- * restore tabs as needed.
- * If we restore tabs, we don't need to create a new tab.
- */
- protected void loadStartupTab(final int flags) {
- if (!mShouldRestore) {
- if (mLastSessionCrashed) {
- // The Recent Tabs panel no longer exists, but BrowserApp will redirect us
- // to the Recent Tabs folder of the Combined History panel.
- Tabs.getInstance().loadUrl(AboutPages.getURLForBuiltinPanelType(PanelType.DEPRECATED_RECENT_TABS), flags);
- } else {
- final String homepage = getHomepage();
- Tabs.getInstance().loadUrl(!TextUtils.isEmpty(homepage) ? homepage : AboutPages.HOME, flags);
- }
- }
- }
-
- /**
- * Loads the initial tab at Fennec startup. This tab will load with the given
- * external URL. If that URL is invalid, a startup tab will be loaded.
- *
- * @param url External URL to load.
- * @param intent External intent whose extras modify the request
- * @param flags Flags used to load the load
- */
- protected void loadStartupTab(final String url, final SafeIntent intent, final int flags) {
- // Invalid url
- if (url == null) {
- loadStartupTab(flags);
- return;
- }
-
- Tabs.getInstance().loadUrlWithIntentExtras(url, intent, flags);
- }
-
- public String getHomepage() {
- return null;
- }
-
- private String getIntentURI(SafeIntent intent) {
- final String passedUri;
- final String uri = getURIFromIntent(intent);
-
- if (!TextUtils.isEmpty(uri)) {
- passedUri = uri;
- } else {
- passedUri = null;
- }
- return passedUri;
- }
-
- private boolean invokedWithExternalURL(String uri) {
- return uri != null && !AboutPages.isAboutHome(uri);
- }
-
- private void initialize() {
- mInitialized = true;
-
- final boolean isFirstTab = !mWasFirstTabShownAfterActivityUnhidden;
- mWasFirstTabShownAfterActivityUnhidden = true; // Reset since we'll be loading a tab.
-
- final SafeIntent intent = new SafeIntent(getIntent());
- final String action = intent.getAction();
-
- final String passedUri = getIntentURI(intent);
-
- final boolean isExternalURL = invokedWithExternalURL(passedUri);
-
- // Start migrating as early as possible, can do this in
- // parallel with Gecko load.
- checkMigrateProfile();
-
- Tabs.registerOnTabsChangedListener(this);
-
- initializeChrome();
-
- // We need to wait here because mShouldRestore can revert back to
- // false if a parsing error occurs and the startup tab we load
- // depends on whether we restore tabs or not.
- synchronized (this) {
- while (!mSessionRestoreParsingFinished) {
- try {
- wait();
- } catch (final InterruptedException e) {
- // Ignore and wait again.
- }
- }
- }
-
- // External URLs should always be loaded regardless of whether Gecko is
- // already running.
- if (isExternalURL) {
- // Restore tabs before opening an external URL so that the new tab
- // is animated properly.
- Tabs.getInstance().notifyListeners(null, Tabs.TabEvents.RESTORED);
- processActionViewIntent(new Runnable() {
- @Override
- public void run() {
- int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_USER_ENTERED | Tabs.LOADURL_EXTERNAL;
- if (ACTION_HOMESCREEN_SHORTCUT.equals(action)) {
- flags |= Tabs.LOADURL_PINNED;
- }
- if (isFirstTab) {
- flags |= Tabs.LOADURL_FIRST_AFTER_ACTIVITY_UNHIDDEN;
- }
- loadStartupTab(passedUri, intent, flags);
- }
- });
- } else {
- if (!mIsRestoringActivity) {
- loadStartupTab(Tabs.LOADURL_NEW_TAB);
- }
-
- Tabs.getInstance().notifyListeners(null, Tabs.TabEvents.RESTORED);
-
- processTabQueue();
- }
-
- recordStartupActionTelemetry(passedUri, action);
-
- // Check if launched from data reporting notification.
- if (ACTION_LAUNCH_SETTINGS.equals(action)) {
- Intent settingsIntent = new Intent(GeckoApp.this, GeckoPreferences.class);
- // Copy extras.
- settingsIntent.putExtras(intent.getUnsafe());
- startActivity(settingsIntent);
- }
-
- //app state callbacks
- mAppStateListeners = new LinkedList<GeckoAppShell.AppStateListener>();
-
- mPromptService = new PromptService(this);
-
- // Trigger the completion of the telemetry timer that wraps activity startup,
- // then grab the duration to give to FHR.
- mJavaUiStartupTimer.stop();
- final long javaDuration = mJavaUiStartupTimer.getElapsed();
-
- ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() {
- @Override
- public void run() {
- final HealthRecorder rec = mHealthRecorder;
- if (rec != null) {
- rec.recordJavaStartupTime(javaDuration);
- }
- }
- }, 50);
-
- final int updateServiceDelay = 30 * 1000;
- ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() {
- @Override
- public void run() {
- UpdateServiceHelper.registerForUpdates(GeckoAppShell.getApplicationContext());
- }
- }, updateServiceDelay);
-
- if (mIsRestoringActivity) {
- Tab selectedTab = Tabs.getInstance().getSelectedTab();
- if (selectedTab != null) {
- Tabs.getInstance().notifyListeners(selectedTab, Tabs.TabEvents.SELECTED);
- }
-
- if (GeckoThread.isRunning()) {
- geckoConnected();
- if (mLayerView != null) {
- mLayerView.setPaintState(LayerView.PAINT_BEFORE_FIRST);
- }
- }
- }
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
- @Override
- public void onGlobalLayout() {
- if (Versions.preJB) {
- mMainLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
- } else {
- mMainLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- }
- if (!mInitialized) {
- initialize();
- }
- }
-
- protected void processActionViewIntent(final Runnable openTabsRunnable) {
- // We need to ensure that if we receive a VIEW action and there are tabs queued then the
- // site loaded from the intent is on top (last loaded) and selected with all other tabs
- // being opened behind it. We process the tab queue first and request a callback from the JS - the
- // listener will open the url from the intent as normal when the tab queue has been processed.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- if (TabQueueHelper.TAB_QUEUE_ENABLED && TabQueueHelper.shouldOpenTabQueueUrls(GeckoApp.this)) {
-
- getAppEventDispatcher().registerGeckoThreadListener(new NativeEventListener() {
- @Override
- public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
- if ("Tabs:TabsOpened".equals(event)) {
- getAppEventDispatcher().unregisterGeckoThreadListener(this, "Tabs:TabsOpened");
- openTabsRunnable.run();
- }
- }
- }, "Tabs:TabsOpened");
- TabQueueHelper.openQueuedUrls(GeckoApp.this, getProfile(), TabQueueHelper.FILE_NAME, true);
- } else {
- openTabsRunnable.run();
- }
- }
- });
- }
-
- @WorkerThread
- private String restoreSessionTabs(final boolean isExternalURL, boolean useBackup) throws SessionRestoreException {
- try {
- String sessionString = getProfile().readSessionFile(useBackup);
- if (sessionString == null) {
- throw new SessionRestoreException("Could not read from session file");
- }
-
- // If we are doing an OOM restore, parse the session data and
- // stub the restored tabs immediately. This allows the UI to be
- // updated before Gecko has restored.
- final JSONArray tabs = new JSONArray();
- final JSONObject windowObject = new JSONObject();
- final boolean sessionDataValid;
-
- LastSessionParser parser = new LastSessionParser(tabs, windowObject, isExternalURL);
-
- if (mPrivateBrowsingSession == null) {
- sessionDataValid = parser.parse(sessionString);
- } else {
- sessionDataValid = parser.parse(sessionString, mPrivateBrowsingSession);
- }
-
- if (tabs.length() > 0) {
- windowObject.put("tabs", tabs);
- sessionString = new JSONObject().put("windows", new JSONArray().put(windowObject)).toString();
- } else {
- if (parser.allTabsSkipped() || sessionDataValid) {
- // If we intentionally skipped all tabs we've read from the session file, we
- // set mShouldRestore back to false at this point already, so the calling code
- // can infer that the exception wasn't due to a damaged session store file.
- // The same applies if the session file was syntactically valid and
- // simply didn't contain any tabs.
- mShouldRestore = false;
- }
- throw new SessionRestoreException("No tabs could be read from session file");
- }
-
- JSONObject restoreData = new JSONObject();
- restoreData.put("sessionString", sessionString);
- return restoreData.toString();
- } catch (JSONException e) {
- throw new SessionRestoreException(e);
- }
- }
-
- public static EventDispatcher getEventDispatcher() {
- final GeckoApp geckoApp = (GeckoApp) GeckoAppShell.getGeckoInterface();
- return geckoApp.getAppEventDispatcher();
- }
-
- @Override
- public EventDispatcher getAppEventDispatcher() {
- return eventDispatcher;
- }
-
- @Override
- public GeckoProfile getProfile() {
- return GeckoThread.getActiveProfile();
- }
-
- /**
- * Check whether we've crashed during the last browsing session.
- *
- * @return True if the crash reporter ran after the last session.
- */
- protected boolean updateCrashedState() {
- try {
- File crashFlag = new File(GeckoProfileDirectories.getMozillaDirectory(this), "CRASHED");
- if (crashFlag.exists() && crashFlag.delete()) {
- // Set the flag that indicates we were stopped as expected, as
- // the crash reporter has run, so it is not a silent OOM crash.
- getSharedPreferences().edit().putBoolean(PREFS_WAS_STOPPED, true).apply();
- return true;
- }
- } catch (NoMozillaDirectoryException e) {
- // If we can't access the Mozilla directory, we're in trouble anyway.
- Log.e(LOGTAG, "Cannot read crash flag: ", e);
- }
- return false;
- }
-
- /**
- * Determine whether the session should be restored.
- *
- * @param savedInstanceState Saved instance state given to the activity
- * @return Whether to restore
- */
- protected boolean getSessionRestoreState(Bundle savedInstanceState) {
- final SharedPreferences prefs = getSharedPreferences();
- boolean shouldRestore = false;
-
- final int versionCode = getVersionCode();
- if (mLastSessionCrashed) {
- if (incrementCrashCount(prefs) <= getSessionStoreMaxCrashResumes(prefs) &&
- getSessionRestoreAfterCrashPreference(prefs)) {
- shouldRestore = true;
- } else {
- shouldRestore = false;
- }
- } else if (prefs.getInt(PREFS_VERSION_CODE, 0) != versionCode) {
- // If the version has changed, the user has done an upgrade, so restore
- // previous tabs.
- prefs.edit().putInt(PREFS_VERSION_CODE, versionCode).apply();
- shouldRestore = true;
- } else if (savedInstanceState != null ||
- getSessionRestorePreference(prefs).equals("always") ||
- getRestartFromIntent()) {
- // We're coming back from a background kill by the OS, the user
- // has chosen to always restore, or we restarted.
- shouldRestore = true;
- }
-
- return shouldRestore;
- }
-
- private int incrementCrashCount(SharedPreferences prefs) {
- final int crashCount = getSuccessiveCrashesCount(prefs) + 1;
- prefs.edit().putInt(PREFS_CRASHED_COUNT, crashCount).apply();
- return crashCount;
- }
-
- private int getSuccessiveCrashesCount(SharedPreferences prefs) {
- return prefs.getInt(PREFS_CRASHED_COUNT, 0);
- }
-
- private int getSessionStoreMaxCrashResumes(SharedPreferences prefs) {
- return prefs.getInt(GeckoPreferences.PREFS_RESTORE_SESSION_MAX_CRASH_RESUMES, 1);
- }
-
- private boolean getSessionRestoreAfterCrashPreference(SharedPreferences prefs) {
- return prefs.getBoolean(GeckoPreferences.PREFS_RESTORE_SESSION_FROM_CRASH, true);
- }
-
- private String getSessionRestorePreference(SharedPreferences prefs) {
- return prefs.getString(GeckoPreferences.PREFS_RESTORE_SESSION, "always");
- }
-
- private boolean getRestartFromIntent() {
- return IntentUtils.getBooleanExtraSafe(getIntent(), "didRestart", false);
- }
-
- /**
- * Enable Android StrictMode checks (for supported OS versions).
- * http://developer.android.com/reference/android/os/StrictMode.html
- */
- private void enableStrictMode() {
- Log.d(LOGTAG, "Enabling Android StrictMode");
-
- StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
- .detectAll()
- .penaltyLog()
- .build());
-
- StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
- .detectAll()
- .penaltyLog()
- .build());
- }
-
- @Override
- public void enableOrientationListener() {
- // Start listening for orientation events
- mCameraOrientationEventListener = new OrientationEventListener(this) {
- @Override
- public void onOrientationChanged(int orientation) {
- if (mAppStateListeners != null) {
- for (GeckoAppShell.AppStateListener listener: mAppStateListeners) {
- listener.onOrientationChanged();
- }
- }
- }
- };
- mCameraOrientationEventListener.enable();
- }
-
- @Override
- public void disableOrientationListener() {
- if (mCameraOrientationEventListener != null) {
- mCameraOrientationEventListener.disable();
- mCameraOrientationEventListener = null;
- }
- }
-
- @Override
- public String getDefaultUAString() {
- return HardwareUtils.isTablet() ? AppConstants.USER_AGENT_FENNEC_TABLET :
- AppConstants.USER_AGENT_FENNEC_MOBILE;
- }
-
- @Override
- public void createShortcut(final String title, final String url) {
- Icons.with(this)
- .pageUrl(url)
- .skipNetwork()
- .skipMemory()
- .forLauncherIcon()
- .build()
- .execute(new IconCallback() {
- @Override
- public void onIconResponse(IconResponse response) {
- doCreateShortcut(title, url, response.getBitmap());
- }
- });
- }
-
- private void doCreateShortcut(final String aTitle, final String aURI, final Bitmap aIcon) {
- // The intent to be launched by the shortcut.
- Intent shortcutIntent = new Intent();
- shortcutIntent.setAction(GeckoApp.ACTION_HOMESCREEN_SHORTCUT);
- shortcutIntent.setData(Uri.parse(aURI));
- shortcutIntent.setClassName(AppConstants.ANDROID_PACKAGE_NAME,
- AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
-
- Intent intent = new Intent();
- intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
- intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, getLauncherIcon(aIcon, GeckoAppShell.getPreferredIconSize()));
-
- if (aTitle != null) {
- intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aTitle);
- } else {
- intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aURI);
- }
-
- // Do not allow duplicate items.
- intent.putExtra("duplicate", false);
-
- intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
- getApplicationContext().sendBroadcast(intent);
-
- // Remember interaction
- final UrlAnnotations urlAnnotations = BrowserDB.from(getApplicationContext()).getUrlAnnotations();
- urlAnnotations.insertHomeScreenShortcut(getContentResolver(), aURI, true);
-
- // After shortcut is created, show the mobile desktop.
- ActivityUtils.goToHomeScreen(this);
- }
-
- private Bitmap getLauncherIcon(Bitmap aSource, int size) {
- final float[] DEFAULT_LAUNCHER_ICON_HSV = { 32.0f, 1.0f, 1.0f };
- final int kOffset = 6;
- final int kRadius = 5;
-
- int insetSize = aSource != null ? size * 2 / 3 : size;
-
- Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
-
- // draw a base color
- Paint paint = new Paint();
- if (aSource == null) {
- // If we aren't drawing a favicon, just use an orange color.
- paint.setColor(Color.HSVToColor(DEFAULT_LAUNCHER_ICON_HSV));
- canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint);
- } else if (aSource.getWidth() >= insetSize || aSource.getHeight() >= insetSize) {
- // Otherwise, if the icon is large enough, just draw it.
- Rect iconBounds = new Rect(0, 0, size, size);
- canvas.drawBitmap(aSource, null, iconBounds, null);
- return bitmap;
- } else {
- // otherwise use the dominant color from the icon + a layer of transparent white to lighten it somewhat
- int color = BitmapUtils.getDominantColor(aSource);
- paint.setColor(color);
- canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint);
- paint.setColor(Color.argb(100, 255, 255, 255));
- canvas.drawRoundRect(new RectF(kOffset, kOffset, size - kOffset, size - kOffset), kRadius, kRadius, paint);
- }
-
- // draw the overlay
- Bitmap overlay = BitmapUtils.decodeResource(this, R.drawable.home_bg);
- canvas.drawBitmap(overlay, null, new Rect(0, 0, size, size), null);
-
- // draw the favicon
- if (aSource == null)
- aSource = BitmapUtils.decodeResource(this, R.drawable.home_star);
-
- // by default, we scale the icon to this size
- int sWidth = insetSize / 2;
- int sHeight = sWidth;
-
- int halfSize = size / 2;
- canvas.drawBitmap(aSource,
- null,
- new Rect(halfSize - sWidth,
- halfSize - sHeight,
- halfSize + sWidth,
- halfSize + sHeight),
- null);
-
- return bitmap;
- }
-
- @Override
- protected void onNewIntent(Intent externalIntent) {
- final SafeIntent intent = new SafeIntent(externalIntent);
-
- final boolean isFirstTab = !mWasFirstTabShownAfterActivityUnhidden;
- mWasFirstTabShownAfterActivityUnhidden = true; // Reset since we'll be loading a tab.
-
- // if we were previously OOM killed, we can end up here when launching
- // from external shortcuts, so set this as the intent for initialization
- if (!mInitialized) {
- setIntent(externalIntent);
- return;
- }
-
- final String action = intent.getAction();
-
- final String uri = getURIFromIntent(intent);
- final String passedUri;
- if (!TextUtils.isEmpty(uri)) {
- passedUri = uri;
- } else {
- passedUri = null;
- }
-
- if (ACTION_LOAD.equals(action)) {
- Tabs.getInstance().loadUrl(intent.getDataString());
- lastSelectedTabId = -1;
- } else if (Intent.ACTION_VIEW.equals(action)) {
- processActionViewIntent(new Runnable() {
- @Override
- public void run() {
- final String url = intent.getDataString();
- int flags = Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_USER_ENTERED | Tabs.LOADURL_EXTERNAL;
- if (isFirstTab) {
- flags |= Tabs.LOADURL_FIRST_AFTER_ACTIVITY_UNHIDDEN;
- }
- Tabs.getInstance().loadUrlWithIntentExtras(url, intent, flags);
- }
- });
- lastSelectedTabId = -1;
- } else if (ACTION_HOMESCREEN_SHORTCUT.equals(action)) {
- mLayerView.loadUri(uri, GeckoView.LOAD_SWITCH_TAB);
- } else if (Intent.ACTION_SEARCH.equals(action)) {
- mLayerView.loadUri(uri, GeckoView.LOAD_NEW_TAB);
- } else if (NotificationHelper.HELPER_BROADCAST_ACTION.equals(action)) {
- NotificationHelper.getInstance(getApplicationContext()).handleNotificationIntent(intent);
- } else if (ACTION_LAUNCH_SETTINGS.equals(action)) {
- // Check if launched from data reporting notification.
- Intent settingsIntent = new Intent(GeckoApp.this, GeckoPreferences.class);
- // Copy extras.
- settingsIntent.putExtras(intent.getUnsafe());
- startActivity(settingsIntent);
- } else if (ACTION_SWITCH_TAB.equals(action)) {
- final int tabId = intent.getIntExtra("TabId", -1);
- Tabs.getInstance().selectTab(tabId);
- lastSelectedTabId = -1;
- }
-
- recordStartupActionTelemetry(passedUri, action);
- }
-
- /**
- * Handles getting a URI from an intent in a way that is backwards-
- * compatible with our previous implementations.
- */
- protected String getURIFromIntent(SafeIntent intent) {
- final String action = intent.getAction();
- if (ACTION_ALERT_CALLBACK.equals(action) ||
- NotificationHelper.HELPER_BROADCAST_ACTION.equals(action)) {
- return null;
- }
-
- return intent.getDataString();
- }
-
- protected int getOrientation() {
- return GeckoScreenOrientation.getInstance().getAndroidOrientation();
- }
-
- @Override
- public void onResume()
- {
- // After an onPause, the activity is back in the foreground.
- // Undo whatever we did in onPause.
- super.onResume();
- if (mIsAbortingAppLaunch) {
- return;
- }
-
- GeckoAppShell.setGeckoInterface(this);
-
- if (lastSelectedTabId >= 0 && (lastActiveGeckoApp == null || lastActiveGeckoApp.get() != this)) {
- Tabs.getInstance().selectTab(lastSelectedTabId);
- }
-
- int newOrientation = getResources().getConfiguration().orientation;
- if (GeckoScreenOrientation.getInstance().update(newOrientation)) {
- refreshChrome();
- }
-
- if (mAppStateListeners != null) {
- for (GeckoAppShell.AppStateListener listener : mAppStateListeners) {
- listener.onResume();
- }
- }
-
- // We use two times: a pseudo-unique wall-clock time to identify the
- // current session across power cycles, and the elapsed realtime to
- // track the duration of the session.
- final long now = System.currentTimeMillis();
- final long realTime = android.os.SystemClock.elapsedRealtime();
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- // Now construct the new session on HealthRecorder's behalf. We do this here
- // so it can benefit from a single near-startup prefs commit.
- SessionInformation currentSession = new SessionInformation(now, realTime);
-
- SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
-
- if (!mLastSessionCrashed) {
- // The last session terminated normally,
- // so we can reset the count of successive crashes.
- editor.putInt(GeckoApp.PREFS_CRASHED_COUNT, 0);
- }
-
- currentSession.recordBegin(editor);
- editor.apply();
-
- final HealthRecorder rec = mHealthRecorder;
- if (rec != null) {
- rec.setCurrentSession(currentSession);
- rec.processDelayed();
- } else {
- Log.w(LOGTAG, "Can't record session: rec is null.");
- }
- }
- });
-
- Restrictions.update(this);
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
- super.onWindowFocusChanged(hasFocus);
-
- if (!mWindowFocusInitialized && hasFocus) {
- mWindowFocusInitialized = true;
- // XXX our editor tests require the GeckoView to have focus to pass, so we have to
- // manually shift focus to the GeckoView. requestFocus apparently doesn't work at
- // this stage of starting up, so we have to unset and reset the focusability.
- mLayerView.setFocusable(false);
- mLayerView.setFocusable(true);
- mLayerView.setFocusableInTouchMode(true);
- getWindow().setBackgroundDrawable(null);
- }
- }
-
- @Override
- public void onPause()
- {
- if (mIsAbortingAppLaunch) {
- super.onPause();
- return;
- }
-
- final Tab selectedTab = Tabs.getInstance().getSelectedTab();
- if (selectedTab != null) {
- lastSelectedTabId = selectedTab.getId();
- }
- lastActiveGeckoApp = new WeakReference<GeckoApp>(this);
-
- final HealthRecorder rec = mHealthRecorder;
- final Context context = this;
-
- // In some way it's sad that Android will trigger StrictMode warnings
- // here as the whole point is to save to disk while the activity is not
- // interacting with the user.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- SharedPreferences prefs = GeckoApp.this.getSharedPreferences();
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, true);
- if (rec != null) {
- rec.recordSessionEnd("P", editor);
- }
-
- // onPause might in fact be called even after a crash, but in that case the
- // crash reporter will record this fact for us and we'll pick it up in onCreate.
- mLastSessionCrashed = false;
-
- // If we haven't done it before, cleanup any old files in our old temp dir
- if (prefs.getBoolean(GeckoApp.PREFS_CLEANUP_TEMP_FILES, true)) {
- File tempDir = GeckoLoader.getGREDir(GeckoApp.this);
- FileUtils.delTree(tempDir, new FileUtils.NameAndAgeFilter(null, ONE_DAY_MS), false);
-
- editor.putBoolean(GeckoApp.PREFS_CLEANUP_TEMP_FILES, false);
- }
-
- editor.apply();
- }
- });
-
- if (mAppStateListeners != null) {
- for (GeckoAppShell.AppStateListener listener : mAppStateListeners) {
- listener.onPause();
- }
- }
-
- super.onPause();
- }
-
- @Override
- public void onRestart() {
- if (mIsAbortingAppLaunch) {
- super.onRestart();
- return;
- }
-
- // Faster on main thread with an async apply().
- final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
- try {
- SharedPreferences.Editor editor = GeckoApp.this.getSharedPreferences().edit();
- editor.putBoolean(GeckoApp.PREFS_WAS_STOPPED, false);
- editor.apply();
- } finally {
- StrictMode.setThreadPolicy(savedPolicy);
- }
-
- super.onRestart();
- }
-
- @Override
- public void onDestroy() {
- if (mIsAbortingAppLaunch) {
- // This build does not support the Android version of the device:
- // We did not initialize anything, so skip cleaning up.
- super.onDestroy();
- return;
- }
-
- getAppEventDispatcher().unregisterGeckoThreadListener((GeckoEventListener)this,
- "Gecko:Ready",
- "Gecko:Exited",
- "Accessibility:Event");
-
- getAppEventDispatcher().unregisterGeckoThreadListener((NativeEventListener)this,
- "Accessibility:Ready",
- "Bookmark:Insert",
- "Contact:Add",
- "DevToolsAuth:Scan",
- "DOMFullScreen:Start",
- "DOMFullScreen:Stop",
- "Image:SetAs",
- "Locale:Set",
- "Permissions:Data",
- "PrivateBrowsing:Data",
- "RuntimePermissions:Prompt",
- "Sanitize:Finished",
- "Session:StatePurged",
- "Share:Text",
- "Snackbar:Show",
- "SystemUI:Visibility",
- "ToggleChrome:Focus",
- "ToggleChrome:Hide",
- "ToggleChrome:Show",
- "Update:Check",
- "Update:Download",
- "Update:Install");
-
- if (mPromptService != null)
- mPromptService.destroy();
-
- final HealthRecorder rec = mHealthRecorder;
- mHealthRecorder = null;
- if (rec != null && rec.isEnabled()) {
- // Closing a HealthRecorder could incur a write.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- rec.close(GeckoApp.this);
- }
- });
- }
-
- super.onDestroy();
-
- Tabs.unregisterOnTabsChangedListener(this);
- }
-
- public void showSDKVersionError() {
- final String message = getString(R.string.unsupported_sdk_version, Build.CPU_ABI, Build.VERSION.SDK_INT);
- Toast.makeText(this, message, Toast.LENGTH_LONG).show();
- }
-
- // Get a temporary directory, may return null
- public static File getTempDirectory() {
- File dir = GeckoApplication.get().getExternalFilesDir("temp");
- return dir;
- }
-
- // Delete any files in our temporary directory
- public static void deleteTempFiles() {
- File dir = getTempDirectory();
- if (dir == null)
- return;
- File[] files = dir.listFiles();
- if (files == null)
- return;
- for (File file : files) {
- file.delete();
- }
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- Log.d(LOGTAG, "onConfigurationChanged: " + newConfig.locale);
-
- final LocaleManager localeManager = BrowserLocaleManager.getInstance();
- final Locale changed = localeManager.onSystemConfigurationChanged(this, getResources(), newConfig, mLastLocale);
- if (changed != null) {
- onLocaleChanged(Locales.getLanguageTag(changed));
- }
-
- // onConfigurationChanged is not called for 180 degree orientation changes,
- // we will miss such rotations and the screen orientation will not be
- // updated.
- if (GeckoScreenOrientation.getInstance().update(newConfig.orientation)) {
- if (mFormAssistPopup != null)
- mFormAssistPopup.hide();
- refreshChrome();
- }
- super.onConfigurationChanged(newConfig);
- }
-
- public String getContentProcessName() {
- return AppConstants.MOZ_CHILD_PROCESS_NAME;
- }
-
- public void addEnvToIntent(Intent intent) {
- Map<String, String> envMap = System.getenv();
- Set<Map.Entry<String, String>> envSet = envMap.entrySet();
- Iterator<Map.Entry<String, String>> envIter = envSet.iterator();
- int c = 0;
- while (envIter.hasNext()) {
- Map.Entry<String, String> entry = envIter.next();
- intent.putExtra("env" + c, entry.getKey() + "="
- + entry.getValue());
- c++;
- }
- }
-
- @Override
- public void doRestart() {
- doRestart(null, null);
- }
-
- public void doRestart(String args) {
- doRestart(args, null);
- }
-
- public void doRestart(Intent intent) {
- doRestart(null, intent);
- }
-
- public void doRestart(String args, Intent restartIntent) {
- if (restartIntent == null) {
- restartIntent = new Intent(Intent.ACTION_MAIN);
- }
-
- if (args != null) {
- restartIntent.putExtra("args", args);
- }
-
- mRestartIntent = restartIntent;
- Log.d(LOGTAG, "doRestart(\"" + restartIntent + "\")");
-
- doShutdown();
- }
-
- private void doShutdown() {
- // Shut down GeckoApp activity.
- runOnUiThread(new Runnable() {
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
- @Override public void run() {
- if (!isFinishing() && (Versions.preJBMR1 || !isDestroyed())) {
- finish();
- }
- }
- });
- }
-
- private void checkMigrateProfile() {
- final File profileDir = getProfile().getDir();
-
- if (profileDir != null) {
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- Handler handler = new Handler();
- handler.postDelayed(new DeferredCleanupTask(), CLEANUP_DEFERRAL_SECONDS * 1000);
- }
- });
- }
- }
-
- private static class DeferredCleanupTask implements Runnable {
- // The cleanup-version setting is recorded to avoid repeating the same
- // tasks on subsequent startups; CURRENT_CLEANUP_VERSION may be updated
- // if we need to do additional cleanup for future Gecko versions.
-
- private static final String CLEANUP_VERSION = "cleanup-version";
- private static final int CURRENT_CLEANUP_VERSION = 1;
-
- @Override
- public void run() {
- final Context context = GeckoAppShell.getApplicationContext();
- long cleanupVersion = GeckoSharedPrefs.forApp(context).getInt(CLEANUP_VERSION, 0);
-
- if (cleanupVersion < 1) {
- // Reduce device storage footprint by removing .ttf files from
- // the res/fonts directory: we no longer need to copy our
- // bundled fonts out of the APK in order to use them.
- // See https://bugzilla.mozilla.org/show_bug.cgi?id=878674.
- File dir = new File("res/fonts");
- if (dir.exists() && dir.isDirectory()) {
- for (File file : dir.listFiles()) {
- if (file.isFile() && file.getName().endsWith(".ttf")) {
- file.delete();
- }
- }
- if (!dir.delete()) {
- Log.w(LOGTAG, "unable to delete res/fonts directory (not empty?)");
- }
- }
- }
-
- // Additional cleanup needed for future versions would go here
-
- if (cleanupVersion != CURRENT_CLEANUP_VERSION) {
- SharedPreferences.Editor editor = GeckoSharedPrefs.forApp(context).edit();
- editor.putInt(CLEANUP_VERSION, CURRENT_CLEANUP_VERSION);
- editor.apply();
- }
- }
- }
-
- protected void onDone() {
- moveTaskToBack(true);
- }
-
- @Override
- public void onBackPressed() {
- if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
- super.onBackPressed();
- return;
- }
-
- if (autoHideTabs()) {
- return;
- }
-
- if (mDoorHangerPopup != null && mDoorHangerPopup.isShowing()) {
- mDoorHangerPopup.dismiss();
- return;
- }
-
- if (mFullScreenPluginView != null) {
- GeckoAppShell.onFullScreenPluginHidden(mFullScreenPluginView);
- removeFullScreenPluginView(mFullScreenPluginView);
- return;
- }
-
- if (mLayerView != null && mLayerView.isFullScreen()) {
- GeckoAppShell.notifyObservers("FullScreen:Exit", null);
- return;
- }
-
- final Tabs tabs = Tabs.getInstance();
- final Tab tab = tabs.getSelectedTab();
- if (tab == null) {
- onDone();
- return;
- }
-
- // Give Gecko a chance to handle the back press first, then fallback to the Java UI.
- GeckoAppShell.sendRequestToGecko(new GeckoRequest("Browser:OnBackPressed", null) {
- @Override
- public void onResponse(NativeJSObject nativeJSObject) {
- if (!nativeJSObject.getBoolean("handled")) {
- // Default behavior is Gecko didn't prevent.
- onDefault();
- }
- }
-
- @Override
- public void onError(NativeJSObject error) {
- // Default behavior is Gecko didn't prevent, via failure.
- onDefault();
- }
-
- // Return from Gecko thread, then back-press through the Java UI.
- private void onDefault() {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- if (tab.doBack()) {
- return;
- }
-
- if (tab.isExternal()) {
- onDone();
- Tab nextSelectedTab = Tabs.getInstance().getNextTab(tab);
- if (nextSelectedTab != null) {
- int nextSelectedTabId = nextSelectedTab.getId();
- GeckoAppShell.notifyObservers("Tab:KeepZombified", Integer.toString(nextSelectedTabId));
- }
- tabs.closeTab(tab);
- return;
- }
-
- final int parentId = tab.getParentId();
- final Tab parent = tabs.getTab(parentId);
- if (parent != null) {
- // The back button should always return to the parent (not a sibling).
- tabs.closeTab(tab, parent);
- return;
- }
-
- onDone();
- }
- });
- }
- });
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (!ActivityHandlerHelper.handleActivityResult(requestCode, resultCode, data)) {
- super.onActivityResult(requestCode, resultCode, data);
- }
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
- Permissions.onRequestPermissionsResult(this, permissions, grantResults);
- }
-
- @Override
- public AbsoluteLayout getPluginContainer() { return mPluginContainer; }
-
- private static final String CPU = "cpu";
- private static final String SCREEN = "screen";
-
- // Called when a Gecko Hal WakeLock is changed
- @Override
- // We keep the wake lock independent from the function scope, so we need to
- // suppress the linter warning.
- @SuppressLint("Wakelock")
- public void notifyWakeLockChanged(String topic, String state) {
- PowerManager.WakeLock wl = mWakeLocks.get(topic);
- if (state.equals("locked-foreground") && wl == null) {
- PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
-
- if (CPU.equals(topic)) {
- wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, topic);
- } else if (SCREEN.equals(topic)) {
- // ON_AFTER_RELEASE is set, the user activity timer will be reset when the
- // WakeLock is released, causing the illumination to remain on a bit longer.
- wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, topic);
- }
-
- if (wl != null) {
- wl.acquire();
- mWakeLocks.put(topic, wl);
- }
- } else if (!state.equals("locked-foreground") && wl != null) {
- wl.release();
- mWakeLocks.remove(topic);
- }
- }
-
- @Override
- public void notifyCheckUpdateResult(String result) {
- GeckoAppShell.notifyObservers("Update:CheckResult", result);
- }
-
- private void geckoConnected() {
- mLayerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
- }
-
- @Override
- public void setAccessibilityEnabled(boolean enabled) {
- }
-
- @Override
- public boolean openUriExternal(String targetURI, String mimeType, String packageName, String className, String action, String title) {
- // Default to showing prompt in private browsing to be safe.
- return IntentHelper.openUriExternal(targetURI, mimeType, packageName, className, action, title, true);
- }
-
- public static class MainLayout extends RelativeLayout {
- private TouchEventInterceptor mTouchEventInterceptor;
- private MotionEventInterceptor mMotionEventInterceptor;
-
- public MainLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- }
-
- public void setTouchEventInterceptor(TouchEventInterceptor interceptor) {
- mTouchEventInterceptor = interceptor;
- }
-
- public void setMotionEventInterceptor(MotionEventInterceptor interceptor) {
- mMotionEventInterceptor = interceptor;
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- if (mTouchEventInterceptor != null && mTouchEventInterceptor.onInterceptTouchEvent(this, event)) {
- return true;
- }
- return super.onInterceptTouchEvent(event);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (mTouchEventInterceptor != null && mTouchEventInterceptor.onTouch(this, event)) {
- return true;
- }
- return super.onTouchEvent(event);
- }
-
- @Override
- public boolean onGenericMotionEvent(MotionEvent event) {
- if (mMotionEventInterceptor != null && mMotionEventInterceptor.onInterceptMotionEvent(this, event)) {
- return true;
- }
- return super.onGenericMotionEvent(event);
- }
-
- @Override
- public void setDrawingCacheEnabled(boolean enabled) {
- // Instead of setting drawing cache in the view itself, we simply
- // enable drawing caching on its children. This is mainly used in
- // animations (see PropertyAnimator)
- super.setChildrenDrawnWithCacheEnabled(enabled);
- }
- }
-
- private class FullScreenHolder extends FrameLayout {
-
- public FullScreenHolder(Context ctx) {
- super(ctx);
- setBackgroundColor(0xff000000);
- }
-
- @Override
- public void addView(View view, int index) {
- /**
- * This normally gets called when Flash adds a separate SurfaceView
- * for the video. It is unhappy if we have the LayerView underneath
- * it for some reason so we need to hide that. Hiding the LayerView causes
- * its surface to be destroyed, which causes a pause composition
- * event to be sent to Gecko. We synchronously wait for that to be
- * processed. Simultaneously, however, Flash is waiting on a mutex so
- * the post() below is an attempt to avoid a deadlock.
- */
- super.addView(view, index);
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- mLayerView.hideSurface();
- }
- });
- }
-
- /**
- * The methods below are simply copied from what Android WebKit does.
- * It wasn't ever called in my testing, but might as well
- * keep it in case it is for some reason. The methods
- * all return true because we don't want any events
- * leaking out from the fullscreen view.
- */
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (event.isSystem()) {
- return super.onKeyDown(keyCode, event);
- }
- mFullScreenPluginView.onKeyDown(keyCode, event);
- return true;
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (event.isSystem()) {
- return super.onKeyUp(keyCode, event);
- }
- mFullScreenPluginView.onKeyUp(keyCode, event);
- return true;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- return true;
- }
-
- @Override
- public boolean onTrackballEvent(MotionEvent event) {
- mFullScreenPluginView.onTrackballEvent(event);
- return true;
- }
- }
-
- private int getVersionCode() {
- int versionCode = 0;
- try {
- versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode;
- } catch (NameNotFoundException e) {
- Log.wtf(LOGTAG, getPackageName() + " not found", e);
- }
- return versionCode;
- }
-
- // FHR reason code for a session end prior to a restart for a
- // locale change.
- private static final String SESSION_END_LOCALE_CHANGED = "L";
-
- /**
- * This exists so that a locale can be applied in two places: when saved
- * in a nested activity, and then again when we get back up to GeckoApp.
- *
- * GeckoApp needs to do a bunch more stuff than, say, GeckoPreferences.
- */
- protected void onLocaleChanged(final String locale) {
- final boolean startNewSession = true;
- final boolean shouldRestart = false;
-
- // If the HealthRecorder is not yet initialized (unlikely), the locale change won't
- // trigger a session transition and subsequent events will be recorded in an environment
- // with the wrong locale.
- final HealthRecorder rec = mHealthRecorder;
- if (rec != null) {
- rec.onAppLocaleChanged(locale);
- rec.onEnvironmentChanged(startNewSession, SESSION_END_LOCALE_CHANGED);
- }
-
- if (!shouldRestart) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- GeckoApp.this.onLocaleReady(locale);
- }
- });
- return;
- }
-
- // Do this in the background so that the health recorder has its
- // time to finish.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- GeckoApp.this.doRestart();
- }
- });
- }
-
- /**
- * Use BrowserLocaleManager to change our persisted and current locales,
- * and poke the system to tell it of our changed state.
- */
- protected void setLocale(final String locale) {
- if (locale == null) {
- return;
- }
-
- final String resultant = BrowserLocaleManager.getInstance().setSelectedLocale(this, locale);
- if (resultant == null) {
- return;
- }
-
- onLocaleChanged(resultant);
- }
-
- private void setSystemUiVisible(final boolean visible) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- if (visible) {
- mMainLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
- } else {
- mMainLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
- }
- }
- });
- }
-
- protected HealthRecorder createHealthRecorder(final Context context,
- final String profilePath,
- final EventDispatcher dispatcher,
- final String osLocale,
- final String appLocale,
- final SessionInformation previousSession) {
- // GeckoApp does not need to record any health information - return a stub.
- return new StubbedHealthRecorder();
- }
-
- protected void recordStartupActionTelemetry(final String passedURL, final String action) {
- }
-
- @Override
- public void checkUriVisited(String uri) {
- GlobalHistory.getInstance().checkUriVisited(uri);
- }
-
- @Override
- public void markUriVisited(final String uri) {
- final Context context = getApplicationContext();
- final BrowserDB db = BrowserDB.from(context);
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- GlobalHistory.getInstance().add(context, db, uri);
- }
- });
- }
-
- @Override
- public void setUriTitle(final String uri, final String title) {
- final Context context = getApplicationContext();
- final BrowserDB db = BrowserDB.from(context);
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- GlobalHistory.getInstance().update(context.getContentResolver(), db, uri, title);
- }
- });
- }
-
- @Override
- public String[] getHandlersForMimeType(String mimeType, String action) {
- Intent intent = IntentHelper.getIntentForActionString(action);
- if (mimeType != null && mimeType.length() > 0)
- intent.setType(mimeType);
- return IntentHelper.getHandlersForIntent(intent);
- }
-
- @Override
- public String[] getHandlersForURL(String url, String action) {
- // May contain the whole URL or just the protocol.
- Uri uri = url.indexOf(':') >= 0 ? Uri.parse(url) : new Uri.Builder().scheme(url).build();
-
- Intent intent = IntentHelper.getOpenURIIntent(getApplicationContext(), uri.toString(), "",
- TextUtils.isEmpty(action) ? Intent.ACTION_VIEW : action, "");
-
- return IntentHelper.getHandlersForIntent(intent);
- }
-
- @Override
- public String getDefaultChromeURI() {
- // Use the chrome URI specified by Gecko's defaultChromeURI pref.
- return null;
- }
-
- public GeckoView getGeckoView() {
- return mLayerView;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java b/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
deleted file mode 100644
index 18a6e6535..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApplication.java
+++ /dev/null
@@ -1,314 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.app.Application;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.squareup.leakcanary.LeakCanary;
-import com.squareup.leakcanary.RefWatcher;
-
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.LocalBrowserDB;
-import org.mozilla.gecko.distribution.Distribution;
-import org.mozilla.gecko.dlc.DownloadContentService;
-import org.mozilla.gecko.home.HomePanelsManager;
-import org.mozilla.gecko.lwt.LightweightTheme;
-import org.mozilla.gecko.mdns.MulticastDNSManager;
-import org.mozilla.gecko.media.AudioFocusAgent;
-import org.mozilla.gecko.notifications.NotificationClient;
-import org.mozilla.gecko.notifications.NotificationHelper;
-import org.mozilla.gecko.preferences.DistroSharedPrefsImport;
-import org.mozilla.gecko.util.BundleEventListener;
-import org.mozilla.gecko.util.Clipboard;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.io.File;
-import java.lang.reflect.Method;
-
-public class GeckoApplication extends Application
- implements ContextGetter {
- private static final String LOG_TAG = "GeckoApplication";
-
- private static volatile GeckoApplication instance;
-
- private boolean mInBackground;
- private boolean mPausedGecko;
-
- private LightweightTheme mLightweightTheme;
-
- private RefWatcher mRefWatcher;
-
- public GeckoApplication() {
- super();
- instance = this;
- }
-
- public static GeckoApplication get() {
- return instance;
- }
-
- public static RefWatcher getRefWatcher(Context context) {
- GeckoApplication app = (GeckoApplication) context.getApplicationContext();
- return app.mRefWatcher;
- }
-
- public static void watchReference(Context context, Object object) {
- if (context == null) {
- return;
- }
-
- getRefWatcher(context).watch(object);
- }
-
- @Override
- public Context getContext() {
- return this;
- }
-
- @Override
- public SharedPreferences getSharedPreferences() {
- return GeckoSharedPrefs.forApp(this);
- }
-
- /**
- * We need to do locale work here, because we need to intercept
- * each hit to onConfigurationChanged.
- */
- @Override
- public void onConfigurationChanged(Configuration config) {
- Log.d(LOG_TAG, "onConfigurationChanged: " + config.locale +
- ", background: " + mInBackground);
-
- // Do nothing if we're in the background. It'll simply cause a loop
- // (Bug 936756 Comment 11), and it's not necessary.
- if (mInBackground) {
- super.onConfigurationChanged(config);
- return;
- }
-
- // Otherwise, correct the locale. This catches some cases that GeckoApp
- // doesn't get a chance to.
- try {
- BrowserLocaleManager.getInstance().correctLocale(this, getResources(), config);
- } catch (IllegalStateException ex) {
- // GeckoApp hasn't started, so we have no ContextGetter in BrowserLocaleManager.
- Log.w(LOG_TAG, "Couldn't correct locale.", ex);
- }
-
- super.onConfigurationChanged(config);
- }
-
- public void onActivityPause(GeckoActivityStatus activity) {
- mInBackground = true;
-
- if ((activity.isFinishing() == false) &&
- (activity.isGeckoActivityOpened() == false)) {
- // Notify Gecko that we are pausing; the cache service will be
- // shutdown, closing the disk cache cleanly. If the android
- // low memory killer subsequently kills us, the disk cache will
- // be left in a consistent state, avoiding costly cleanup and
- // re-creation.
- GeckoThread.onPause();
- mPausedGecko = true;
-
- final BrowserDB db = BrowserDB.from(this);
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- db.expireHistory(getContentResolver(), BrowserContract.ExpirePriority.NORMAL);
- }
- });
- }
- GeckoNetworkManager.getInstance().stop();
- }
-
- public void onActivityResume(GeckoActivityStatus activity) {
- if (mPausedGecko) {
- GeckoThread.onResume();
- mPausedGecko = false;
- }
-
- GeckoBatteryManager.getInstance().start(this);
- GeckoNetworkManager.getInstance().start(this);
-
- mInBackground = false;
- }
-
- @Override
- protected void attachBaseContext(Context base) {
- super.attachBaseContext(base);
- AppConstants.maybeInstallMultiDex(base);
- }
-
- @Override
- public void onCreate() {
- Log.i(LOG_TAG, "zerdatime " + SystemClock.uptimeMillis() + " - Fennec application start");
-
- mRefWatcher = LeakCanary.install(this);
-
- final Context context = getApplicationContext();
- GeckoAppShell.setApplicationContext(context);
- HardwareUtils.init(context);
- Clipboard.init(context);
- FilePicker.init(context);
- DownloadsIntegration.init();
- HomePanelsManager.getInstance().init(context);
-
- GlobalPageMetadata.getInstance().init();
-
- // We need to set the notification client before launching Gecko, since Gecko could start
- // sending notifications immediately after startup, which we don't want to lose/crash on.
- GeckoAppShell.setNotificationListener(new NotificationClient(context));
- // This getInstance call will force initialization of the NotificationHelper, but does nothing with the result
- NotificationHelper.getInstance(context).init();
-
- MulticastDNSManager.getInstance(context).init();
-
- GeckoService.register();
-
- EventDispatcher.getInstance().registerBackgroundThreadListener(new EventListener(),
- "Profile:Create");
-
- super.onCreate();
- }
-
- public void onDelayedStartup() {
- if (AppConstants.MOZ_ANDROID_GCM) {
- // TODO: only run in main process.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- // It's fine to throw GCM initialization onto a background thread; the registration process requires
- // network access, so is naturally asynchronous. This, of course, races against Gecko page load of
- // content requiring GCM-backed services, like Web Push. There's nothing to be done here.
- try {
- final Class<?> clazz = Class.forName("org.mozilla.gecko.push.PushService");
- final Method onCreate = clazz.getMethod("onCreate", Context.class);
- onCreate.invoke(null, getApplicationContext()); // Method is static.
- } catch (Exception e) {
- Log.e(LOG_TAG, "Got exception during startup; ignoring.", e);
- return;
- }
- }
- });
- }
-
- if (AppConstants.MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE) {
- DownloadContentService.startStudy(this);
- }
-
- GeckoAccessibility.setAccessibilityManagerListeners(this);
-
- AudioFocusAgent.getInstance().attachToContext(this);
- }
-
- private class EventListener implements BundleEventListener
- {
- private void onProfileCreate(final String name, final String path) {
- // Add everything when we're done loading the distribution.
- final Context context = GeckoApplication.this;
- final GeckoProfile profile = GeckoProfile.get(context, name);
- final Distribution distribution = Distribution.getInstance(context);
-
- distribution.addOnDistributionReadyCallback(new Distribution.ReadyCallback() {
- @Override
- public void distributionNotFound() {
- this.distributionFound(null);
- }
-
- @Override
- public void distributionFound(final Distribution distribution) {
- Log.d(LOG_TAG, "Running post-distribution task: bookmarks.");
- // Because we are running in the background, we want to synchronize on the
- // GeckoProfile instance so that we don't race with main thread operations
- // such as locking/unlocking/removing the profile.
- synchronized (profile.getLock()) {
- distributionFoundLocked(distribution);
- }
- }
-
- @Override
- public void distributionArrivedLate(final Distribution distribution) {
- Log.d(LOG_TAG, "Running late distribution task: bookmarks.");
- // Recover as best we can.
- synchronized (profile.getLock()) {
- distributionArrivedLateLocked(distribution);
- }
- }
-
- private void distributionFoundLocked(final Distribution distribution) {
- // Skip initialization if the profile directory has been removed.
- if (!(new File(path)).exists()) {
- return;
- }
-
- final ContentResolver cr = context.getContentResolver();
- final LocalBrowserDB db = new LocalBrowserDB(profile.getName());
-
- // We pass the number of added bookmarks to ensure that the
- // indices of the distribution and default bookmarks are
- // contiguous. Because there are always at least as many
- // bookmarks as there are favicons, we can also guarantee that
- // the favicon IDs won't overlap.
- final int offset = distribution == null ? 0 :
- db.addDistributionBookmarks(cr, distribution, 0);
- db.addDefaultBookmarks(context, cr, offset);
-
- Log.d(LOG_TAG, "Running post-distribution task: android preferences.");
- DistroSharedPrefsImport.importPreferences(context, distribution);
- }
-
- private void distributionArrivedLateLocked(final Distribution distribution) {
- // Skip initialization if the profile directory has been removed.
- if (!(new File(path)).exists()) {
- return;
- }
-
- final ContentResolver cr = context.getContentResolver();
- final LocalBrowserDB db = new LocalBrowserDB(profile.getName());
-
- // We assume we've been called very soon after startup, and so our offset
- // into "Mobile Bookmarks" is the number of bookmarks in the DB.
- final int offset = db.getCount(cr, "bookmarks");
- db.addDistributionBookmarks(cr, distribution, offset);
-
- Log.d(LOG_TAG, "Running late distribution task: android preferences.");
- DistroSharedPrefsImport.importPreferences(context, distribution);
- }
- });
- }
-
- @Override // BundleEventListener
- public void handleMessage(final String event, final Bundle message,
- final EventCallback callback) {
- if ("Profile:Create".equals(event)) {
- onProfileCreate(message.getCharSequence("name").toString(),
- message.getCharSequence("path").toString());
- }
- }
- }
-
- public boolean isApplicationInBackground() {
- return mInBackground;
- }
-
- public LightweightTheme getLightweightTheme() {
- return mLightweightTheme;
- }
-
- public void prepareLightweightTheme() {
- mLightweightTheme = new LightweightTheme(this);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoJavaSampler.java b/mobile/android/base/java/org/mozilla/gecko/GeckoJavaSampler.java
deleted file mode 100644
index 319eccec1..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoJavaSampler.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.os.SystemClock;
-import android.util.Log;
-import android.util.SparseArray;
-
-import org.mozilla.gecko.annotation.WrapForJNI;
-
-import java.lang.Thread;
-import java.util.Set;
-
-public class GeckoJavaSampler {
- private static final String LOGTAG = "JavaSampler";
- private static Thread sSamplingThread;
- private static SamplingThread sSamplingRunnable;
- private static Thread sMainThread;
-
- // Use the same timer primitive as the profiler
- // to get a perfect sample syncing.
- @WrapForJNI
- private static native double getProfilerTime();
-
- private static class Sample {
- public Frame[] mFrames;
- public double mTime;
- public long mJavaTime; // non-zero if Android system time is used
- public Sample(StackTraceElement[] aStack) {
- mFrames = new Frame[aStack.length];
- if (GeckoThread.isStateAtLeast(GeckoThread.State.LIBS_READY)) {
- mTime = getProfilerTime();
- }
- if (mTime == 0.0d) {
- // getProfilerTime is not available yet; either libs are not loaded,
- // or profiling hasn't started on the Gecko side yet
- mJavaTime = SystemClock.elapsedRealtime();
- }
- for (int i = 0; i < aStack.length; i++) {
- mFrames[aStack.length - 1 - i] = new Frame();
- mFrames[aStack.length - 1 - i].fileName = aStack[i].getFileName();
- mFrames[aStack.length - 1 - i].lineNo = aStack[i].getLineNumber();
- mFrames[aStack.length - 1 - i].methodName = aStack[i].getMethodName();
- mFrames[aStack.length - 1 - i].className = aStack[i].getClassName();
- }
- }
- }
- private static class Frame {
- public String fileName;
- public int lineNo;
- public String methodName;
- public String className;
- }
-
- private static class SamplingThread implements Runnable {
- private final int mInterval;
- private final int mSampleCount;
-
- private boolean mPauseSampler;
- private boolean mStopSampler;
-
- private final SparseArray<Sample[]> mSamples = new SparseArray<Sample[]>();
- private int mSamplePos;
-
- public SamplingThread(final int aInterval, final int aSampleCount) {
- // If we sample faster then 10ms we get to many missed samples
- mInterval = Math.max(10, aInterval);
- mSampleCount = aSampleCount;
- }
-
- @Override
- public void run() {
- synchronized (GeckoJavaSampler.class) {
- mSamples.put(0, new Sample[mSampleCount]);
- mSamplePos = 0;
-
- // Find the main thread
- Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
- for (Thread t : threadSet) {
- if (t.getName().compareToIgnoreCase("main") == 0) {
- sMainThread = t;
- break;
- }
- }
-
- if (sMainThread == null) {
- Log.e(LOGTAG, "Main thread not found");
- return;
- }
- }
-
- while (true) {
- try {
- Thread.sleep(mInterval);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- synchronized (GeckoJavaSampler.class) {
- if (!mPauseSampler) {
- StackTraceElement[] bt = sMainThread.getStackTrace();
- mSamples.get(0)[mSamplePos] = new Sample(bt);
- mSamplePos = (mSamplePos + 1) % mSamples.get(0).length;
- }
- if (mStopSampler) {
- break;
- }
- }
- }
- }
-
- private Sample getSample(int aThreadId, int aSampleId) {
- if (aThreadId < mSamples.size() && aSampleId < mSamples.get(aThreadId).length &&
- mSamples.get(aThreadId)[aSampleId] != null) {
- int startPos = 0;
- if (mSamples.get(aThreadId)[mSamplePos] != null) {
- startPos = mSamplePos;
- }
- int readPos = (startPos + aSampleId) % mSamples.get(aThreadId).length;
- return mSamples.get(aThreadId)[readPos];
- }
- return null;
- }
- }
-
-
- @WrapForJNI
- public synchronized static String getThreadName(int aThreadId) {
- if (aThreadId == 0 && sMainThread != null) {
- return sMainThread.getName();
- }
- return null;
- }
-
- private synchronized static Sample getSample(int aThreadId, int aSampleId) {
- return sSamplingRunnable.getSample(aThreadId, aSampleId);
- }
-
- @WrapForJNI
- public synchronized static double getSampleTime(int aThreadId, int aSampleId) {
- Sample sample = getSample(aThreadId, aSampleId);
- if (sample != null) {
- if (sample.mJavaTime != 0) {
- return (sample.mJavaTime -
- SystemClock.elapsedRealtime()) + getProfilerTime();
- }
- System.out.println("Sample: " + sample.mTime);
- return sample.mTime;
- }
- return 0;
- }
-
- @WrapForJNI
- public synchronized static String getFrameName(int aThreadId, int aSampleId, int aFrameId) {
- Sample sample = getSample(aThreadId, aSampleId);
- if (sample != null && aFrameId < sample.mFrames.length) {
- Frame frame = sample.mFrames[aFrameId];
- if (frame == null) {
- return null;
- }
- return frame.className + "." + frame.methodName + "()";
- }
- return null;
- }
-
- @WrapForJNI
- public static void start(int aInterval, int aSamples) {
- synchronized (GeckoJavaSampler.class) {
- if (sSamplingRunnable != null) {
- return;
- }
- sSamplingRunnable = new SamplingThread(aInterval, aSamples);
- sSamplingThread = new Thread(sSamplingRunnable, "Java Sampler");
- sSamplingThread.start();
- }
- }
-
- @WrapForJNI
- public static void pause() {
- synchronized (GeckoJavaSampler.class) {
- sSamplingRunnable.mPauseSampler = true;
- }
- }
-
- @WrapForJNI
- public static void unpause() {
- synchronized (GeckoJavaSampler.class) {
- sSamplingRunnable.mPauseSampler = false;
- }
- }
-
- @WrapForJNI
- public static void stop() {
- synchronized (GeckoJavaSampler.class) {
- if (sSamplingThread == null) {
- return;
- }
-
- sSamplingRunnable.mStopSampler = true;
- try {
- sSamplingThread.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- sSamplingThread = null;
- sSamplingRunnable = null;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoMediaPlayer.java b/mobile/android/base/java/org/mozilla/gecko/GeckoMediaPlayer.java
deleted file mode 100644
index c199aad55..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoMediaPlayer.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.json.JSONObject;
-import org.mozilla.gecko.util.EventCallback;
-
-/**
- * Wrapper for MediaRouter types supported by Android, such as Chromecast, Miracast, etc.
- */
-interface GeckoMediaPlayer {
- /**
- * Can return null.
- */
- JSONObject toJSON();
- void load(String title, String url, String type, EventCallback callback);
- void play(EventCallback callback);
- void pause(EventCallback callback);
- void stop(EventCallback callback);
- void start(EventCallback callback);
- void end(EventCallback callback);
- void mirror(EventCallback callback);
- void message(String message, EventCallback callback);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoMessageReceiver.java b/mobile/android/base/java/org/mozilla/gecko/GeckoMessageReceiver.java
deleted file mode 100644
index b7f4870c2..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoMessageReceiver.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-public class GeckoMessageReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (GeckoApp.ACTION_INIT_PW.equals(action)) {
- GeckoAppShell.notifyObservers("Passwords:Init", null);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoPresentationDisplay.java b/mobile/android/base/java/org/mozilla/gecko/GeckoPresentationDisplay.java
deleted file mode 100644
index df9844d7b..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoPresentationDisplay.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.json.JSONObject;
-import org.mozilla.gecko.util.EventCallback;
-
-/**
- * Wrapper for MediaRouter types supported by Android to use for
- * Presentation API, such as Chromecast, Miracast, etc.
- */
-interface GeckoPresentationDisplay {
- /**
- * Can return null.
- */
- JSONObject toJSON();
- void start(EventCallback callback);
- void stop(EventCallback callback);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoProfilesProvider.java b/mobile/android/base/java/org/mozilla/gecko/GeckoProfilesProvider.java
deleted file mode 100644
index 8a9c461c5..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoProfilesProvider.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import java.io.File;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
-import org.mozilla.gecko.db.BrowserContract;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.net.Uri;
-import android.util.Log;
-
-/**
- * This is not a per-profile provider. This provider allows read-only,
- * restricted access to certain attributes of Fennec profiles.
- */
-public class GeckoProfilesProvider extends ContentProvider {
- private static final String LOG_TAG = "GeckoProfilesProvider";
-
- private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
-
- private static final int PROFILES = 100;
- private static final int PROFILES_NAME = 101;
- private static final int PROFILES_DEFAULT = 200;
-
- private static final String[] DEFAULT_ARGS = {
- BrowserContract.Profiles.NAME,
- BrowserContract.Profiles.PATH,
- };
-
- static {
- URI_MATCHER.addURI(BrowserContract.PROFILES_AUTHORITY, "profiles", PROFILES);
- URI_MATCHER.addURI(BrowserContract.PROFILES_AUTHORITY, "profiles/*", PROFILES_NAME);
- URI_MATCHER.addURI(BrowserContract.PROFILES_AUTHORITY, "default", PROFILES_DEFAULT);
- }
-
- @Override
- public String getType(Uri uri) {
- return null;
- }
-
- @Override
- public boolean onCreate() {
- // Successfully loaded.
- return true;
- }
-
- private String[] profileValues(final String name, final String path, int len, int nameIndex, int pathIndex) {
- final String[] values = new String[len];
- if (nameIndex >= 0) {
- values[nameIndex] = name;
- }
- if (pathIndex >= 0) {
- values[pathIndex] = path;
- }
- return values;
- }
-
- protected void addRowForProfile(final MatrixCursor cursor, final int len, final int nameIndex, final int pathIndex, final String name, final String path) {
- if (path == null || name == null) {
- return;
- }
-
- cursor.addRow(profileValues(name, path, len, nameIndex, pathIndex));
- }
-
- protected Cursor getCursorForProfiles(final String[] args, Map<String, String> profiles) {
- // Compute the projection.
- int nameIndex = -1;
- int pathIndex = -1;
- for (int i = 0; i < args.length; ++i) {
- if (BrowserContract.Profiles.NAME.equals(args[i])) {
- nameIndex = i;
- } else if (BrowserContract.Profiles.PATH.equals(args[i])) {
- pathIndex = i;
- }
- }
-
- final MatrixCursor cursor = new MatrixCursor(args);
- for (Entry<String, String> entry : profiles.entrySet()) {
- addRowForProfile(cursor, args.length, nameIndex, pathIndex, entry.getKey(), entry.getValue());
- }
- return cursor;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder) {
-
- final String[] args = (projection == null) ? DEFAULT_ARGS : projection;
-
- final File mozillaDir;
- try {
- mozillaDir = GeckoProfileDirectories.getMozillaDirectory(getContext());
- } catch (NoMozillaDirectoryException e) {
- Log.d(LOG_TAG, "No Mozilla directory; cannot query for profiles. Assuming there are none.");
- return new MatrixCursor(projection);
- }
-
- final Map<String, String> matchingProfiles;
-
- final int match = URI_MATCHER.match(uri);
- switch (match) {
- case PROFILES:
- // Return all profiles.
- matchingProfiles = GeckoProfileDirectories.getAllProfiles(mozillaDir);
- break;
- case PROFILES_NAME:
- // Return data about the specified profile.
- final String name = uri.getLastPathSegment();
- matchingProfiles = GeckoProfileDirectories.getProfilesNamed(mozillaDir,
- name);
- break;
- case PROFILES_DEFAULT:
- matchingProfiles = GeckoProfileDirectories.getDefaultProfile(mozillaDir);
- break;
- default:
- throw new UnsupportedOperationException("Unknown query URI " + uri);
- }
-
- return getCursorForProfiles(args, matchingProfiles);
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- throw new IllegalStateException("Inserts not supported.");
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- throw new IllegalStateException("Deletes not supported.");
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
- throw new IllegalStateException("Updates not supported.");
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoService.java b/mobile/android/base/java/org/mozilla/gecko/GeckoService.java
deleted file mode 100644
index 3a99fd2a1..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoService.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.app.AlarmManager;
-import android.app.Service;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.os.IBinder;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
-
-import java.io.File;
-
-import org.mozilla.gecko.util.NativeEventListener;
-import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.gecko.util.EventCallback;
-
-public class GeckoService extends Service {
-
- private static final String LOGTAG = "GeckoService";
- private static final boolean DEBUG = false;
-
- private static final String INTENT_PROFILE_NAME = "org.mozilla.gecko.intent.PROFILE_NAME";
- private static final String INTENT_PROFILE_DIR = "org.mozilla.gecko.intent.PROFILE_DIR";
-
- private static final String INTENT_ACTION_UPDATE_ADDONS = "update-addons";
- private static final String INTENT_ACTION_CREATE_SERVICES = "create-services";
-
- private static final String INTENT_SERVICE_CATEGORY = "category";
- private static final String INTENT_SERVICE_DATA = "data";
-
- private static class EventListener implements NativeEventListener {
- @Override // NativeEventListener
- public void handleMessage(final String event,
- final NativeJSObject message,
- final EventCallback callback) {
- final Context context = GeckoAppShell.getApplicationContext();
- switch (event) {
- case "Gecko:ScheduleRun":
- if (DEBUG) {
- Log.d(LOGTAG, "Scheduling " + message.getString("action") +
- " @ " + message.getInt("interval") + "ms");
- }
-
- final Intent intent = getIntentForAction(context, message.getString("action"));
- final PendingIntent pendingIntent = PendingIntent.getService(
- context, /* requestCode */ 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
-
- final AlarmManager am = (AlarmManager)
- context.getSystemService(Context.ALARM_SERVICE);
- // Cancel any previous alarm and schedule a new one.
- am.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,
- message.getInt("trigger"),
- message.getInt("interval"),
- pendingIntent);
- break;
-
- default:
- throw new UnsupportedOperationException(event);
- }
- }
- }
-
- private static final EventListener EVENT_LISTENER = new EventListener();
-
- public static void register() {
- if (DEBUG) {
- Log.d(LOGTAG, "Registered listener");
- }
- EventDispatcher.getInstance().registerGeckoThreadListener(EVENT_LISTENER,
- "Gecko:ScheduleRun");
- }
-
- public static void unregister() {
- if (DEBUG) {
- Log.d(LOGTAG, "Unregistered listener");
- }
- EventDispatcher.getInstance().unregisterGeckoThreadListener(EVENT_LISTENER,
- "Gecko:ScheduleRun");
- }
-
- @Override // Service
- public void onCreate() {
- GeckoAppShell.ensureCrashHandling();
- GeckoThread.onResume();
- super.onCreate();
-
- if (DEBUG) {
- Log.d(LOGTAG, "Created");
- }
- }
-
- @Override // Service
- public void onDestroy() {
- GeckoThread.onPause();
-
- // We want to block here if we can, so we don't get killed when Gecko is in the
- // middle of handling onPause().
- if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
- GeckoThread.waitOnGecko();
- }
-
- if (DEBUG) {
- Log.d(LOGTAG, "Destroyed");
- }
- super.onDestroy();
- }
-
- private static Intent getIntentForAction(final Context context, final String action) {
- final Intent intent = new Intent(action, /* uri */ null, context, GeckoService.class);
- final GeckoProfile profile = GeckoThread.getActiveProfile();
- if (profile != null) {
- setIntentProfile(intent, profile.getName(), profile.getDir().getAbsolutePath());
- }
- return intent;
- }
-
- public static Intent getIntentToCreateServices(final Context context, final String category, final String data) {
- final Intent intent = getIntentForAction(context, INTENT_ACTION_CREATE_SERVICES);
- intent.putExtra(INTENT_SERVICE_CATEGORY, category);
- intent.putExtra(INTENT_SERVICE_DATA, data);
- return intent;
- }
-
- public static Intent getIntentToCreateServices(final Context context, final String category) {
- return getIntentToCreateServices(context, category, /* data */ null);
- }
-
- public static void setIntentProfile(final Intent intent, final String profileName,
- final String profileDir) {
- intent.putExtra(INTENT_PROFILE_NAME, profileName);
- intent.putExtra(INTENT_PROFILE_DIR, profileDir);
- }
-
- private int handleIntent(final Intent intent, final int startId) {
- if (DEBUG) {
- Log.d(LOGTAG, "Handling " + intent.getAction());
- }
-
- final String profileName = intent.getStringExtra(INTENT_PROFILE_NAME);
- final String profileDir = intent.getStringExtra(INTENT_PROFILE_DIR);
-
- if (profileName == null) {
- throw new IllegalArgumentException("Intent must specify profile.");
- }
-
- if (!GeckoThread.initWithProfile(profileName != null ? profileName : "",
- profileDir != null ? new File(profileDir) : null)) {
- Log.w(LOGTAG, "Ignoring due to profile mismatch: " +
- profileName + " [" + profileDir + ']');
-
- final GeckoProfile profile = GeckoThread.getActiveProfile();
- if (profile != null) {
- Log.w(LOGTAG, "Current profile is " + profile.getName() +
- " [" + profile.getDir().getAbsolutePath() + ']');
- }
- stopSelf(startId);
- return Service.START_NOT_STICKY;
- }
-
- GeckoThread.launch();
-
- switch (intent.getAction()) {
- case INTENT_ACTION_UPDATE_ADDONS:
- // Run the add-on update service. Because the service is automatically invoked
- // when loading Gecko, we don't have to do anything else here.
- break;
-
- case INTENT_ACTION_CREATE_SERVICES:
- final String category = intent.getStringExtra(INTENT_SERVICE_CATEGORY);
- final String data = intent.getStringExtra(INTENT_SERVICE_DATA);
-
- if (category == null) {
- break;
- }
- GeckoThread.createServices(category, data);
- break;
-
- default:
- Log.w(LOGTAG, "Unknown request: " + intent);
- }
-
- stopSelf(startId);
- return Service.START_NOT_STICKY;
- }
-
- @Override // Service
- public int onStartCommand(final Intent intent, final int flags, final int startId) {
- if (intent == null) {
- return Service.START_NOT_STICKY;
- }
- try {
- return handleIntent(intent, startId);
- } catch (final Throwable e) {
- Log.e(LOGTAG, "Cannot handle intent: " + intent, e);
- return Service.START_NOT_STICKY;
- }
- }
-
- @Override // Service
- public IBinder onBind(final Intent intent) {
- return null;
- }
-
- public static void startGecko(final GeckoProfile profile, final String args, final Context context) {
- if (GeckoThread.isLaunched()) {
- if (DEBUG) {
- Log.v(LOGTAG, "already launched");
- }
- return;
- }
-
- Handler handler = new Handler(Looper.getMainLooper());
- handler.post(new Runnable() {
- @Override
- public void run() {
- GeckoAppShell.ensureCrashHandling();
- GeckoAppShell.setApplicationContext(context);
- GeckoThread.onResume();
-
- GeckoThread.init(profile, args, null, false);
- GeckoThread.launch();
-
- if (DEBUG) {
- Log.v(LOGTAG, "warmed up (launched)");
- }
- }
- });
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/GeckoUpdateReceiver.java b/mobile/android/base/java/org/mozilla/gecko/GeckoUpdateReceiver.java
deleted file mode 100644
index f73c42e40..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoUpdateReceiver.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.updater.UpdateServiceHelper;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-public class GeckoUpdateReceiver extends BroadcastReceiver
-{
- @Override
- public void onReceive(Context context, Intent intent) {
- if (UpdateServiceHelper.ACTION_CHECK_UPDATE_RESULT.equals(intent.getAction())) {
- String result = intent.getStringExtra("result");
- if (GeckoAppShell.getGeckoInterface() != null && result != null) {
- GeckoAppShell.getGeckoInterface().notifyCheckUpdateResult(result);
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/GlobalHistory.java b/mobile/android/base/java/org/mozilla/gecko/GlobalHistory.java
deleted file mode 100644
index c1d9c4939..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/GlobalHistory.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import java.lang.ref.SoftReference;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.Queue;
-import java.util.Set;
-
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.reader.ReaderModeUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.util.Log;
-
-class GlobalHistory {
- private static final String LOGTAG = "GeckoGlobalHistory";
-
- public static final String EVENT_URI_AVAILABLE_IN_HISTORY = "URI_INSERTED_TO_HISTORY";
- public static final String EVENT_PARAM_URI = "uri";
-
- private static final String TELEMETRY_HISTOGRAM_ADD = "FENNEC_GLOBALHISTORY_ADD_MS";
- private static final String TELEMETRY_HISTOGRAM_UPDATE = "FENNEC_GLOBALHISTORY_UPDATE_MS";
- private static final String TELEMETRY_HISTOGRAM_BUILD_VISITED_LINK = "FENNEC_GLOBALHISTORY_VISITED_BUILD_MS";
-
- private static final GlobalHistory sInstance = new GlobalHistory();
-
- static GlobalHistory getInstance() {
- return sInstance;
- }
-
- // this is the delay between receiving a URI check request and processing it.
- // this allows batching together multiple requests and processing them together,
- // which is more efficient.
- private static final long BATCHING_DELAY_MS = 100;
-
- private final Handler mHandler; // a background thread on which we can process requests
-
- // Note: These fields are accessed through the NotificationRunnable inner class.
- final Queue<String> mPendingUris; // URIs that need to be checked
- SoftReference<Set<String>> mVisitedCache; // cache of the visited URI list
- boolean mProcessing; // = false // whether or not the runnable is queued/working
-
- private class NotifierRunnable implements Runnable {
- private final ContentResolver mContentResolver;
- private final BrowserDB mDB;
-
- public NotifierRunnable(final Context context) {
- mContentResolver = context.getContentResolver();
- mDB = BrowserDB.from(context);
- }
-
- @Override
- public void run() {
- Set<String> visitedSet = mVisitedCache.get();
- if (visitedSet == null) {
- // The cache was wiped. Repopulate it.
- Log.w(LOGTAG, "Rebuilding visited link set...");
- final long start = SystemClock.uptimeMillis();
- final Cursor c = mDB.getAllVisitedHistory(mContentResolver);
- if (c == null) {
- return;
- }
-
- try {
- visitedSet = new HashSet<String>();
- if (c.moveToFirst()) {
- do {
- visitedSet.add(c.getString(0));
- } while (c.moveToNext());
- }
- mVisitedCache = new SoftReference<Set<String>>(visitedSet);
- final long end = SystemClock.uptimeMillis();
- final long took = end - start;
- Telemetry.addToHistogram(TELEMETRY_HISTOGRAM_BUILD_VISITED_LINK, (int) Math.min(took, Integer.MAX_VALUE));
- } finally {
- c.close();
- }
- }
-
- // This runs on the same handler thread as the checkUriVisited code,
- // so no synchronization is needed.
- while (true) {
- final String uri = mPendingUris.poll();
- if (uri == null) {
- break;
- }
-
- if (visitedSet.contains(uri)) {
- GeckoAppShell.notifyUriVisited(uri);
- }
- }
-
- mProcessing = false;
- }
- };
-
- private GlobalHistory() {
- mHandler = ThreadUtils.getBackgroundHandler();
- mPendingUris = new LinkedList<String>();
- mVisitedCache = new SoftReference<Set<String>>(null);
- }
-
- public void addToGeckoOnly(String uri) {
- Set<String> visitedSet = mVisitedCache.get();
- if (visitedSet != null) {
- visitedSet.add(uri);
- }
- GeckoAppShell.notifyUriVisited(uri);
- }
-
- public void add(final Context context, final BrowserDB db, String uri) {
- ThreadUtils.assertOnBackgroundThread();
- final long start = SystemClock.uptimeMillis();
-
- // stripAboutReaderUrl only removes about:reader if present, in all other cases the original string is returned
- final String uriToStore = ReaderModeUtils.stripAboutReaderUrl(uri);
-
- db.updateVisitedHistory(context.getContentResolver(), uriToStore);
-
- final long end = SystemClock.uptimeMillis();
- final long took = end - start;
- Telemetry.addToHistogram(TELEMETRY_HISTOGRAM_ADD, (int) Math.min(took, Integer.MAX_VALUE));
- addToGeckoOnly(uriToStore);
- dispatchUriAvailableMessage(uri);
- }
-
- @SuppressWarnings("static-method")
- public void update(final ContentResolver cr, final BrowserDB db, String uri, String title) {
- ThreadUtils.assertOnBackgroundThread();
- final long start = SystemClock.uptimeMillis();
-
- final String uriToStore = ReaderModeUtils.stripAboutReaderUrl(uri);
-
- db.updateHistoryTitle(cr, uriToStore, title);
-
- final long end = SystemClock.uptimeMillis();
- final long took = end - start;
- Telemetry.addToHistogram(TELEMETRY_HISTOGRAM_UPDATE, (int) Math.min(took, Integer.MAX_VALUE));
- }
-
- public void checkUriVisited(final String uri) {
- final String storedURI = ReaderModeUtils.stripAboutReaderUrl(uri);
-
- final NotifierRunnable runnable = new NotifierRunnable(GeckoAppShell.getContext());
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- // this runs on the same handler thread as the processing loop,
- // so no synchronization needed
- mPendingUris.add(storedURI);
- if (mProcessing) {
- // there's already a runnable queued up or working away, so
- // no need to post another
- return;
- }
- mProcessing = true;
- mHandler.postDelayed(runnable, BATCHING_DELAY_MS);
- }
- });
- }
-
- private void dispatchUriAvailableMessage(String uri) {
- final Bundle message = new Bundle();
- message.putString(EVENT_PARAM_URI, uri);
- EventDispatcher.getInstance().dispatch(EVENT_URI_AVAILABLE_IN_HISTORY, message);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/GlobalPageMetadata.java b/mobile/android/base/java/org/mozilla/gecko/GlobalPageMetadata.java
deleted file mode 100644
index d9d12962c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/GlobalPageMetadata.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.content.ContentProviderClient;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
-import android.util.Log;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.util.BundleEventListener;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/**
- * Provides access to metadata information about websites.
- *
- * While storing, in case of timing issues preventing us from looking up History GUID by a given uri,
- * we queue up metadata and wait for GlobalHistory to let us know history record is now available.
- *
- * TODO Bug 1313515: selection of metadata for a given uri/history_GUID
- *
- * @author grisha
- */
-/* package-local */ class GlobalPageMetadata implements BundleEventListener {
- private static final String LOG_TAG = "GeckoGlobalPageMetadata";
-
- private static final GlobalPageMetadata instance = new GlobalPageMetadata();
-
- private static final String KEY_HAS_IMAGE = "hasImage";
- private static final String KEY_METADATA_JSON = "metadataJSON";
-
- private static final int MAX_METADATA_QUEUE_SIZE = 15;
-
- private final Map<String, Bundle> queuedMetadata = Collections.synchronizedMap(new LimitedLinkedHashMap<String, Bundle>());
-
- public static GlobalPageMetadata getInstance() {
- return instance;
- }
-
- private static class LimitedLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
- private static final long serialVersionUID = 6359725112736360244L;
-
- @Override
- protected boolean removeEldestEntry(Entry<K, V> eldest) {
- if (size() > MAX_METADATA_QUEUE_SIZE) {
- Log.w(LOG_TAG, "Page metadata queue is full. Dropping oldest metadata.");
- return true;
- }
- return false;
- }
- }
-
- private GlobalPageMetadata() {}
-
- public void init() {
- EventDispatcher
- .getInstance()
- .registerBackgroundThreadListener(this, GlobalHistory.EVENT_URI_AVAILABLE_IN_HISTORY);
- }
-
- public void add(BrowserDB db, ContentProviderClient contentProviderClient, String uri, boolean hasImage, @NonNull String metadataJSON) {
- ThreadUtils.assertOnBackgroundThread();
-
- // NB: Other than checking that JSON is valid and trimming it,
- // we do not process metadataJSON in any way, trusting our source.
- doAddOrQueue(db, contentProviderClient, uri, hasImage, metadataJSON);
- }
-
- @VisibleForTesting
- /*package-local */ void doAddOrQueue(BrowserDB db, ContentProviderClient contentProviderClient, String uri, boolean hasImage, @NonNull String metadataJSON) {
- final String preparedMetadataJSON;
- try {
- preparedMetadataJSON = prepareJSON(metadataJSON);
- } catch (JSONException e) {
- Log.e(LOG_TAG, "Couldn't process metadata JSON", e);
- return;
- }
-
- // Don't bother queuing this if deletions fails to find a corresponding history record.
- // If we can't delete metadata because it didn't exist yet, that's OK.
- if (preparedMetadataJSON.equals("{}")) {
- final int deleted = db.deletePageMetadata(contentProviderClient, uri);
- // We could delete none if history record for uri isn't present.
- // We must delete one if history record for uri is present.
- if (deleted != 0 && deleted != 1) {
- throw new IllegalStateException("Deleted unexpected number of page metadata records: " + deleted);
- }
- return;
- }
-
- // If we could insert page metadata, we're done.
- if (db.insertPageMetadata(contentProviderClient, uri, hasImage, preparedMetadataJSON)) {
- return;
- }
-
- // Otherwise, we need to queue it for future insertion when history record is available.
- Bundle bundledMetadata = new Bundle();
- bundledMetadata.putBoolean(KEY_HAS_IMAGE, hasImage);
- bundledMetadata.putString(KEY_METADATA_JSON, preparedMetadataJSON);
- queuedMetadata.put(uri, bundledMetadata);
- }
-
- @VisibleForTesting
- /* package-local */ int getMetadataQueueSize() {
- return queuedMetadata.size();
- }
-
- @Override
- public void handleMessage(String event, Bundle message, EventCallback callback) {
- ThreadUtils.assertOnBackgroundThread();
-
- if (!GlobalHistory.EVENT_URI_AVAILABLE_IN_HISTORY.equals(event)) {
- return;
- }
-
- final String uri = message.getString(GlobalHistory.EVENT_PARAM_URI);
- if (TextUtils.isEmpty(uri)) {
- return;
- }
-
- final Bundle bundledMetadata;
- synchronized (queuedMetadata) {
- if (!queuedMetadata.containsKey(uri)) {
- return;
- }
-
- bundledMetadata = queuedMetadata.get(uri);
- queuedMetadata.remove(uri);
- }
-
- insertMetadataBundleForUri(uri, bundledMetadata);
- }
-
- private void insertMetadataBundleForUri(String uri, Bundle bundledMetadata) {
- final boolean hasImage = bundledMetadata.getBoolean(KEY_HAS_IMAGE);
- final String metadataJSON = bundledMetadata.getString(KEY_METADATA_JSON);
-
- // Acquire CPC, must be released in this function.
- final ContentProviderClient contentProviderClient = GeckoAppShell.getApplicationContext()
- .getContentResolver()
- .acquireContentProviderClient(BrowserContract.PageMetadata.CONTENT_URI);
-
- // Pre-conditions...
- if (contentProviderClient == null) {
- Log.e(LOG_TAG, "Couldn't acquire content provider client");
- return;
- }
-
- if (TextUtils.isEmpty(metadataJSON)) {
- Log.e(LOG_TAG, "Metadata bundle contained empty metadata json");
- return;
- }
-
- // Insert!
- try {
- add(
- BrowserDB.from(GeckoThread.getActiveProfile()),
- contentProviderClient,
- uri, hasImage, metadataJSON
- );
- } finally {
- contentProviderClient.release();
- }
- }
-
- private String prepareJSON(String json) throws JSONException {
- return (new JSONObject(json)).toString();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/GuestSession.java b/mobile/android/base/java/org/mozilla/gecko/GuestSession.java
deleted file mode 100644
index 69502f44a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/GuestSession.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko;
-
-import android.app.KeyguardManager;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.support.v4.app.NotificationCompat;
-import android.view.Window;
-import android.view.WindowManager;
-
-// Utility methods for entering/exiting guest mode.
-public final class GuestSession {
- private static final String LOGTAG = "GeckoGuestSession";
-
- public static final String NOTIFICATION_INTENT = "org.mozilla.gecko.GUEST_SESSION_INPROGRESS";
-
- private static PendingIntent getNotificationIntent(Context context) {
- Intent intent = new Intent(NOTIFICATION_INTENT);
- intent.setClassName(context, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
- return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- public static void showNotification(Context context) {
- final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
- final Resources res = context.getResources();
- builder.setContentTitle(res.getString(R.string.guest_browsing_notification_title))
- .setContentText(res.getString(R.string.guest_browsing_notification_text))
- .setSmallIcon(R.drawable.alert_guest)
- .setOngoing(true)
- .setContentIntent(getNotificationIntent(context));
-
- final NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- manager.notify(R.id.guestNotification, builder.build());
- }
-
- public static void hideNotification(Context context) {
- final NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- manager.cancel(R.id.guestNotification);
- }
-
- public static void onNotificationIntentReceived(BrowserApp context) {
- context.showGuestModeDialog(BrowserApp.GuestModeDialog.LEAVING);
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/IntentHelper.java b/mobile/android/base/java/org/mozilla/gecko/IntentHelper.java
deleted file mode 100644
index e2f34f926..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/IntentHelper.java
+++ /dev/null
@@ -1,599 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.overlays.ui.ShareDialog;
-import org.mozilla.gecko.util.ActivityResultHandler;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.JSONUtils;
-import org.mozilla.gecko.util.NativeEventListener;
-import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.gecko.widget.ExternalIntentDuringPrivateBrowsingPromptFragment;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.net.Uri;
-import android.provider.Browser;
-import android.support.annotation.Nullable;
-import android.support.v4.app.FragmentActivity;
-import android.text.TextUtils;
-import android.util.Log;
-import android.webkit.MimeTypeMap;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLEncoder;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-
-public final class IntentHelper implements GeckoEventListener,
- NativeEventListener {
-
- private static final String LOGTAG = "GeckoIntentHelper";
- private static final String[] EVENTS = {
- "Intent:GetHandlers",
- "Intent:Open",
- "Intent:OpenForResult",
- };
-
- private static final String[] NATIVE_EVENTS = {
- "Intent:OpenNoHandler",
- };
-
- // via http://developer.android.com/distribute/tools/promote/linking.html
- private static String MARKET_INTENT_URI_PACKAGE_PREFIX = "market://details?id=";
- private static String EXTRA_BROWSER_FALLBACK_URL = "browser_fallback_url";
-
- /** A partial URI to an error page - the encoded error URI should be appended before loading. */
- private static String UNKNOWN_PROTOCOL_URI_PREFIX = "about:neterror?e=unknownProtocolFound&u=";
-
- private static IntentHelper instance;
-
- private final FragmentActivity activity;
-
- private IntentHelper(final FragmentActivity activity) {
- this.activity = activity;
- EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener) this, EVENTS);
- EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener) this, NATIVE_EVENTS);
- }
-
- public static IntentHelper init(final FragmentActivity activity) {
- if (instance == null) {
- instance = new IntentHelper(activity);
- } else {
- Log.w(LOGTAG, "IntentHelper.init() called twice, ignoring.");
- }
-
- return instance;
- }
-
- public static void destroy() {
- if (instance != null) {
- EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener) instance, EVENTS);
- EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener) instance, NATIVE_EVENTS);
- instance = null;
- }
- }
-
- /**
- * Given the inputs to <code>getOpenURIIntent</code>, plus an optional
- * package name and class name, create and fire an intent to open the
- * provided URI. If a class name is specified but a package name is not,
- * we will default to using the current fennec package.
- *
- * @param targetURI the string spec of the URI to open.
- * @param mimeType an optional MIME type string.
- * @param packageName an optional app package name.
- * @param className an optional intent class name.
- * @param action an Android action specifier, such as
- * <code>Intent.ACTION_SEND</code>.
- * @param title the title to use in <code>ACTION_SEND</code> intents.
- * @param showPromptInPrivateBrowsing whether or not the user should be prompted when opening
- * this uri from private browsing. This should be true
- * when the user doesn't explicitly choose to open an an
- * external app (e.g. just clicked a link).
- * @return true if the activity started successfully or the user was prompted to open the
- * application; false otherwise.
- */
- public static boolean openUriExternal(String targetURI,
- String mimeType,
- String packageName,
- String className,
- String action,
- String title,
- final boolean showPromptInPrivateBrowsing) {
- final GeckoAppShell.GeckoInterface gi = GeckoAppShell.getGeckoInterface();
- final Context activityContext = gi != null ? gi.getActivity() : null;
- final Context context = activityContext != null ? activityContext : GeckoAppShell.getApplicationContext();
- final Intent intent = getOpenURIIntent(context, targetURI,
- mimeType, action, title);
-
- if (intent == null) {
- return false;
- }
-
- if (!TextUtils.isEmpty(className)) {
- if (!TextUtils.isEmpty(packageName)) {
- intent.setClassName(packageName, className);
- } else {
- // Default to using the fennec app context.
- intent.setClassName(context, className);
- }
- }
-
- if (!showPromptInPrivateBrowsing || activityContext == null) {
- if (activityContext == null) {
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- }
- return ActivityHandlerHelper.startIntentAndCatch(LOGTAG, context, intent);
- } else {
- // Ideally we retrieve the Activity from the calling args, rather than
- // statically, but since this method is called from Gecko and I'm
- // unfamiliar with that code, this is a simpler solution.
- final FragmentActivity fragmentActivity = (FragmentActivity) activityContext;
- return ExternalIntentDuringPrivateBrowsingPromptFragment.showDialogOrAndroidChooser(
- context, fragmentActivity.getSupportFragmentManager(), intent);
- }
- }
-
- public static boolean hasHandlersForIntent(Intent intent) {
- try {
- return !GeckoAppShell.queryIntentActivities(intent).isEmpty();
- } catch (Exception ex) {
- Log.e(LOGTAG, "Exception in hasHandlersForIntent");
- return false;
- }
- }
-
- public static String[] getHandlersForIntent(Intent intent) {
- final PackageManager pm = GeckoAppShell.getApplicationContext().getPackageManager();
- try {
- final List<ResolveInfo> list = GeckoAppShell.queryIntentActivities(intent);
-
- int numAttr = 4;
- final String[] ret = new String[list.size() * numAttr];
- for (int i = 0; i < list.size(); i++) {
- ResolveInfo resolveInfo = list.get(i);
- ret[i * numAttr] = resolveInfo.loadLabel(pm).toString();
- if (resolveInfo.isDefault)
- ret[i * numAttr + 1] = "default";
- else
- ret[i * numAttr + 1] = "";
- ret[i * numAttr + 2] = resolveInfo.activityInfo.applicationInfo.packageName;
- ret[i * numAttr + 3] = resolveInfo.activityInfo.name;
- }
- return ret;
- } catch (Exception ex) {
- Log.e(LOGTAG, "Exception in getHandlersForIntent");
- return new String[0];
- }
- }
-
- public static Intent getIntentForActionString(String aAction) {
- // Default to the view action if no other action as been specified.
- if (TextUtils.isEmpty(aAction)) {
- return new Intent(Intent.ACTION_VIEW);
- }
- return new Intent(aAction);
- }
-
- /**
- * Given a URI, a MIME type, and a title,
- * produce a share intent which can be used to query all activities
- * than can open the specified URI.
- *
- * @param context a <code>Context</code> instance.
- * @param targetURI the string spec of the URI to open.
- * @param mimeType an optional MIME type string.
- * @param title the title to use in <code>ACTION_SEND</code> intents.
- * @return an <code>Intent</code>, or <code>null</code> if none could be
- * produced.
- */
- public static Intent getShareIntent(final Context context,
- final String targetURI,
- final String mimeType,
- final String title) {
- Intent shareIntent = getIntentForActionString(Intent.ACTION_SEND);
- shareIntent.putExtra(Intent.EXTRA_TEXT, targetURI);
- shareIntent.putExtra(Intent.EXTRA_SUBJECT, title);
- shareIntent.putExtra(ShareDialog.INTENT_EXTRA_DEVICES_ONLY, true);
-
- // Note that EXTRA_TITLE is intended to be used for share dialog
- // titles. Common usage (e.g., Pocket) suggests that it's sometimes
- // interpreted as an alternate to EXTRA_SUBJECT, so we include it.
- shareIntent.putExtra(Intent.EXTRA_TITLE, title);
-
- if (mimeType != null && mimeType.length() > 0) {
- shareIntent.setType(mimeType);
- }
-
- return shareIntent;
- }
-
- /**
- * Given a URI, a MIME type, an Android intent "action", and a title,
- * produce an intent which can be used to start an activity to open
- * the specified URI.
- *
- * @param context a <code>Context</code> instance.
- * @param targetURI the string spec of the URI to open.
- * @param mimeType an optional MIME type string.
- * @param action an Android action specifier, such as
- * <code>Intent.ACTION_SEND</code>.
- * @param title the title to use in <code>ACTION_SEND</code> intents.
- * @return an <code>Intent</code>, or <code>null</code> if none could be
- * produced.
- */
- static Intent getOpenURIIntent(final Context context,
- final String targetURI,
- final String mimeType,
- final String action,
- final String title) {
-
- // The resultant chooser can return non-exported activities in 4.1 and earlier.
- // https://code.google.com/p/android/issues/detail?id=29535
- final Intent intent = getOpenURIIntentInner(context, targetURI, mimeType, action, title);
-
- if (intent != null) {
- // Some applications use this field to return to the same browser after processing the
- // Intent. While there is some danger (e.g. denial of service), other major browsers already
- // use it and so it's the norm.
- intent.putExtra(Browser.EXTRA_APPLICATION_ID, AppConstants.ANDROID_PACKAGE_NAME);
- }
-
- return intent;
- }
-
- private static Intent getOpenURIIntentInner(final Context context, final String targetURI,
- final String mimeType, final String action, final String title) {
-
- if (action.equalsIgnoreCase(Intent.ACTION_SEND)) {
- Intent shareIntent = getShareIntent(context, targetURI, mimeType, title);
- return Intent.createChooser(shareIntent,
- context.getResources().getString(R.string.share_title));
- }
-
- Uri uri = normalizeUriScheme(targetURI.indexOf(':') >= 0 ? Uri.parse(targetURI) : new Uri.Builder().scheme(targetURI).build());
- if (!TextUtils.isEmpty(mimeType)) {
- Intent intent = getIntentForActionString(action);
- intent.setDataAndType(uri, mimeType);
- return intent;
- }
-
- if (!GeckoAppShell.isUriSafeForScheme(uri)) {
- return null;
- }
-
- final String scheme = uri.getScheme();
- if ("intent".equals(scheme) || "android-app".equals(scheme)) {
- final Intent intent;
- try {
- intent = Intent.parseUri(targetURI, 0);
- } catch (final URISyntaxException e) {
- Log.e(LOGTAG, "Unable to parse URI - " + e);
- return null;
- }
-
- final Uri data = intent.getData();
- if (data != null && "file".equals(data.normalizeScheme().getScheme())) {
- Log.w(LOGTAG, "Blocked intent with \"file://\" data scheme.");
- return null;
- }
-
- // Only open applications which can accept arbitrary data from a browser.
- intent.addCategory(Intent.CATEGORY_BROWSABLE);
-
- // Prevent site from explicitly opening our internal activities, which can leak data.
- intent.setComponent(null);
- nullIntentSelector(intent);
-
- return intent;
- }
-
- // Compute our most likely intent, then check to see if there are any
- // custom handlers that would apply.
- // Start with the original URI. If we end up modifying it, we'll
- // overwrite it.
- final String extension = MimeTypeMap.getFileExtensionFromUrl(targetURI);
- final Intent intent = getIntentForActionString(action);
- intent.setData(uri);
-
- if ("file".equals(scheme)) {
- // Only set explicit mimeTypes on file://.
- final String mimeType2 = GeckoAppShell.getMimeTypeFromExtension(extension);
- intent.setType(mimeType2);
- return intent;
- }
-
- // Have a special handling for SMS based schemes, as the query parameters
- // are not extracted from the URI automatically.
- if (!"sms".equals(scheme) && !"smsto".equals(scheme) && !"mms".equals(scheme) && !"mmsto".equals(scheme)) {
- return intent;
- }
-
- final String query = uri.getEncodedQuery();
- if (TextUtils.isEmpty(query)) {
- return intent;
- }
-
- // It is common to see sms*/mms* uris on the web without '//', it is W3C standard not to have the slashes,
- // but android's Uri builder & Uri require the slashes and will interpret those without as malformed.
- String currentUri = uri.toString();
- String correctlyFormattedDataURIScheme = scheme + "://";
- if (!currentUri.contains(correctlyFormattedDataURIScheme)) {
- uri = Uri.parse(currentUri.replaceFirst(scheme + ":", correctlyFormattedDataURIScheme));
- }
-
- final String[] fields = query.split("&");
- boolean shouldUpdateIntent = false;
- String resultQuery = "";
- for (String field : fields) {
- if (field.startsWith("body=")) {
- final String body = Uri.decode(field.substring(5));
- intent.putExtra("sms_body", body);
- shouldUpdateIntent = true;
- } else if (field.startsWith("subject=")) {
- final String subject = Uri.decode(field.substring(8));
- intent.putExtra("subject", subject);
- shouldUpdateIntent = true;
- } else if (field.startsWith("cc=")) {
- final String ccNumber = Uri.decode(field.substring(3));
- String phoneNumber = uri.getAuthority();
- if (phoneNumber != null) {
- uri = uri.buildUpon().encodedAuthority(phoneNumber + ";" + ccNumber).build();
- }
- shouldUpdateIntent = true;
- } else {
- resultQuery = resultQuery.concat(resultQuery.length() > 0 ? "&" + field : field);
- }
- }
-
- if (!shouldUpdateIntent) {
- // No need to rewrite the URI, then.
- return intent;
- }
-
- // Form a new URI without the extracted fields in the query part, and
- // push that into the new Intent.
- final String newQuery = resultQuery.length() > 0 ? "?" + resultQuery : "";
- final Uri pruned = uri.buildUpon().encodedQuery(newQuery).build();
- intent.setData(pruned);
-
- return intent;
- }
-
- // We create a separate method to better encapsulate the @TargetApi use.
- @TargetApi(15)
- private static void nullIntentSelector(final Intent intent) {
- intent.setSelector(null);
- }
-
- /**
- * Return a <code>Uri</code> instance which is equivalent to <code>u</code>,
- * but with a guaranteed-lowercase scheme as if the API level 16 method
- * <code>u.normalizeScheme</code> had been called.
- *
- * @param u the <code>Uri</code> to normalize.
- * @return a <code>Uri</code>, which might be <code>u</code>.
- */
- private static Uri normalizeUriScheme(final Uri u) {
- final String scheme = u.getScheme();
- final String lower = scheme.toLowerCase(Locale.US);
- if (lower.equals(scheme)) {
- return u;
- }
-
- // Otherwise, return a new URI with a normalized scheme.
- return u.buildUpon().scheme(lower).build();
- }
-
- @Override
- public void handleMessage(final String event, final NativeJSObject message, final EventCallback callback) {
- if (event.equals("Intent:OpenNoHandler")) {
- openNoHandler(message, callback);
- }
- }
-
- @Override
- public void handleMessage(String event, JSONObject message) {
- try {
- if (event.equals("Intent:GetHandlers")) {
- getHandlers(message);
- } else if (event.equals("Intent:Open")) {
- open(message);
- } else if (event.equals("Intent:OpenForResult")) {
- openForResult(message);
- }
- } catch (JSONException e) {
- Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
- }
- }
-
- private void getHandlers(JSONObject message) throws JSONException {
- final Intent intent = getOpenURIIntent(activity,
- message.optString("url"),
- message.optString("mime"),
- message.optString("action"),
- message.optString("title"));
- final List<String> appList = Arrays.asList(getHandlersForIntent(intent));
-
- final JSONObject response = new JSONObject();
- response.put("apps", new JSONArray(appList));
- EventDispatcher.sendResponse(message, response);
- }
-
- private void open(JSONObject message) throws JSONException {
- openUriExternal(message.optString("url"),
- message.optString("mime"),
- message.optString("packageName"),
- message.optString("className"),
- message.optString("action"),
- message.optString("title"), false);
- }
-
- private void openForResult(final JSONObject message) throws JSONException {
- Intent intent = getOpenURIIntent(activity,
- message.optString("url"),
- message.optString("mime"),
- message.optString("action"),
- message.optString("title"));
- intent.setClassName(message.optString("packageName"), message.optString("className"));
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-
- final ResultHandler handler = new ResultHandler(message);
- try {
- ActivityHandlerHelper.startIntentForActivity(activity, intent, handler);
- } catch (SecurityException e) {
- Log.w(LOGTAG, "Forbidden to launch activity.", e);
- }
- }
-
- /**
- * Opens a URI without any valid handlers on device. In the best case, a package is specified
- * and we can bring the user directly to the application page in an app market. If a package is
- * not specified and there is a fallback url in the intent extras, we open that url. If neither
- * is present, we alert the user that we were unable to open the link.
- *
- * @param msg A message with the uri with no handlers as the value for the "uri" key
- * @param callback A callback that will be called with success & no params if Java loads a page, or with error and
- * the uri to load if Java does not load a page
- */
- private void openNoHandler(final NativeJSObject msg, final EventCallback callback) {
- final String uri = msg.getString("uri");
-
- if (TextUtils.isEmpty(uri)) {
- Log.w(LOGTAG, "Received empty URL - loading about:neterror");
- callback.sendError(getUnknownProtocolErrorPageUri(""));
- return;
- }
-
- final Intent intent;
- try {
- // TODO (bug 1173626): This will not handle android-app uris on non 5.1 devices.
- intent = Intent.parseUri(uri, 0);
- } catch (final URISyntaxException e) {
- String errorUri;
- try {
- errorUri = getUnknownProtocolErrorPageUri(URLEncoder.encode(uri, "UTF-8"));
- } catch (final UnsupportedEncodingException encodingE) {
- errorUri = getUnknownProtocolErrorPageUri("");
- }
-
- // Don't log the exception to prevent leaking URIs.
- Log.w(LOGTAG, "Unable to parse Intent URI - loading about:neterror");
- callback.sendError(errorUri);
- return;
- }
-
- // For this flow, we follow Chrome's lead:
- // https://developer.chrome.com/multidevice/android/intents
- final String fallbackUrl = intent.getStringExtra(EXTRA_BROWSER_FALLBACK_URL);
- if (isFallbackUrlValid(fallbackUrl)) {
- // Opens the page in JS.
- callback.sendError(fallbackUrl);
-
- } else if (intent.getPackage() != null) {
- // Note on alternative flows: we could get the intent package from a component, however, for
- // security reasons, components are ignored when opening URIs (bug 1168998) so we should
- // ignore it here too.
- //
- // Our old flow used to prompt the user to search for their app in the market by scheme and
- // while this could help the user find a new app, there is not always a correlation in
- // scheme to application name and we could end up steering the user wrong (potentially to
- // malicious software). Better to leave that one alone.
- final String marketUri = MARKET_INTENT_URI_PACKAGE_PREFIX + intent.getPackage();
- final Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(marketUri));
- marketIntent.addCategory(Intent.CATEGORY_BROWSABLE);
- marketIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- // (Bug 1192436) We don't know if marketIntent matches any Activities (e.g. non-Play
- // Store devices). If it doesn't, clicking the link will cause no action to occur.
- ExternalIntentDuringPrivateBrowsingPromptFragment.showDialogOrAndroidChooser(
- activity, activity.getSupportFragmentManager(), marketIntent);
- callback.sendSuccess(null);
-
- } else {
- // We return the error page here, but it will only be shown if we think the load did
- // not come from clicking a link. Chrome does not show error pages in that case, and
- // many websites have catered to this behavior. For example, the site might set a timeout and load a play
- // store url for their app if the intent link fails to load, i.e. the app is not installed.
- // These work-arounds would often end with our users seeing about:neterror instead of the intended experience.
- // While I feel showing about:neterror is a better solution for users (when not hacked around),
- // we should match the status quo for the good of our users.
- //
- // Don't log the URI to prevent leaking it.
- Log.w(LOGTAG, "Unable to open URI, maybe showing neterror");
- callback.sendError(getUnknownProtocolErrorPageUri(intent.getData().toString()));
- }
- }
-
- private static boolean isFallbackUrlValid(@Nullable final String fallbackUrl) {
- if (fallbackUrl == null) {
- return false;
- }
-
- try {
- final String anyCaseScheme = new URI(fallbackUrl).getScheme();
- final String scheme = (anyCaseScheme == null) ? null : anyCaseScheme.toLowerCase(Locale.US);
- if ("http".equals(scheme) || "https".equals(scheme)) {
- return true;
- } else {
- Log.w(LOGTAG, "Fallback URI uses unsupported scheme: " + scheme + ". Try http or https.");
- }
- } catch (final URISyntaxException e) {
- // Do not include Exception to avoid leaking uris.
- Log.w(LOGTAG, "URISyntaxException parsing fallback URI");
- }
- return false;
- }
-
- /**
- * Returns an about:neterror uri with the unknownProtocolFound text as a parameter.
- * @param encodedUri The encoded uri. While the page does not open correctly without specifying
- * a uri parameter, it happily accepts the empty String so this argument may
- * be the empty String.
- */
- private String getUnknownProtocolErrorPageUri(final String encodedUri) {
- return UNKNOWN_PROTOCOL_URI_PREFIX + encodedUri;
- }
-
- private static class ResultHandler implements ActivityResultHandler {
- private final JSONObject message;
-
- public ResultHandler(JSONObject message) {
- this.message = message;
- }
-
- @Override
- public void onActivityResult(int resultCode, Intent data) {
- JSONObject response = new JSONObject();
- try {
- if (data != null) {
- if (data.getExtras() != null) {
- response.put("extras", JSONUtils.bundleToJSON(data.getExtras()));
- }
- if (data.getData() != null) {
- response.put("uri", data.getData().toString());
- }
- }
- response.put("resultCode", resultCode);
- } catch (JSONException e) {
- Log.w(LOGTAG, "Error building JSON response.", e);
- }
- EventDispatcher.sendResponse(message, response);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/LauncherActivity.java b/mobile/android/base/java/org/mozilla/gecko/LauncherActivity.java
deleted file mode 100644
index 4de8fa423..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/LauncherActivity.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.customtabs.CustomTabsIntent;
-
-import org.mozilla.gecko.customtabs.CustomTabsActivity;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.mozglue.SafeIntent;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.tabqueue.TabQueueHelper;
-import org.mozilla.gecko.tabqueue.TabQueueService;
-
-/**
- * Activity that receives incoming Intents and dispatches them to the appropriate activities (e.g. browser, custom tabs, web app).
- */
-public class LauncherActivity extends Activity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- GeckoAppShell.ensureCrashHandling();
-
- final SafeIntent safeIntent = new SafeIntent(getIntent());
-
- // If it's not a view intent, it won't be a custom tabs intent either. Just launch!
- if (!isViewIntentWithURL(safeIntent)) {
- dispatchNormalIntent();
-
- // Is this a custom tabs intent, and are custom tabs enabled?
- } else if (AppConstants.MOZ_ANDROID_CUSTOM_TABS && isCustomTabsIntent(safeIntent)
- && isCustomTabsEnabled()) {
- dispatchCustomTabsIntent();
-
- // Can we dispatch this VIEW action intent to the tab queue service?
- } else if (!safeIntent.getBooleanExtra(BrowserContract.SKIP_TAB_QUEUE_FLAG, false)
- && TabQueueHelper.TAB_QUEUE_ENABLED
- && TabQueueHelper.isTabQueueEnabled(this)) {
- dispatchTabQueueIntent();
-
- // Dispatch this VIEW action intent to the browser.
- } else {
- dispatchNormalIntent();
- }
-
- finish();
- }
-
- /**
- * Launch tab queue service to display overlay.
- */
- private void dispatchTabQueueIntent() {
- Intent intent = new Intent(getIntent());
- intent.setClass(getApplicationContext(), TabQueueService.class);
- startService(intent);
- }
-
- /**
- * Launch the browser activity.
- */
- private void dispatchNormalIntent() {
- Intent intent = new Intent(getIntent());
- intent.setClassName(getApplicationContext(), AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
-
- filterFlags(intent);
-
- startActivity(intent);
- }
-
- private void dispatchCustomTabsIntent() {
- Intent intent = new Intent(getIntent());
- intent.setClassName(getApplicationContext(), CustomTabsActivity.class.getName());
-
- filterFlags(intent);
-
- startActivity(intent);
- }
-
- private static void filterFlags(Intent intent) {
- // Explicitly remove the new task and clear task flags (Our browser activity is a single
- // task activity and we never want to start a second task here). See bug 1280112.
- intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_CLEAR_TASK);
-
- // LauncherActivity is started with the "exclude from recents" flag (set in manifest). We do
- // not want to propagate this flag from the launcher activity to the browser.
- intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- }
-
- private static boolean isViewIntentWithURL(@NonNull final SafeIntent safeIntent) {
- return Intent.ACTION_VIEW.equals(safeIntent.getAction())
- && safeIntent.getDataString() != null;
- }
-
- private static boolean isCustomTabsIntent(@NonNull final SafeIntent safeIntent) {
- return isViewIntentWithURL(safeIntent)
- && safeIntent.hasExtra(CustomTabsIntent.EXTRA_SESSION);
- }
-
- private boolean isCustomTabsEnabled() {
- return GeckoSharedPrefs.forApp(this).getBoolean(GeckoPreferences.PREFS_CUSTOM_TABS, false);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/LocaleManager.java b/mobile/android/base/java/org/mozilla/gecko/LocaleManager.java
deleted file mode 100644
index 795caa925..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/LocaleManager.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import java.util.Locale;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-
-/**
- * Implement this interface to provide Fennec's locale switching functionality.
- *
- * The LocaleManager is responsible for persisting and applying selected locales,
- * and correcting configurations after Android has changed them.
- */
-public interface LocaleManager {
- void initialize(Context context);
-
- /**
- * @return true if locale switching is enabled.
- */
- boolean isEnabled();
- Locale getCurrentLocale(Context context);
- String getAndApplyPersistedLocale(Context context);
- void correctLocale(Context context, Resources resources, Configuration newConfig);
- void updateConfiguration(Context context, Locale locale);
- String setSelectedLocale(Context context, String localeCode);
- boolean systemLocaleDidChange();
- void resetToSystemLocale(Context context);
-
- /**
- * Call this in your onConfigurationChanged handler. This method is expected
- * to do the appropriate thing: if the user has selected a locale, it
- * corrects the incoming configuration; if not, it signals the new locale to
- * use.
- */
- Locale onSystemConfigurationChanged(Context context, Resources resources, Configuration configuration, Locale currentActivityLocale);
- String getFallbackLocaleTag();
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/Locales.java b/mobile/android/base/java/org/mozilla/gecko/Locales.java
deleted file mode 100644
index e030b95e9..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/Locales.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import java.lang.reflect.Method;
-import java.util.Locale;
-
-import org.mozilla.gecko.LocaleManager;
-
-import android.app.Activity;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.StrictMode;
-import android.support.v4.app.FragmentActivity;
-import android.support.v7.app.AppCompatActivity;
-
-/**
- * This is a helper class to do typical locale switching operations without
- * hitting StrictMode errors or adding boilerplate to common activity
- * subclasses.
- *
- * Either call {@link Locales#initializeLocale(Context)} in your
- * <code>onCreate</code> method, or inherit from
- * <code>LocaleAwareFragmentActivity</code> or <code>LocaleAwareActivity</code>.
- */
-public class Locales {
- public static LocaleManager getLocaleManager() {
- try {
- final Class<?> clazz = Class.forName("org.mozilla.gecko.BrowserLocaleManager");
- final Method getInstance = clazz.getMethod("getInstance");
- final LocaleManager localeManager = (LocaleManager) getInstance.invoke(null);
- return localeManager;
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- public static void initializeLocale(Context context) {
- final LocaleManager localeManager = getLocaleManager();
- final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
- StrictMode.allowThreadDiskWrites();
- try {
- localeManager.getAndApplyPersistedLocale(context);
- } finally {
- StrictMode.setThreadPolicy(savedPolicy);
- }
- }
-
- public static abstract class LocaleAwareAppCompatActivity extends AppCompatActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- Locales.initializeLocale(getApplicationContext());
- super.onCreate(savedInstanceState);
- }
-
- }
- public static abstract class LocaleAwareFragmentActivity extends FragmentActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- Locales.initializeLocale(getApplicationContext());
- super.onCreate(savedInstanceState);
- }
- }
-
- public static abstract class LocaleAwareActivity extends Activity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- Locales.initializeLocale(getApplicationContext());
- super.onCreate(savedInstanceState);
- }
- }
-
- /**
- * Sometimes we want just the language for a locale, not the entire language
- * tag. But Java's .getLanguage method is wrong.
- *
- * This method is equivalent to the first part of
- * {@link Locales#getLanguageTag(Locale)}.
- *
- * @return a language string, such as "he" for the Hebrew locales.
- */
- public static String getLanguage(final Locale locale) {
- // Can, but should never be, an empty string.
- final String language = locale.getLanguage();
-
- // Modernize certain language codes.
- if (language.equals("iw")) {
- return "he";
- }
-
- if (language.equals("in")) {
- return "id";
- }
-
- if (language.equals("ji")) {
- return "yi";
- }
-
- return language;
- }
-
- /**
- * Gecko uses locale codes like "es-ES", whereas a Java {@link Locale}
- * stringifies as "es_ES".
- *
- * This method approximates the Java 7 method
- * <code>Locale#toLanguageTag()</code>.
- *
- * @return a locale string suitable for passing to Gecko.
- */
- public static String getLanguageTag(final Locale locale) {
- // If this were Java 7:
- // return locale.toLanguageTag();
-
- final String language = getLanguage(locale);
- final String country = locale.getCountry(); // Can be an empty string.
- if (country.equals("")) {
- return language;
- }
- return language + "-" + country;
- }
-
- public static Locale parseLocaleCode(final String localeCode) {
- int index;
- if ((index = localeCode.indexOf('-')) != -1 ||
- (index = localeCode.indexOf('_')) != -1) {
- final String langCode = localeCode.substring(0, index);
- final String countryCode = localeCode.substring(index + 1);
- return new Locale(langCode, countryCode);
- }
-
- return new Locale(localeCode);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/MediaCastingBar.java b/mobile/android/base/java/org/mozilla/gecko/MediaCastingBar.java
deleted file mode 100644
index bd109058c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/MediaCastingBar.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import org.json.JSONObject;
-
-import android.content.Context;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-public class MediaCastingBar extends RelativeLayout implements View.OnClickListener, GeckoEventListener {
- private static final String LOGTAG = "GeckoMediaCastingBar";
-
- private TextView mCastingTo;
- private ImageButton mMediaPlay;
- private ImageButton mMediaPause;
- private ImageButton mMediaStop;
-
- private boolean mInflated;
-
- public MediaCastingBar(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
- "Casting:Started",
- "Casting:Paused",
- "Casting:Playing",
- "Casting:Stopped");
- }
-
- public void inflateContent() {
- LayoutInflater inflater = LayoutInflater.from(getContext());
- View content = inflater.inflate(R.layout.media_casting, this);
-
- mMediaPlay = (ImageButton) content.findViewById(R.id.media_play);
- mMediaPlay.setOnClickListener(this);
- mMediaPause = (ImageButton) content.findViewById(R.id.media_pause);
- mMediaPause.setOnClickListener(this);
- mMediaStop = (ImageButton) content.findViewById(R.id.media_stop);
- mMediaStop.setOnClickListener(this);
-
- mCastingTo = (TextView) content.findViewById(R.id.media_sending_to);
-
- // Capture clicks on the rest of the view to prevent them from
- // leaking into other views positioned below.
- content.setOnClickListener(this);
-
- mInflated = true;
- }
-
- public void show() {
- if (!mInflated)
- inflateContent();
-
- setVisibility(VISIBLE);
- }
-
- public void hide() {
- setVisibility(GONE);
- }
-
- public void onDestroy() {
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
- "Casting:Started",
- "Casting:Paused",
- "Casting:Playing",
- "Casting:Stopped");
- }
-
- // View.OnClickListener implementation
- @Override
- public void onClick(View v) {
- final int viewId = v.getId();
-
- if (viewId == R.id.media_play) {
- GeckoAppShell.notifyObservers("Casting:Play", "");
- mMediaPlay.setVisibility(GONE);
- mMediaPause.setVisibility(VISIBLE);
- } else if (viewId == R.id.media_pause) {
- GeckoAppShell.notifyObservers("Casting:Pause", "");
- mMediaPause.setVisibility(GONE);
- mMediaPlay.setVisibility(VISIBLE);
- } else if (viewId == R.id.media_stop) {
- GeckoAppShell.notifyObservers("Casting:Stop", "");
- }
- }
-
- // GeckoEventListener implementation
- @Override
- public void handleMessage(final String event, final JSONObject message) {
- final String device = message.optString("device");
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- if (event.equals("Casting:Started")) {
- show();
- if (!TextUtils.isEmpty(device)) {
- mCastingTo.setText(device);
- } else {
- // Should not happen
- mCastingTo.setText("");
- Log.d(LOGTAG, "Device name is empty.");
- }
- mMediaPlay.setVisibility(GONE);
- mMediaPause.setVisibility(VISIBLE);
- } else if (event.equals("Casting:Paused")) {
- mMediaPause.setVisibility(GONE);
- mMediaPlay.setVisibility(VISIBLE);
- } else if (event.equals("Casting:Playing")) {
- mMediaPlay.setVisibility(GONE);
- mMediaPause.setVisibility(VISIBLE);
- } else if (event.equals("Casting:Stopped")) {
- hide();
- }
- }
- });
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/MediaPlayerManager.java b/mobile/android/base/java/org/mozilla/gecko/MediaPlayerManager.java
deleted file mode 100644
index fc0ce82cf..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/MediaPlayerManager.java
+++ /dev/null
@@ -1,323 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v7.media.MediaControlIntent;
-import android.support.v7.media.MediaRouteSelector;
-import android.support.v7.media.MediaRouter;
-import android.support.v7.media.MediaRouter.RouteInfo;
-import android.util.Log;
-
-import com.google.android.gms.cast.CastMediaControlIntent;
-
-import org.json.JSONObject;
-import org.mozilla.gecko.annotation.JNITarget;
-import org.mozilla.gecko.annotation.ReflectionTarget;
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.NativeEventListener;
-import org.mozilla.gecko.util.NativeJSObject;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * Manages a list of GeckoMediaPlayers methods (i.e. Chromecast/Miracast). Routes messages
- * from Gecko to the correct caster based on the id of the display
- */
-public class MediaPlayerManager extends Fragment implements NativeEventListener {
- /**
- * Create a new instance of DetailsFragment, initialized to
- * show the text at 'index'.
- */
-
- private static MediaPlayerManager instance = null;
-
- @ReflectionTarget
- public static MediaPlayerManager getInstance() {
- if (instance != null) {
- return instance;
- }
- if (Versions.feature17Plus) {
- instance = (MediaPlayerManager) new PresentationMediaPlayerManager();
- } else {
- instance = new MediaPlayerManager();
- }
- return instance;
- }
-
- private static final String LOGTAG = "GeckoMediaPlayerManager";
- protected boolean isPresentationMode = false; // Used to prevent mirroring when Presentation API is used.
-
- @ReflectionTarget
- public static final String MEDIA_PLAYER_TAG = "MPManagerFragment";
-
- private static final boolean SHOW_DEBUG = false;
- // Simplified debugging interfaces
- private static void debug(String msg, Exception e) {
- if (SHOW_DEBUG) {
- Log.e(LOGTAG, msg, e);
- }
- }
-
- private static void debug(String msg) {
- if (SHOW_DEBUG) {
- Log.d(LOGTAG, msg);
- }
- }
-
- protected MediaRouter mediaRouter = null;
- protected final Map<String, GeckoMediaPlayer> players = new HashMap<String, GeckoMediaPlayer>();
- protected final Map<String, GeckoPresentationDisplay> displays = new HashMap<String, GeckoPresentationDisplay>(); // used for Presentation API
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
- "MediaPlayer:Load",
- "MediaPlayer:Start",
- "MediaPlayer:Stop",
- "MediaPlayer:Play",
- "MediaPlayer:Pause",
- "MediaPlayer:End",
- "MediaPlayer:Mirror",
- "MediaPlayer:Message",
- "AndroidCastDevice:Start",
- "AndroidCastDevice:Stop",
- "AndroidCastDevice:SyncDevice");
- }
-
- @Override
- @JNITarget
- public void onDestroy() {
- super.onDestroy();
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
- "MediaPlayer:Load",
- "MediaPlayer:Start",
- "MediaPlayer:Stop",
- "MediaPlayer:Play",
- "MediaPlayer:Pause",
- "MediaPlayer:End",
- "MediaPlayer:Mirror",
- "MediaPlayer:Message",
- "AndroidCastDevice:Start",
- "AndroidCastDevice:Stop",
- "AndroidCastDevice:SyncDevice");
- }
-
- // GeckoEventListener implementation
- @Override
- public void handleMessage(String event, final NativeJSObject message, final EventCallback callback) {
- debug(event);
- if (event.startsWith("MediaPlayer:")) {
- final GeckoMediaPlayer player = players.get(message.getString("id"));
- if (player == null) {
- Log.e(LOGTAG, "Couldn't find a player for this id: " + message.getString("id") + " for message: " + event);
- if (callback != null) {
- callback.sendError(null);
- }
- return;
- }
-
- if ("MediaPlayer:Play".equals(event)) {
- player.play(callback);
- } else if ("MediaPlayer:Start".equals(event)) {
- player.start(callback);
- } else if ("MediaPlayer:Stop".equals(event)) {
- player.stop(callback);
- } else if ("MediaPlayer:Pause".equals(event)) {
- player.pause(callback);
- } else if ("MediaPlayer:End".equals(event)) {
- player.end(callback);
- } else if ("MediaPlayer:Mirror".equals(event)) {
- player.mirror(callback);
- } else if ("MediaPlayer:Message".equals(event) && message.has("data")) {
- player.message(message.getString("data"), callback);
- } else if ("MediaPlayer:Load".equals(event)) {
- final String url = message.optString("source", "");
- final String type = message.optString("type", "video/mp4");
- final String title = message.optString("title", "");
- player.load(title, url, type, callback);
- }
- }
-
- if (event.startsWith("AndroidCastDevice:")) {
- if ("AndroidCastDevice:Start".equals(event)) {
- final GeckoPresentationDisplay display = displays.get(message.getString("id"));
- if (display == null) {
- Log.e(LOGTAG, "Couldn't find a display for this id: " + message.getString("id") + " for message: " + event);
- return;
- }
- display.start(callback);
- } else if ("AndroidCastDevice:Stop".equals(event)) {
- final GeckoPresentationDisplay display = displays.get(message.getString("id"));
- if (display == null) {
- Log.e(LOGTAG, "Couldn't find a display for this id: " + message.getString("id") + " for message: " + event);
- return;
- }
- display.stop(callback);
- } else if ("AndroidCastDevice:SyncDevice".equals(event)) {
- for (Map.Entry<String, GeckoPresentationDisplay> entry : displays.entrySet()) {
- GeckoPresentationDisplay display = entry.getValue();
- JSONObject json = display.toJSON();
- if (json == null) {
- break;
- }
- GeckoAppShell.notifyObservers("AndroidCastDevice:Added", json.toString());
- }
- }
- }
- }
-
- private final MediaRouter.Callback callback =
- new MediaRouter.Callback() {
- @Override
- public void onRouteRemoved(MediaRouter router, RouteInfo route) {
- debug("onRouteRemoved: route=" + route);
-
- // Remove from media player list.
- players.remove(route.getId());
- GeckoAppShell.notifyObservers("MediaPlayer:Removed", route.getId());
- updatePresentation();
-
- // Remove from presentation display list.
- displays.remove(route.getId());
- GeckoAppShell.notifyObservers("AndroidCastDevice:Removed", route.getId());
- }
-
- @SuppressWarnings("unused")
- public void onRouteSelected(MediaRouter router, int type, MediaRouter.RouteInfo route) {
- updatePresentation();
- }
-
- // These methods aren't used by the support version Media Router
- @SuppressWarnings("unused")
- public void onRouteUnselected(MediaRouter router, int type, RouteInfo route) {
- updatePresentation();
- }
-
- @Override
- public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo route) {
- updatePresentation();
- }
-
- @Override
- public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
- }
-
- @Override
- public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo route) {
- debug("onRouteAdded: route=" + route);
- final GeckoMediaPlayer player = getMediaPlayerForRoute(route);
- saveAndNotifyOfPlayer("MediaPlayer:Added", route, player);
- updatePresentation();
-
- final GeckoPresentationDisplay display = getPresentationDisplayForRoute(route);
- saveAndNotifyOfDisplay("AndroidCastDevice:Added", route, display);
- }
-
- @Override
- public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
- debug("onRouteChanged: route=" + route);
- final GeckoMediaPlayer player = players.get(route.getId());
- saveAndNotifyOfPlayer("MediaPlayer:Changed", route, player);
- updatePresentation();
-
- final GeckoPresentationDisplay display = displays.get(route.getId());
- saveAndNotifyOfDisplay("AndroidCastDevice:Changed", route, display);
- }
-
- private void saveAndNotifyOfPlayer(final String eventName,
- MediaRouter.RouteInfo route,
- final GeckoMediaPlayer player) {
- if (player == null) {
- return;
- }
-
- final JSONObject json = player.toJSON();
- if (json == null) {
- return;
- }
-
- players.put(route.getId(), player);
- GeckoAppShell.notifyObservers(eventName, json.toString());
- }
-
- private void saveAndNotifyOfDisplay(final String eventName,
- MediaRouter.RouteInfo route,
- final GeckoPresentationDisplay display) {
- if (display == null) {
- return;
- }
-
- final JSONObject json = display.toJSON();
- if (json == null) {
- return;
- }
-
- displays.put(route.getId(), display);
- GeckoAppShell.notifyObservers(eventName, json.toString());
- }
- };
-
- private GeckoMediaPlayer getMediaPlayerForRoute(MediaRouter.RouteInfo route) {
- try {
- if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
- return new ChromeCastPlayer(getActivity(), route);
- }
- } catch (Exception ex) {
- debug("Error handling presentation", ex);
- }
-
- return null;
- }
-
- private GeckoPresentationDisplay getPresentationDisplayForRoute(MediaRouter.RouteInfo route) {
- try {
- if (route.supportsControlCategory(CastMediaControlIntent.categoryForCast(ChromeCastDisplay.REMOTE_DISPLAY_APP_ID))) {
- return new ChromeCastDisplay(getActivity(), route);
- }
- } catch (Exception ex) {
- debug("Error handling presentation", ex);
- }
- return null;
- }
-
- @Override
- public void onPause() {
- super.onPause();
- mediaRouter.removeCallback(callback);
- mediaRouter = null;
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- // The mediaRouter shouldn't exist here, but this is a nice safety check.
- if (mediaRouter != null) {
- return;
- }
-
- mediaRouter = MediaRouter.getInstance(getActivity());
- final MediaRouteSelector selectorBuilder = new MediaRouteSelector.Builder()
- .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
- .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
- .addControlCategory(CastMediaControlIntent.categoryForCast(ChromeCastPlayer.MIRROR_RECEIVER_APP_ID))
- .addControlCategory(CastMediaControlIntent.categoryForCast(ChromeCastDisplay.REMOTE_DISPLAY_APP_ID))
- .build();
- mediaRouter.addCallback(selectorBuilder, callback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
- }
-
- public void setPresentationMode(boolean isPresentationMode) {
- this.isPresentationMode = isPresentationMode;
- }
-
- protected void updatePresentation() { /* Overridden in sub-classes. */ }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/MemoryMonitor.java b/mobile/android/base/java/org/mozilla/gecko/MemoryMonitor.java
deleted file mode 100644
index 94ca761b9..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/MemoryMonitor.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.annotation.WrapForJNI;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserProvider;
-import org.mozilla.gecko.home.ImageLoader;
-import org.mozilla.gecko.icons.storage.MemoryStorage;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentCallbacks2;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.support.v4.content.LocalBroadcastManager;
-import android.util.Log;
-
-/**
- * This is a utility class to keep track of how much memory and disk-space pressure
- * the system is under. It receives input from GeckoActivity via the onLowMemory() and
- * onTrimMemory() functions, and also listens for some system intents related to
- * disk-space notifications. Internally it will track how much memory and disk pressure
- * the system is under, and perform various actions to help alleviate the pressure.
- *
- * Note that since there is no notification for when the system has lots of free memory
- * again, this class also assumes that, over time, the system will free up memory. This
- * assumption is implemented using a timer that slowly lowers the internal memory
- * pressure state if no new low-memory notifications are received.
- *
- * Synchronization note: MemoryMonitor contains an inner class PressureDecrementer. Both
- * of these classes may be accessed from various threads, and have both been designed to
- * be thread-safe. In terms of lock ordering, code holding the PressureDecrementer lock
- * is allowed to pick up the MemoryMonitor lock, but not vice-versa.
- */
-class MemoryMonitor extends BroadcastReceiver {
- private static final String LOGTAG = "GeckoMemoryMonitor";
- private static final String ACTION_MEMORY_DUMP = "org.mozilla.gecko.MEMORY_DUMP";
- private static final String ACTION_FORCE_PRESSURE = "org.mozilla.gecko.FORCE_MEMORY_PRESSURE";
-
- // Memory pressure levels. Keep these in sync with those in AndroidJavaWrappers.h
- private static final int MEMORY_PRESSURE_NONE = 0;
- private static final int MEMORY_PRESSURE_CLEANUP = 1;
- private static final int MEMORY_PRESSURE_LOW = 2;
- private static final int MEMORY_PRESSURE_MEDIUM = 3;
- private static final int MEMORY_PRESSURE_HIGH = 4;
-
- private static final MemoryMonitor sInstance = new MemoryMonitor();
-
- static MemoryMonitor getInstance() {
- return sInstance;
- }
-
- private Context mAppContext;
- private final PressureDecrementer mPressureDecrementer;
- private int mMemoryPressure; // Synchronized access only.
- private volatile boolean mStoragePressure; // Accessed via UI thread intent, background runnables.
- private boolean mInited;
-
- private MemoryMonitor() {
- mPressureDecrementer = new PressureDecrementer();
- mMemoryPressure = MEMORY_PRESSURE_NONE;
- }
-
- public void init(final Context context) {
- if (mInited) {
- return;
- }
-
- mAppContext = context.getApplicationContext();
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
- filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
- filter.addAction(ACTION_MEMORY_DUMP);
- filter.addAction(ACTION_FORCE_PRESSURE);
- mAppContext.registerReceiver(this, filter);
- mInited = true;
- }
-
- public void onLowMemory() {
- Log.d(LOGTAG, "onLowMemory() notification received");
- if (increaseMemoryPressure(MEMORY_PRESSURE_HIGH)) {
- // We need to wait on Gecko here, because if we haven't reduced
- // memory usage enough when we return from this, Android will kill us.
- if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
- GeckoThread.waitOnGecko();
- }
- }
- }
-
- public void onTrimMemory(int level) {
- Log.d(LOGTAG, "onTrimMemory() notification received with level " + level);
- if (level == ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
- // We seem to get this just by entering the task switcher or hitting the home button.
- // Seems bogus, because we are the foreground app, or at least not at the end of the LRU list.
- // Just ignore it, and if there is a real memory pressure event (CRITICAL, MODERATE, etc),
- // we'll respond appropriately.
- return;
- }
-
- switch (level) {
- case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
- case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
- // TRIM_MEMORY_MODERATE is the highest level we'll respond to while backgrounded
- increaseMemoryPressure(MEMORY_PRESSURE_HIGH);
- break;
- case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
- increaseMemoryPressure(MEMORY_PRESSURE_MEDIUM);
- break;
- case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
- increaseMemoryPressure(MEMORY_PRESSURE_LOW);
- break;
- case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
- case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
- increaseMemoryPressure(MEMORY_PRESSURE_CLEANUP);
- break;
- default:
- Log.d(LOGTAG, "Unhandled onTrimMemory() level " + level);
- break;
- }
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(intent.getAction())) {
- Log.d(LOGTAG, "Device storage is low");
- mStoragePressure = true;
- ThreadUtils.postToBackgroundThread(new StorageReducer(context));
- } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(intent.getAction())) {
- Log.d(LOGTAG, "Device storage is ok");
- mStoragePressure = false;
- } else if (ACTION_MEMORY_DUMP.equals(intent.getAction())) {
- String label = intent.getStringExtra("label");
- if (label == null) {
- label = "default";
- }
- GeckoAppShell.notifyObservers("Memory:Dump", label);
- } else if (ACTION_FORCE_PRESSURE.equals(intent.getAction())) {
- increaseMemoryPressure(MEMORY_PRESSURE_HIGH);
- }
- }
-
- @WrapForJNI(calledFrom = "ui")
- private static native void dispatchMemoryPressure();
-
- private boolean increaseMemoryPressure(int level) {
- int oldLevel;
- synchronized (this) {
- // bump up our level if we're not already higher
- if (mMemoryPressure > level) {
- return false;
- }
- oldLevel = mMemoryPressure;
- mMemoryPressure = level;
- }
-
- Log.d(LOGTAG, "increasing memory pressure to " + level);
-
- // since we don't get notifications for when memory pressure is off,
- // we schedule our own timer to slowly back off the memory pressure level.
- // note that this will reset the time to next decrement if the decrementer
- // is already running, which is the desired behaviour because we just got
- // a new low-mem notification.
- mPressureDecrementer.start();
-
- if (oldLevel == level) {
- // if we're not going to a higher level we probably don't
- // need to run another round of the same memory reductions
- // we did on the last memory pressure increase.
- return false;
- }
-
- // TODO hook in memory-reduction stuff for different levels here
- if (level >= MEMORY_PRESSURE_MEDIUM) {
- //Only send medium or higher events because that's all that is used right now
- if (GeckoThread.isRunning()) {
- dispatchMemoryPressure();
- }
-
- MemoryStorage.get().evictAll();
- ImageLoader.clearLruCache();
- LocalBroadcastManager.getInstance(mAppContext)
- .sendBroadcast(new Intent(BrowserProvider.ACTION_SHRINK_MEMORY));
- }
- return true;
- }
-
- /**
- * Thread-safe due to mStoragePressure's volatility.
- */
- boolean isUnderStoragePressure() {
- return mStoragePressure;
- }
-
- private boolean decreaseMemoryPressure() {
- int newLevel;
- synchronized (this) {
- if (mMemoryPressure <= 0) {
- return false;
- }
-
- newLevel = --mMemoryPressure;
- }
- Log.d(LOGTAG, "Decreased memory pressure to " + newLevel);
-
- return true;
- }
-
- class PressureDecrementer implements Runnable {
- private static final int DECREMENT_DELAY = 5 * 60 * 1000; // 5 minutes
-
- private boolean mPosted;
-
- synchronized void start() {
- if (mPosted) {
- // cancel the old one before scheduling a new one
- ThreadUtils.getBackgroundHandler().removeCallbacks(this);
- }
- ThreadUtils.getBackgroundHandler().postDelayed(this, DECREMENT_DELAY);
- mPosted = true;
- }
-
- @Override
- public synchronized void run() {
- if (!decreaseMemoryPressure()) {
- // done decrementing, bail out
- mPosted = false;
- return;
- }
-
- // need to keep decrementing
- ThreadUtils.getBackgroundHandler().postDelayed(this, DECREMENT_DELAY);
- }
- }
-
- private static class StorageReducer implements Runnable {
- private final Context mContext;
- private final BrowserDB mDB;
-
- public StorageReducer(final Context context) {
- this.mContext = context;
- // Since this may be called while Fennec is in the background, we don't want to risk accidentally
- // using the wrong context. If the profile we get is a guest profile, use the default profile instead.
- GeckoProfile profile = GeckoProfile.get(mContext);
- if (profile.inGuestMode()) {
- // If it was the guest profile, switch to the default one.
- profile = GeckoProfile.get(mContext, GeckoProfile.DEFAULT_PROFILE);
- }
-
- mDB = BrowserDB.from(profile);
- }
-
- @Override
- public void run() {
- // this might get run right on startup, if so wait 10 seconds and try again
- if (!GeckoThread.isRunning()) {
- ThreadUtils.getBackgroundHandler().postDelayed(this, 10000);
- return;
- }
-
- if (!MemoryMonitor.getInstance().isUnderStoragePressure()) {
- // Pressure is off, so we can abort.
- return;
- }
-
- final ContentResolver cr = mContext.getContentResolver();
- mDB.expireHistory(cr, BrowserContract.ExpirePriority.AGGRESSIVE);
- mDB.removeThumbnails(cr);
-
- // TODO: drop or shrink disk caches
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/MotionEventInterceptor.java b/mobile/android/base/java/org/mozilla/gecko/MotionEventInterceptor.java
deleted file mode 100644
index 814c09995..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/MotionEventInterceptor.java
+++ /dev/null
@@ -1,13 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.view.MotionEvent;
-import android.view.View;
-
-public interface MotionEventInterceptor {
- public boolean onInterceptMotionEvent(View view, MotionEvent event);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/PackageReplacedReceiver.java b/mobile/android/base/java/org/mozilla/gecko/PackageReplacedReceiver.java
deleted file mode 100644
index 37dd8c304..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/PackageReplacedReceiver.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-import org.mozilla.gecko.mozglue.GeckoLoader;
-
-/**
- * This broadcast receiver receives ACTION_MY_PACKAGE_REPLACED broadcasts and
- * starts procedures that should run after the APK has been updated.
- */
-public class PackageReplacedReceiver extends BroadcastReceiver {
- public static final String ACTION_MY_PACKAGE_REPLACED = "android.intent.action.MY_PACKAGE_REPLACED";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent == null || !ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())) {
- // This is not the broadcast we are looking for.
- return;
- }
-
- // Extract Gecko libs to allow them to be loaded from cache on startup.
- extractGeckoLibs(context);
- }
-
- private static void extractGeckoLibs(final Context context) {
- final String resourcePath = context.getPackageResourcePath();
- GeckoLoader.loadMozGlue(context);
- GeckoLoader.extractGeckoLibs(context, resourcePath);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/PresentationMediaPlayerManager.java b/mobile/android/base/java/org/mozilla/gecko/PresentationMediaPlayerManager.java
deleted file mode 100644
index e44096489..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/PresentationMediaPlayerManager.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.annotation.TargetApi;
-import android.app.Presentation;
-import android.content.Context;
-import android.os.Bundle;
-import android.support.v7.media.MediaRouter;
-import android.util.Log;
-import android.view.Display;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-
-import org.mozilla.gecko.AppConstants.Versions;
-
-import org.mozilla.gecko.annotation.WrapForJNI;
-
-/**
- * A MediaPlayerManager with API 17+ Presentation support.
- */
-@TargetApi(17)
-public class PresentationMediaPlayerManager extends MediaPlayerManager {
-
- private static final String LOGTAG = "Gecko" + PresentationMediaPlayerManager.class.getSimpleName();
-
- private GeckoPresentation presentation;
-
- public PresentationMediaPlayerManager() {
- if (!Versions.feature17Plus) {
- throw new IllegalStateException(PresentationMediaPlayerManager.class.getSimpleName() +
- " does not support < API 17");
- }
- }
-
- @Override
- public void onStop() {
- super.onStop();
- if (presentation != null) {
- presentation.dismiss();
- presentation = null;
- }
- }
-
- @Override
- protected void updatePresentation() {
- if (mediaRouter == null) {
- return;
- }
-
- if (isPresentationMode) {
- return;
- }
-
- MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
- Display display = route != null ? route.getPresentationDisplay() : null;
-
- if (display != null) {
- if ((presentation != null) && (presentation.getDisplay() != display)) {
- presentation.dismiss();
- presentation = null;
- }
-
- if (presentation == null) {
- final GeckoView geckoView = (GeckoView) getActivity().findViewById(R.id.layer_view);
- presentation = new GeckoPresentation(getActivity(), display, geckoView);
-
- try {
- presentation.show();
- } catch (WindowManager.InvalidDisplayException ex) {
- Log.w(LOGTAG, "Couldn't show presentation! Display was removed in "
- + "the meantime.", ex);
- presentation = null;
- }
- }
- } else if (presentation != null) {
- presentation.dismiss();
- presentation = null;
- }
- }
-
- @WrapForJNI(calledFrom = "ui")
- /* protected */ static native void invalidateAndScheduleComposite(GeckoView geckoView);
-
- @WrapForJNI(calledFrom = "ui")
- /* protected */ static native void addPresentationSurface(GeckoView geckoView, Surface surface);
-
- @WrapForJNI(calledFrom = "ui")
- /* protected */ static native void removePresentationSurface();
-
- private static final class GeckoPresentation extends Presentation {
- private SurfaceView mView;
- private GeckoView mGeckoView;
-
- public GeckoPresentation(Context context, Display display, GeckoView geckoView) {
- super(context, display);
-
- mGeckoView = geckoView;
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mView = new SurfaceView(getContext());
- setContentView(mView, new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT));
- mView.getHolder().addCallback(new SurfaceListener(mGeckoView));
- }
- }
-
- private static final class SurfaceListener implements SurfaceHolder.Callback {
- private GeckoView mGeckoView;
-
- public SurfaceListener(GeckoView geckoView) {
- mGeckoView = geckoView;
- }
-
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width,
- int height) {
- // Surface changed so force a composite
- if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
- invalidateAndScheduleComposite(mGeckoView);
- }
- }
-
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
- addPresentationSurface(mGeckoView, holder.getSurface());
- }
- }
-
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
- removePresentationSurface();
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/PresentationView.java b/mobile/android/base/java/org/mozilla/gecko/PresentationView.java
deleted file mode 100644
index 3e5b5ffb3..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/PresentationView.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * vim: ts=4 sw=4 expandtab:
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.annotation.WrapForJNI;
-import org.mozilla.gecko.GeckoThread;
-import org.mozilla.gecko.GeckoView;
-import org.mozilla.gecko.ScreenManagerHelper;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-
-public class PresentationView extends GeckoView {
- private static final String LOGTAG = "PresentationView";
- private static final String presentationViewURI = "chrome://browser/content/PresentationView.xul";
-
- public PresentationView(Context context, String deviceId, int screenId) {
- super(context);
- this.chromeURI = presentationViewURI + "#" + deviceId;
- this.screenId = screenId;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/PrintHelper.java b/mobile/android/base/java/org/mozilla/gecko/PrintHelper.java
deleted file mode 100644
index 077b2d29b..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/PrintHelper.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.util.GeckoRequest;
-import org.mozilla.gecko.util.IOUtils;
-import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.os.CancellationSignal;
-import android.os.ParcelFileDescriptor;
-import android.print.PrintAttributes;
-import android.print.PrintDocumentAdapter;
-import android.print.PrintDocumentAdapter.LayoutResultCallback;
-import android.print.PrintDocumentAdapter.WriteResultCallback;
-import android.print.PrintDocumentInfo;
-import android.print.PrintManager;
-import android.print.PageRange;
-import android.util.Log;
-
-public class PrintHelper {
- private static final String LOGTAG = "GeckoPrintUtils";
-
- public static void printPDF(final Context context) {
- GeckoAppShell.sendRequestToGecko(new GeckoRequest("Print:PDF", new JSONObject()) {
- @Override
- public void onResponse(NativeJSObject nativeJSObject) {
- final String filePath = nativeJSObject.getString("file");
- final String title = nativeJSObject.getString("title");
- finish(context, filePath, title);
- }
-
- @Override
- public void onError(NativeJSObject error) {
- // Gecko didn't respond due to state change, javascript error, etc.
- Log.d(LOGTAG, "No response from Gecko on request to generate a PDF");
- }
-
- private void finish(final Context context, final String filePath, final String title) {
- PrintManager printManager = (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
- String jobName = title;
-
- // The adapter methods are all called on the UI thread by the PrintManager. Put the heavyweight code
- // in onWrite on the background thread.
- PrintDocumentAdapter pda = new PrintDocumentAdapter() {
- @Override
- public void onWrite(final PageRange[] pages, final ParcelFileDescriptor destination, final CancellationSignal cancellationSignal, final WriteResultCallback callback) {
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- InputStream input = null;
- OutputStream output = null;
-
- try {
- File pdfFile = new File(filePath);
- input = new FileInputStream(pdfFile);
- output = new FileOutputStream(destination.getFileDescriptor());
-
- byte[] buf = new byte[8192];
- int bytesRead;
- while ((bytesRead = input.read(buf)) > 0) {
- output.write(buf, 0, bytesRead);
- }
-
- callback.onWriteFinished(new PageRange[] { PageRange.ALL_PAGES });
- } catch (FileNotFoundException ee) {
- Log.d(LOGTAG, "Unable to find the temporary PDF file.");
- } catch (IOException ioe) {
- Log.e(LOGTAG, "IOException while transferring temporary PDF file: ", ioe);
- } finally {
- IOUtils.safeStreamClose(input);
- IOUtils.safeStreamClose(output);
- }
- }
- });
- }
-
- @Override
- public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal, LayoutResultCallback callback, Bundle extras) {
- if (cancellationSignal.isCanceled()) {
- callback.onLayoutCancelled();
- return;
- }
-
- PrintDocumentInfo pdi = new PrintDocumentInfo.Builder(filePath).setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT).build();
- callback.onLayoutFinished(pdi, true);
- }
-
- @Override
- public void onFinish() {
- // Remove the temporary file when the printing system is finished.
- try {
- File pdfFile = new File(filePath);
- pdfFile.delete();
- } catch (NullPointerException npe) {
- // Silence the exception. We only want to delete a real file. We don't
- // care if the file doesn't exist.
- }
- }
- };
-
- printManager.print(jobName, pda, null);
- }
- });
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/PrivateTab.java b/mobile/android/base/java/org/mozilla/gecko/PrivateTab.java
deleted file mode 100644
index 39b6899d3..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/PrivateTab.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.content.Context;
-
-import org.json.JSONObject;
-import org.mozilla.gecko.db.BrowserDB;
-
-public class PrivateTab extends Tab {
- public PrivateTab(Context context, int id, String url, boolean external, int parentId, String title) {
- super(context, id, url, external, parentId, title);
- }
-
- @Override
- protected void saveThumbnailToDB(final BrowserDB db) {}
-
- @Override
- public void setMetadata(JSONObject metadata) {}
-
- @Override
- public boolean isPrivate() {
- return true;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/RemoteClientsDialogFragment.java b/mobile/android/base/java/org/mozilla/gecko/RemoteClientsDialogFragment.java
deleted file mode 100644
index b4aee9370..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/RemoteClientsDialogFragment.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.mozilla.gecko.db.RemoteClient;
-
-import android.app.AlertDialog;
-import android.app.AlertDialog.Builder;
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.support.v4.app.DialogFragment;
-import android.support.v4.app.Fragment;
-import android.util.SparseBooleanArray;
-
-/**
- * A dialog fragment that displays a list of remote clients.
- * <p>
- * The dialog allows both single (one tap) and multiple (checkbox) selection.
- * The dialog's results are communicated via the {@link RemoteClientsListener}
- * interface. Either the dialog fragment's <i>target fragment</i> (see
- * {@link Fragment#setTargetFragment(Fragment, int)}), or the containing
- * <i>activity</i>, must implement that interface. See
- * {@link #notifyListener(List)} for details.
- */
-public class RemoteClientsDialogFragment extends DialogFragment {
- private static final String KEY_TITLE = "title";
- private static final String KEY_CHOICE_MODE = "choice_mode";
- private static final String KEY_POSITIVE_BUTTON_TEXT = "positive_button_text";
- private static final String KEY_CLIENTS = "clients";
-
- public interface RemoteClientsListener {
- // Always called on the main UI thread.
- public void onClients(List<RemoteClient> clients);
- }
-
- public enum ChoiceMode {
- SINGLE,
- MULTIPLE,
- }
-
- public static RemoteClientsDialogFragment newInstance(String title, String positiveButtonText, ChoiceMode choiceMode, ArrayList<RemoteClient> clients) {
- final RemoteClientsDialogFragment dialog = new RemoteClientsDialogFragment();
- final Bundle args = new Bundle();
- args.putString(KEY_TITLE, title);
- args.putString(KEY_POSITIVE_BUTTON_TEXT, positiveButtonText);
- args.putInt(KEY_CHOICE_MODE, choiceMode.ordinal());
- args.putParcelableArrayList(KEY_CLIENTS, clients);
- dialog.setArguments(args);
- return dialog;
- }
-
- public RemoteClientsDialogFragment() {
- // Empty constructor is required for DialogFragment.
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
-
- GeckoApplication.watchReference(getActivity(), this);
- }
-
- protected void notifyListener(List<RemoteClient> clients) {
- RemoteClientsListener listener;
- try {
- listener = (RemoteClientsListener) getTargetFragment();
- } catch (ClassCastException e) {
- try {
- listener = (RemoteClientsListener) getActivity();
- } catch (ClassCastException f) {
- throw new ClassCastException(getTargetFragment() + " or " + getActivity()
- + " must implement RemoteClientsListener");
- }
- }
- listener.onClients(clients);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- final String title = getArguments().getString(KEY_TITLE);
- final String positiveButtonText = getArguments().getString(KEY_POSITIVE_BUTTON_TEXT);
- final ChoiceMode choiceMode = ChoiceMode.values()[getArguments().getInt(KEY_CHOICE_MODE)];
- final ArrayList<RemoteClient> clients = getArguments().getParcelableArrayList(KEY_CLIENTS);
-
- final Builder builder = new AlertDialog.Builder(getActivity());
- builder.setTitle(title);
-
- final String[] clientNames = new String[clients.size()];
- for (int i = 0; i < clients.size(); i++) {
- clientNames[i] = clients.get(i).name;
- }
-
- if (choiceMode == ChoiceMode.MULTIPLE) {
- builder.setMultiChoiceItems(clientNames, null, null);
- builder.setPositiveButton(positiveButtonText, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialogInterface, int which) {
- if (which != Dialog.BUTTON_POSITIVE) {
- return;
- }
-
- final AlertDialog dialog = (AlertDialog) dialogInterface;
- final SparseBooleanArray checkedItemPositions = dialog.getListView().getCheckedItemPositions();
- final ArrayList<RemoteClient> checked = new ArrayList<RemoteClient>();
- for (int i = 0; i < clients.size(); i++) {
- if (checkedItemPositions.get(i)) {
- checked.add(clients.get(i));
- }
- }
- notifyListener(checked);
- }
- });
- } else {
- builder.setItems(clientNames, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int index) {
- final ArrayList<RemoteClient> checked = new ArrayList<RemoteClient>();
- checked.add(clients.get(index));
- notifyListener(checked);
- }
- });
- }
-
- return builder.create();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/RemotePresentationService.java b/mobile/android/base/java/org/mozilla/gecko/RemotePresentationService.java
deleted file mode 100644
index b5a5527c9..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/RemotePresentationService.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * vim: ts=4 sw=4 expandtab:
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.json.JSONObject;
-import org.json.JSONException;
-
-import org.json.JSONObject;
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.PresentationView;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.ScreenManagerHelper;
-import org.mozilla.gecko.annotation.JNITarget;
-import org.mozilla.gecko.annotation.ReflectionTarget;
-import org.mozilla.gecko.annotation.WrapForJNI;
-import org.mozilla.gecko.gfx.LayerView;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.NativeEventListener;
-import org.mozilla.gecko.util.NativeJSObject;
-
-import com.google.android.gms.cast.CastMediaControlIntent;
-import com.google.android.gms.cast.CastPresentation;
-import com.google.android.gms.cast.CastRemoteDisplayLocalService;
-import com.google.android.gms.common.ConnectionResult;
-import com.google.android.gms.common.GooglePlayServicesUtil;
-
-import android.app.Activity;
-import android.content.Context;
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v7.media.MediaControlIntent;
-import android.support.v7.media.MediaRouteSelector;
-import android.support.v7.media.MediaRouter.RouteInfo;
-import android.support.v7.media.MediaRouter;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Display;
-import android.view.ViewGroup.LayoutParams;
-import android.view.WindowManager;
-import android.widget.RelativeLayout;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/*
- * Service to keep the remote display running even when the app goes into the background
- */
-public class RemotePresentationService extends CastRemoteDisplayLocalService {
-
- private static final String LOGTAG = "RemotePresentationService";
- private CastPresentation presentation;
- private String deviceId;
- private int screenId;
-
- public void setDeviceId(String deviceId) {
- this.deviceId = deviceId;
- }
-
- public String getDeviceId() {
- return deviceId;
- }
-
- @Override
- public void onCreatePresentation(Display display) {
- createPresentation();
- }
-
- @Override
- public void onDismissPresentation() {
- dismissPresentation();
- }
-
- private void dismissPresentation() {
- if (presentation != null) {
- presentation.dismiss();
- presentation = null;
- ScreenManagerHelper.removeDisplay(screenId);
- MediaPlayerManager.getInstance().setPresentationMode(false);
- }
- }
-
- private void createPresentation() {
- dismissPresentation();
-
- MediaPlayerManager.getInstance().setPresentationMode(true);
-
- DisplayMetrics metrics = new DisplayMetrics();
- getDisplay().getMetrics(metrics);
- screenId = ScreenManagerHelper.addDisplay(ScreenManagerHelper.DISPLAY_VIRTUAL,
- metrics.widthPixels,
- metrics.heightPixels,
- metrics.density);
-
- VirtualPresentation virtualPresentation = new VirtualPresentation(this, getDisplay());
- virtualPresentation.setDeviceId(deviceId);
- virtualPresentation.setScreenId(screenId);
- presentation = (CastPresentation) virtualPresentation;
-
- try {
- presentation.show();
- } catch (WindowManager.InvalidDisplayException ex) {
- Log.e(LOGTAG, "Unable to show presentation, display was removed.", ex);
- dismissPresentation();
- }
- }
-}
-
-class VirtualPresentation extends CastPresentation {
- private final String LOGTAG = "VirtualPresentation";
- private RelativeLayout layout;
- private PresentationView view;
- private String deviceId;
- private int screenId;
-
- public VirtualPresentation(Context context, Display display) {
- super(context, display);
- }
-
- public void setDeviceId(String deviceId) { this.deviceId = deviceId; }
- public void setScreenId(int screenId) { this.screenId = screenId; }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- /*
- * NOTICE: The context get from getContext() is different to the context
- * of the application. Presentaion has its own context to get correct
- * resources.
- */
-
- // Create new PresentationView
- view = new PresentationView(getContext(), deviceId, screenId);
- view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT));
-
- // Create new layout to put the GeckoView
- layout = new RelativeLayout(getContext());
- layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT));
- layout.addView(view);
-
- setContentView(layout);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/Restarter.java b/mobile/android/base/java/org/mozilla/gecko/Restarter.java
deleted file mode 100644
index b049f7627..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/Restarter.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-import android.os.Process;
-import android.util.Log;
-
-public class Restarter extends Service {
- private static final String LOGTAG = "GeckoRestarter";
-
- private void doRestart(Intent intent) {
- final int oldProc = intent.getIntExtra("pid", -1);
- if (oldProc < 0) {
- return;
- }
-
- Process.killProcess(oldProc);
- Log.d(LOGTAG, "Killed " + oldProc);
- try {
- Thread.sleep(100);
- } catch (final InterruptedException e) {
- }
-
- final Intent restartIntent = (Intent)intent.getParcelableExtra(Intent.EXTRA_INTENT);
- restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .putExtra("didRestart", true)
- .setClassName(getApplicationContext(),
- AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
- startActivity(restartIntent);
- Log.d(LOGTAG, "Launched " + restartIntent);
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- doRestart(intent);
- stopSelf(startId);
- return Service.START_NOT_STICKY;
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/ScreenManagerHelper.java b/mobile/android/base/java/org/mozilla/gecko/ScreenManagerHelper.java
deleted file mode 100644
index 5cb404ce8..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/ScreenManagerHelper.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * vim: ts=4 sw=4 expandtab:
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.annotation.WrapForJNI;
-
-class ScreenManagerHelper {
-
- /**
- * The following display types use the same definition in nsIScreen.idl
- */
- final static int DISPLAY_PRIMARY = 0; // primary screen
- final static int DISPLAY_EXTERNAL = 1; // wired displays, such as HDMI, DisplayPort, etc.
- final static int DISPLAY_VIRTUAL = 2; // wireless displays, such as Chromecast, WiFi-Display, etc.
-
- /**
- * Add a new nsScreen when a new display in Android is available.
- *
- * @param displayType the display type of the nsScreen would be added
- * @param width the width of the new nsScreen
- * @param height the height of the new nsScreen
- * @param density the density of the new nsScreen
- *
- * @return return the ID of the added nsScreen
- */
- @WrapForJNI
- public native static int addDisplay(int displayType,
- int width,
- int height,
- float density);
-
- /**
- * Remove the nsScreen by the specific screen ID.
- *
- * @param screenId the ID of the screen would be removed.
- */
- @WrapForJNI
- public native static void removeDisplay(int screenId);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/ScreenshotObserver.java b/mobile/android/base/java/org/mozilla/gecko/ScreenshotObserver.java
deleted file mode 100644
index 64f101e51..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/ScreenshotObserver.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.permissions.Permissions;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.Manifest;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.MediaStore;
-import android.util.Log;
-
-public class ScreenshotObserver {
- private static final String LOGTAG = "GeckoScreenshotObserver";
- public Context context;
-
- /**
- * Listener for screenshot changes.
- */
- public interface OnScreenshotListener {
- /**
- * This callback is executed on the UI thread.
- */
- public void onScreenshotTaken(String data, String title);
- }
-
- private OnScreenshotListener listener;
-
- public ScreenshotObserver() {
- }
-
- public void setListener(Context context, OnScreenshotListener listener) {
- this.context = context;
- this.listener = listener;
- }
-
- private MediaObserver mediaObserver;
- private String[] mediaProjections = new String[] {
- MediaStore.Images.ImageColumns.DATA,
- MediaStore.Images.ImageColumns.DISPLAY_NAME,
- MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
- MediaStore.Images.ImageColumns.DATE_TAKEN,
- MediaStore.Images.ImageColumns.TITLE
- };
-
- /**
- * Start ScreenshotObserver if this device is supported and all required runtime permissions
- * have been granted by the user. Calling this method will not prompt for permissions.
- */
- public void start() {
- Permissions.from(context)
- .withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
- .doNotPrompt()
- .run(startObserverRunnable());
- }
-
- private Runnable startObserverRunnable() {
- return new Runnable() {
- @Override
- public void run() {
- try {
- if (mediaObserver == null) {
- mediaObserver = new MediaObserver();
- context.getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, false, mediaObserver);
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Failure to start watching media: ", e);
- }
- }
- };
- }
-
- public void stop() {
- if (mediaObserver == null) {
- return;
- }
-
- try {
- context.getContentResolver().unregisterContentObserver(mediaObserver);
- mediaObserver = null;
- } catch (Exception e) {
- Log.e(LOGTAG, "Failure to stop watching media: ", e);
- }
- }
-
- public void onMediaChange(final Uri uri) {
- // Make sure we are on not on the main thread.
- final ContentResolver cr = context.getContentResolver();
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- // Find the most recent image added to the MediaStore and see if it's a screenshot.
- final Cursor cursor = cr.query(uri, mediaProjections, null, null, MediaStore.Images.ImageColumns.DATE_ADDED + " DESC LIMIT 1");
- try {
- if (cursor == null) {
- return;
- }
-
- while (cursor.moveToNext()) {
- String data = cursor.getString(0);
- Log.i(LOGTAG, "data: " + data);
- String display = cursor.getString(1);
- Log.i(LOGTAG, "display: " + display);
- String album = cursor.getString(2);
- Log.i(LOGTAG, "album: " + album);
- long date = cursor.getLong(3);
- String title = cursor.getString(4);
- Log.i(LOGTAG, "title: " + title);
- if (album != null && album.toLowerCase().contains("screenshot")) {
- if (listener != null) {
- listener.onScreenshotTaken(data, title);
- break;
- }
- }
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Failure to process media change: ", e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
- });
- }
-
- private class MediaObserver extends ContentObserver {
- public MediaObserver() {
- super(null);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
- onMediaChange(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/SessionParser.java b/mobile/android/base/java/org/mozilla/gecko/SessionParser.java
deleted file mode 100644
index d29aaadc7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/SessionParser.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * ***** BEGIN LICENSE BLOCK *****
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/.
- *
- * ***** END LICENSE BLOCK ***** */
-
-package org.mozilla.gecko;
-
-import java.util.LinkedList;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.util.Log;
-
-public abstract class SessionParser {
- private static final String LOGTAG = "GeckoSessionParser";
-
- public class SessionTab {
- final private String mTitle;
- final private String mUrl;
- final private JSONObject mTabObject;
- private boolean mIsSelected;
-
- private SessionTab(String title, String url, boolean isSelected, JSONObject tabObject) {
- mTitle = title;
- mUrl = url;
- mIsSelected = isSelected;
- mTabObject = tabObject;
- }
-
- public String getTitle() {
- return mTitle;
- }
-
- public String getUrl() {
- return mUrl;
- }
-
- public boolean isSelected() {
- return mIsSelected;
- }
-
- public JSONObject getTabObject() {
- return mTabObject;
- }
-
- /**
- * Is this tab pointing to about:home and does not contain any other history?
- */
- public boolean isAboutHomeWithoutHistory() {
- JSONArray entries = mTabObject.optJSONArray("entries");
- return entries != null && entries.length() == 1 && AboutPages.isAboutHome(mUrl);
- }
- };
-
- abstract public void onTabRead(SessionTab tab);
-
- /**
- * Placeholder method that must be overloaded to handle closedTabs while parsing session data.
- *
- * @param closedTabs, JSONArray of recently closed tab entries.
- * @throws JSONException
- */
- public void onClosedTabsRead(final JSONArray closedTabs) throws JSONException {
- }
-
- /**
- * Parses the provided session store data and calls onTabRead for each tab that has been found.
- *
- * @param sessionStrings One or more strings containing session store data.
- * @return False if any of the session strings provided didn't contain valid session store data.
- */
- public boolean parse(String... sessionStrings) {
- final LinkedList<SessionTab> sessionTabs = new LinkedList<SessionTab>();
- int totalCount = 0;
- int selectedIndex = -1;
- try {
- for (String sessionString : sessionStrings) {
- final JSONArray windowsArray = new JSONObject(sessionString).getJSONArray("windows");
- if (windowsArray.length() == 0) {
- // Session json can be empty if the user has opted out of session restore.
- Log.d(LOGTAG, "Session restore file is empty, no session entries found.");
- continue;
- }
-
- final JSONObject window = windowsArray.getJSONObject(0);
- final JSONArray tabs = window.getJSONArray("tabs");
- final int optSelected = window.optInt("selected", -1);
- final JSONArray closedTabs = window.optJSONArray("closedTabs");
- if (closedTabs != null) {
- onClosedTabsRead(closedTabs);
- }
-
- for (int i = 0; i < tabs.length(); i++) {
- final JSONObject tab = tabs.getJSONObject(i);
- final int index = tab.getInt("index");
- final JSONArray entries = tab.getJSONArray("entries");
- if (index < 1 || entries.length() < index) {
- Log.w(LOGTAG, "Session entries and index don't agree.");
- continue;
- }
- final JSONObject entry = entries.getJSONObject(index - 1);
- final String url = entry.getString("url");
-
- String title = entry.optString("title");
- if (title.length() == 0) {
- title = url;
- }
-
- totalCount++;
- boolean selected = false;
- if (optSelected == i + 1) {
- selected = true;
- selectedIndex = totalCount;
- }
- sessionTabs.add(new SessionTab(title, url, selected, tab));
- }
- }
- } catch (JSONException e) {
- Log.e(LOGTAG, "JSON error", e);
- return false;
- }
-
- // If no selected index was found, select the first tab.
- if (selectedIndex == -1 && sessionTabs.size() > 0) {
- sessionTabs.getFirst().mIsSelected = true;
- }
-
- for (SessionTab tab : sessionTabs) {
- onTabRead(tab);
- }
-
- return true;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/SharedPreferencesHelper.java b/mobile/android/base/java/org/mozilla/gecko/SharedPreferencesHelper.java
deleted file mode 100644
index 1066da079..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/SharedPreferencesHelper.java
+++ /dev/null
@@ -1,311 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
-import android.util.Log;
-
-import java.util.Map;
-import java.util.HashMap;
-
-/**
- * Helper class to get, set, and observe Android Shared Preferences.
- */
-public final class SharedPreferencesHelper
- implements GeckoEventListener
-{
- public static final String LOGTAG = "GeckoAndSharedPrefs";
-
- // Calculate this once, at initialization. isLoggable is too expensive to
- // have in-line in each log call.
- private static final boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
-
- private enum Scope {
- APP("app"),
- PROFILE("profile"),
- GLOBAL("global");
-
- public final String key;
-
- private Scope(String key) {
- this.key = key;
- }
-
- public static Scope forKey(String key) {
- for (Scope scope : values()) {
- if (scope.key.equals(key)) {
- return scope;
- }
- }
-
- throw new IllegalStateException("SharedPreferences scope must be valid.");
- }
- }
-
- protected final Context mContext;
-
- // mListeners is not synchronized because it is only updated in
- // handleObserve, which is called from Gecko serially.
- protected final Map<String, SharedPreferences.OnSharedPreferenceChangeListener> mListeners;
-
- public SharedPreferencesHelper(Context context) {
- mContext = context;
-
- mListeners = new HashMap<String, SharedPreferences.OnSharedPreferenceChangeListener>();
-
- EventDispatcher dispatcher = GeckoApp.getEventDispatcher();
- if (dispatcher == null) {
- Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
- return;
- }
- dispatcher.registerGeckoThreadListener(this,
- "SharedPreferences:Set",
- "SharedPreferences:Get",
- "SharedPreferences:Observe");
- }
-
- public synchronized void uninit() {
- EventDispatcher dispatcher = GeckoApp.getEventDispatcher();
- if (dispatcher == null) {
- Log.e(LOGTAG, "Gecko event dispatcher must not be null", new RuntimeException());
- return;
- }
- dispatcher.unregisterGeckoThreadListener(this,
- "SharedPreferences:Set",
- "SharedPreferences:Get",
- "SharedPreferences:Observe");
- }
-
- private SharedPreferences getSharedPreferences(JSONObject message) throws JSONException {
- final Scope scope = Scope.forKey(message.getString("scope"));
- switch (scope) {
- case APP:
- return GeckoSharedPrefs.forApp(mContext);
- case PROFILE:
- final String profileName = message.optString("profileName", null);
- if (profileName == null) {
- return GeckoSharedPrefs.forProfile(mContext);
- } else {
- return GeckoSharedPrefs.forProfileName(mContext, profileName);
- }
- case GLOBAL:
- final String branch = message.optString("branch", null);
- if (branch == null) {
- return PreferenceManager.getDefaultSharedPreferences(mContext);
- } else {
- return mContext.getSharedPreferences(branch, Context.MODE_PRIVATE);
- }
- }
-
- return null;
- }
-
- private String getBranch(Scope scope, String profileName, String branch) {
- switch (scope) {
- case APP:
- return GeckoSharedPrefs.APP_PREFS_NAME;
- case PROFILE:
- if (profileName == null) {
- profileName = GeckoProfile.get(mContext).getName();
- }
-
- return GeckoSharedPrefs.PROFILE_PREFS_NAME_PREFIX + profileName;
- case GLOBAL:
- return branch;
- }
-
- return null;
- }
-
- /**
- * Set many SharedPreferences in Android.
- *
- * message.branch must exist, and should be a String SharedPreferences
- * branch name, or null for the default branch.
- * message.preferences should be an array of preferences. Each preference
- * must include a String name, a String type in ["bool", "int", "string"],
- * and an Object value.
- */
- private void handleSet(JSONObject message) throws JSONException {
- SharedPreferences.Editor editor = getSharedPreferences(message).edit();
-
- JSONArray jsonPrefs = message.getJSONArray("preferences");
-
- for (int i = 0; i < jsonPrefs.length(); i++) {
- JSONObject pref = jsonPrefs.getJSONObject(i);
- String name = pref.getString("name");
- String type = pref.getString("type");
- if ("bool".equals(type)) {
- editor.putBoolean(name, pref.getBoolean("value"));
- } else if ("int".equals(type)) {
- editor.putInt(name, pref.getInt("value"));
- } else if ("string".equals(type)) {
- editor.putString(name, pref.getString("value"));
- } else {
- Log.w(LOGTAG, "Unknown pref value type [" + type + "] for pref [" + name + "]");
- }
- editor.apply();
- }
- }
-
- /**
- * Get many SharedPreferences from Android.
- *
- * message.branch must exist, and should be a String SharedPreferences
- * branch name, or null for the default branch.
- * message.preferences should be an array of preferences. Each preference
- * must include a String name, and a String type in ["bool", "int",
- * "string"].
- */
- private JSONArray handleGet(JSONObject message) throws JSONException {
- SharedPreferences prefs = getSharedPreferences(message);
- JSONArray jsonPrefs = message.getJSONArray("preferences");
- JSONArray jsonValues = new JSONArray();
-
- for (int i = 0; i < jsonPrefs.length(); i++) {
- JSONObject pref = jsonPrefs.getJSONObject(i);
- String name = pref.getString("name");
- String type = pref.getString("type");
- JSONObject jsonValue = new JSONObject();
- jsonValue.put("name", name);
- jsonValue.put("type", type);
- try {
- if ("bool".equals(type)) {
- boolean value = prefs.getBoolean(name, false);
- jsonValue.put("value", value);
- } else if ("int".equals(type)) {
- int value = prefs.getInt(name, 0);
- jsonValue.put("value", value);
- } else if ("string".equals(type)) {
- String value = prefs.getString(name, "");
- jsonValue.put("value", value);
- } else {
- Log.w(LOGTAG, "Unknown pref value type [" + type + "] for pref [" + name + "]");
- }
- } catch (ClassCastException e) {
- // Thrown if there is a preference with the given name that is
- // not the right type.
- Log.w(LOGTAG, "Wrong pref value type [" + type + "] for pref [" + name + "]");
- }
- jsonValues.put(jsonValue);
- }
-
- return jsonValues;
- }
-
- private static class ChangeListener
- implements SharedPreferences.OnSharedPreferenceChangeListener {
- public final Scope scope;
- public final String branch;
- public final String profileName;
-
- public ChangeListener(final Scope scope, final String branch, final String profileName) {
- this.scope = scope;
- this.branch = branch;
- this.profileName = profileName;
- }
-
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- if (logVerbose) {
- Log.v(LOGTAG, "Got onSharedPreferenceChanged");
- }
- try {
- final JSONObject msg = new JSONObject();
- msg.put("scope", this.scope.key);
- msg.put("branch", this.branch);
- msg.put("profileName", this.profileName);
- msg.put("key", key);
-
- // Truly, this is awful, but the API impedance is strong: there
- // is no way to get a single untyped value from a
- // SharedPreferences instance.
- msg.put("value", sharedPreferences.getAll().get(key));
-
- GeckoAppShell.notifyObservers("SharedPreferences:Changed", msg.toString());
- } catch (JSONException e) {
- Log.e(LOGTAG, "Got exception creating JSON object", e);
- return;
- }
- }
- }
-
- /**
- * Register or unregister a SharedPreferences.OnSharedPreferenceChangeListener.
- *
- * message.branch must exist, and should be a String SharedPreferences
- * branch name, or null for the default branch.
- * message.enable should be a boolean: true to enable listening, false to
- * disable listening.
- */
- private void handleObserve(JSONObject message) throws JSONException {
- final SharedPreferences prefs = getSharedPreferences(message);
- final boolean enable = message.getBoolean("enable");
-
- final Scope scope = Scope.forKey(message.getString("scope"));
- final String profileName = message.optString("profileName", null);
- final String branch = getBranch(scope, profileName, message.optString("branch", null));
-
- if (branch == null) {
- Log.e(LOGTAG, "No branch specified for SharedPreference:Observe; aborting.");
- return;
- }
-
- // mListeners is only modified in this one observer, which is called
- // from Gecko serially.
- if (enable && !this.mListeners.containsKey(branch)) {
- SharedPreferences.OnSharedPreferenceChangeListener listener
- = new ChangeListener(scope, branch, profileName);
- this.mListeners.put(branch, listener);
- prefs.registerOnSharedPreferenceChangeListener(listener);
- }
- if (!enable && this.mListeners.containsKey(branch)) {
- SharedPreferences.OnSharedPreferenceChangeListener listener
- = this.mListeners.remove(branch);
- prefs.unregisterOnSharedPreferenceChangeListener(listener);
- }
- }
-
- @Override
- public void handleMessage(String event, JSONObject message) {
- // Everything here is synchronous and serial, so we need not worry about
- // overwriting an in-progress response.
- try {
- if (event.equals("SharedPreferences:Set")) {
- if (logVerbose) {
- Log.v(LOGTAG, "Got SharedPreferences:Set message.");
- }
- handleSet(message);
- } else if (event.equals("SharedPreferences:Get")) {
- if (logVerbose) {
- Log.v(LOGTAG, "Got SharedPreferences:Get message.");
- }
- JSONObject obj = new JSONObject();
- obj.put("values", handleGet(message));
- EventDispatcher.sendResponse(message, obj);
- } else if (event.equals("SharedPreferences:Observe")) {
- if (logVerbose) {
- Log.v(LOGTAG, "Got SharedPreferences:Observe message.");
- }
- handleObserve(message);
- } else {
- Log.e(LOGTAG, "SharedPreferencesHelper got unexpected message " + event);
- return;
- }
- } catch (JSONException e) {
- Log.e(LOGTAG, "Got exception in handleMessage handling event " + event, e);
- return;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/SiteIdentity.java b/mobile/android/base/java/org/mozilla/gecko/SiteIdentity.java
deleted file mode 100644
index e39d25dd8..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/SiteIdentity.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.json.JSONObject;
-
-import android.text.TextUtils;
-
-public class SiteIdentity {
- private final String LOGTAG = "GeckoSiteIdentity";
- private SecurityMode mSecurityMode;
- private boolean mSecure;
- private MixedMode mMixedModeActive;
- private MixedMode mMixedModeDisplay;
- private TrackingMode mTrackingMode;
- private String mHost;
- private String mOwner;
- private String mSupplemental;
- private String mCountry;
- private String mVerifier;
- private String mOrigin;
-
- // The order of the items here relate to image levels in
- // site_security_level.xml
- public enum SecurityMode {
- UNKNOWN("unknown"),
- IDENTIFIED("identified"),
- VERIFIED("verified"),
- CHROMEUI("chromeUI");
-
- private final String mId;
-
- private SecurityMode(String id) {
- mId = id;
- }
-
- public static SecurityMode fromString(String id) {
- if (id == null) {
- throw new IllegalArgumentException("Can't convert null String to SiteIdentity");
- }
-
- for (SecurityMode mode : SecurityMode.values()) {
- if (TextUtils.equals(mode.mId, id)) {
- return mode;
- }
- }
-
- throw new IllegalArgumentException("Could not convert String id to SiteIdentity");
- }
-
- @Override
- public String toString() {
- return mId;
- }
- }
-
- // The order of the items here relate to image levels in
- // site_security_level.xml
- public enum MixedMode {
- UNKNOWN("unknown"),
- MIXED_CONTENT_BLOCKED("blocked"),
- MIXED_CONTENT_LOADED("loaded");
-
- private final String mId;
-
- private MixedMode(String id) {
- mId = id;
- }
-
- public static MixedMode fromString(String id) {
- if (id == null) {
- throw new IllegalArgumentException("Can't convert null String to MixedMode");
- }
-
- for (MixedMode mode : MixedMode.values()) {
- if (TextUtils.equals(mode.mId, id.toLowerCase())) {
- return mode;
- }
- }
-
- throw new IllegalArgumentException("Could not convert String id to MixedMode");
- }
-
- @Override
- public String toString() {
- return mId;
- }
- }
-
- // The order of the items here relate to image levels in
- // site_security_level.xml
- public enum TrackingMode {
- UNKNOWN("unknown"),
- TRACKING_CONTENT_BLOCKED("tracking_content_blocked"),
- TRACKING_CONTENT_LOADED("tracking_content_loaded");
-
- private final String mId;
-
- private TrackingMode(String id) {
- mId = id;
- }
-
- public static TrackingMode fromString(String id) {
- if (id == null) {
- throw new IllegalArgumentException("Can't convert null String to TrackingMode");
- }
-
- for (TrackingMode mode : TrackingMode.values()) {
- if (TextUtils.equals(mode.mId, id.toLowerCase())) {
- return mode;
- }
- }
-
- throw new IllegalArgumentException("Could not convert String id to TrackingMode");
- }
-
- @Override
- public String toString() {
- return mId;
- }
- }
-
- public SiteIdentity() {
- reset();
- }
-
- public void resetIdentity() {
- mSecurityMode = SecurityMode.UNKNOWN;
- mOrigin = null;
- mHost = null;
- mOwner = null;
- mSupplemental = null;
- mCountry = null;
- mVerifier = null;
- mSecure = false;
- }
-
- public void reset() {
- resetIdentity();
- mMixedModeActive = MixedMode.UNKNOWN;
- mMixedModeDisplay = MixedMode.UNKNOWN;
- mTrackingMode = TrackingMode.UNKNOWN;
- }
-
- void update(JSONObject identityData) {
- if (identityData == null) {
- reset();
- return;
- }
-
- try {
- JSONObject mode = identityData.getJSONObject("mode");
-
- try {
- mMixedModeDisplay = MixedMode.fromString(mode.getString("mixed_display"));
- } catch (Exception e) {
- mMixedModeDisplay = MixedMode.UNKNOWN;
- }
-
- try {
- mMixedModeActive = MixedMode.fromString(mode.getString("mixed_active"));
- } catch (Exception e) {
- mMixedModeActive = MixedMode.UNKNOWN;
- }
-
- try {
- mTrackingMode = TrackingMode.fromString(mode.getString("tracking"));
- } catch (Exception e) {
- mTrackingMode = TrackingMode.UNKNOWN;
- }
-
- try {
- mSecurityMode = SecurityMode.fromString(mode.getString("identity"));
- } catch (Exception e) {
- resetIdentity();
- return;
- }
-
- try {
- mOrigin = identityData.getString("origin");
- mHost = identityData.optString("host", null);
- mOwner = identityData.optString("owner", null);
- mSupplemental = identityData.optString("supplemental", null);
- mCountry = identityData.optString("country", null);
- mVerifier = identityData.optString("verifier", null);
- mSecure = identityData.optBoolean("secure", false);
- } catch (Exception e) {
- resetIdentity();
- }
- } catch (Exception e) {
- reset();
- }
- }
-
- public SecurityMode getSecurityMode() {
- return mSecurityMode;
- }
-
- public String getOrigin() {
- return mOrigin;
- }
-
- public String getHost() {
- return mHost;
- }
-
- public String getOwner() {
- return mOwner;
- }
-
- public boolean hasOwner() {
- return !TextUtils.isEmpty(mOwner);
- }
-
- public String getSupplemental() {
- return mSupplemental;
- }
-
- public String getCountry() {
- return mCountry;
- }
-
- public boolean hasCountry() {
- return !TextUtils.isEmpty(mCountry);
- }
-
- public String getVerifier() {
- return mVerifier;
- }
-
- public boolean isSecure() {
- return mSecure;
- }
-
- public MixedMode getMixedModeActive() {
- return mMixedModeActive;
- }
-
- public MixedMode getMixedModeDisplay() {
- return mMixedModeDisplay;
- }
-
- public TrackingMode getTrackingMode() {
- return mTrackingMode;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/SnackbarBuilder.java b/mobile/android/base/java/org/mozilla/gecko/SnackbarBuilder.java
deleted file mode 100644
index 3283e7c37..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/SnackbarBuilder.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.NativeJSObject;
-
-import android.app.Activity;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.InsetDrawable;
-import android.support.annotation.StringRes;
-import android.support.design.widget.Snackbar;
-import android.support.v4.content.ContextCompat;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.View;
-import android.widget.TextView;
-
-import java.lang.ref.WeakReference;
-
-/**
- * Helper class for creating and dismissing snackbars. Use this class to guarantee a consistent style and behavior
- * across the app.
- */
-public class SnackbarBuilder {
- /**
- * Combined interface for handling all callbacks from a snackbar because anonymous classes can only extend one
- * interface or class.
- */
- public static abstract class SnackbarCallback extends Snackbar.Callback implements View.OnClickListener {}
- public static final String LOGTAG = "GeckoSnackbarBuilder";
-
- /**
- * SnackbarCallback implementation for delegating snackbar events to an EventCallback.
- */
- private static class SnackbarEventCallback extends SnackbarCallback {
- private EventCallback callback;
-
- public SnackbarEventCallback(EventCallback callback) {
- this.callback = callback;
- }
-
- @Override
- public synchronized void onClick(View view) {
- if (callback == null) {
- return;
- }
-
- callback.sendSuccess(null);
- callback = null; // Releasing reference. We only want to execute the callback once.
- }
-
- @Override
- public synchronized void onDismissed(Snackbar snackbar, int event) {
- if (callback == null || event == Snackbar.Callback.DISMISS_EVENT_ACTION) {
- return;
- }
-
- callback.sendError(null);
- callback = null; // Releasing reference. We only want to execute the callback once.
- }
- }
-
- private static final Object currentSnackbarLock = new Object();
- private static WeakReference<Snackbar> currentSnackbar = new WeakReference<>(null); // Guarded by 'currentSnackbarLock'
-
- private final Activity activity;
- private String message;
- private int duration;
- private String action;
- private SnackbarCallback callback;
- private Drawable icon;
- private Integer backgroundColor;
- private Integer actionColor;
-
- /**
- * @param activity Activity to show the snackbar in.
- */
- private SnackbarBuilder(final Activity activity) {
- this.activity = activity;
- }
-
- public static SnackbarBuilder builder(final Activity activity) {
- return new SnackbarBuilder(activity);
- }
-
- /**
- * @param message The text to show. Can be formatted text.
- */
- public SnackbarBuilder message(final String message) {
- this.message = message;
- return this;
- }
-
- /**
- * @param id The id of the string resource to show. Can be formatted text.
- */
- public SnackbarBuilder message(@StringRes final int id) {
- message = activity.getResources().getString(id);
- return this;
- }
-
- /**
- * @param duration How long to display the message.
- */
- public SnackbarBuilder duration(final int duration) {
- this.duration = duration;
- return this;
- }
-
- /**
- * @param action Action text to display.
- */
- public SnackbarBuilder action(final String action) {
- this.action = action;
- return this;
- }
-
- /**
- * @param id The id of the string resource for the action text to display.
- */
- public SnackbarBuilder action(@StringRes final int id) {
- action = activity.getResources().getString(id);
- return this;
- }
-
- /**
- * @param callback Callback to be invoked when the action is clicked or the snackbar is dismissed.
- */
- public SnackbarBuilder callback(final SnackbarCallback callback) {
- this.callback = callback;
- return this;
- }
-
- /**
- * @param callback Callback to be invoked when the action is clicked or the snackbar is dismissed.
- */
- public SnackbarBuilder callback(final EventCallback callback) {
- this.callback = new SnackbarEventCallback(callback);
- return this;
- }
-
- /**
- * @param icon Icon to be displayed with the snackbar text.
- */
- public SnackbarBuilder icon(final Drawable icon) {
- this.icon = icon;
- return this;
- }
-
- /**
- * @param backgroundColor Snackbar background color.
- */
- public SnackbarBuilder backgroundColor(final Integer backgroundColor) {
- this.backgroundColor = backgroundColor;
- return this;
- }
-
- /**
- * @param actionColor Action text color.
- */
- public SnackbarBuilder actionColor(final Integer actionColor) {
- this.actionColor = actionColor;
- return this;
- }
-
- /**
- * @param object Populate the builder with data from a Gecko Snackbar:Show event.
- */
- public SnackbarBuilder fromEvent(final NativeJSObject object) {
- message = object.getString("message");
- duration = object.getInt("duration");
-
- if (object.has("backgroundColor")) {
- final String providedColor = object.getString("backgroundColor");
- try {
- backgroundColor = Color.parseColor(providedColor);
- } catch (IllegalArgumentException e) {
- Log.w(LOGTAG, "Failed to parse color string: " + providedColor);
- }
- }
-
- NativeJSObject actionObject = object.optObject("action", null);
- if (actionObject != null) {
- action = actionObject.optString("label", null);
- }
- return this;
- }
-
- public void buildAndShow() {
- final View parentView = findBestParentView(activity);
- final Snackbar snackbar = Snackbar.make(parentView, message, duration);
-
- if (callback != null && !TextUtils.isEmpty(action)) {
- snackbar.setAction(action, callback);
- if (actionColor == null) {
- snackbar.setActionTextColor(ContextCompat.getColor(activity, R.color.fennec_ui_orange));
- } else {
- snackbar.setActionTextColor(actionColor);
- }
- snackbar.setCallback(callback);
- }
-
- if (icon != null) {
- int leftPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, activity.getResources().getDisplayMetrics());
-
- final InsetDrawable paddedIcon = new InsetDrawable(icon, 0, 0, leftPadding, 0);
-
- paddedIcon.setBounds(0, 0, leftPadding + icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
-
- TextView textView = (TextView) snackbar.getView().findViewById(android.support.design.R.id.snackbar_text);
- textView.setCompoundDrawables(paddedIcon, null, null, null);
- }
-
- if (backgroundColor != null) {
- snackbar.getView().setBackgroundColor(backgroundColor);
- }
-
- snackbar.show();
-
- synchronized (currentSnackbarLock) {
- currentSnackbar = new WeakReference<>(snackbar);
- }
- }
-
- /**
- * Dismiss the currently visible snackbar.
- */
- public static void dismissCurrentSnackbar() {
- synchronized (currentSnackbarLock) {
- final Snackbar snackbar = currentSnackbar.get();
- if (snackbar != null && snackbar.isShown()) {
- snackbar.dismiss();
- }
- }
- }
-
- /**
- * Find the best parent view to hold the Snackbar's view. The Snackbar implementation of the support
- * library will use this view to walk up the view tree to find an actual suitable parent (if needed).
- */
- private static View findBestParentView(Activity activity) {
- if (activity instanceof GeckoApp) {
- final View view = activity.findViewById(R.id.root_layout);
- if (view != null) {
- return view;
- }
- }
-
- return activity.findViewById(android.R.id.content);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/SuggestClient.java b/mobile/android/base/java/org/mozilla/gecko/SuggestClient.java
deleted file mode 100644
index e43bbef1f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/SuggestClient.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-
-import org.json.JSONArray;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.util.HardwareUtils;
-
-import android.content.Context;
-import android.text.TextUtils;
-import android.util.Log;
-import org.mozilla.gecko.util.NetworkUtils;
-
-/**
- * Use network-based search suggestions.
- */
-public class SuggestClient {
- private static final String LOGTAG = "GeckoSuggestClient";
-
- // This should go through GeckoInterface to get the UA, but the search activity
- // doesn't use a GeckoView yet. Until it does, get the UA directly.
- private static final String USER_AGENT = HardwareUtils.isTablet() ?
- AppConstants.USER_AGENT_FENNEC_TABLET : AppConstants.USER_AGENT_FENNEC_MOBILE;
-
- private final Context mContext;
- private final int mTimeout;
-
- // should contain the string "__searchTerms__", which is replaced with the query
- private final String mSuggestTemplate;
-
- // the maximum number of suggestions to return
- private final int mMaxResults;
-
- // used by robocop for testing
- private final boolean mCheckNetwork;
-
- // used to make suggestions appear instantly after opt-in
- private String mPrevQuery;
- private ArrayList<String> mPrevResults;
-
- @RobocopTarget
- public SuggestClient(Context context, String suggestTemplate, int timeout, int maxResults, boolean checkNetwork) {
- mContext = context;
- mMaxResults = maxResults;
- mSuggestTemplate = suggestTemplate;
- mTimeout = timeout;
- mCheckNetwork = checkNetwork;
- }
-
- public String getSuggestTemplate() {
- return mSuggestTemplate;
- }
-
- /**
- * Queries for a given search term and returns an ArrayList of suggestions.
- */
- public ArrayList<String> query(String query) {
- if (query.equals(mPrevQuery))
- return mPrevResults;
-
- ArrayList<String> suggestions = new ArrayList<String>();
- if (TextUtils.isEmpty(mSuggestTemplate) || TextUtils.isEmpty(query)) {
- return suggestions;
- }
-
- if (!NetworkUtils.isConnected(mContext) && mCheckNetwork) {
- Log.i(LOGTAG, "Not connected to network");
- return suggestions;
- }
-
- try {
- String encoded = URLEncoder.encode(query, "UTF-8");
- String suggestUri = mSuggestTemplate.replace("__searchTerms__", encoded);
-
- URL url = new URL(suggestUri);
- String json = null;
- HttpURLConnection urlConnection = null;
- InputStream in = null;
- try {
- urlConnection = (HttpURLConnection) url.openConnection();
- urlConnection.setConnectTimeout(mTimeout);
- urlConnection.setRequestProperty("User-Agent", USER_AGENT);
- in = new BufferedInputStream(urlConnection.getInputStream());
- json = convertStreamToString(in);
- } finally {
- if (urlConnection != null)
- urlConnection.disconnect();
- if (in != null) {
- try {
- in.close();
- } catch (IOException e) {
- Log.e(LOGTAG, "error", e);
- }
- }
- }
-
- if (json != null) {
- /*
- * Sample result:
- * ["foo",["food network","foothill college","foot locker",...]]
- */
- JSONArray results = new JSONArray(json);
- JSONArray jsonSuggestions = results.getJSONArray(1);
-
- int added = 0;
- for (int i = 0; (i < jsonSuggestions.length()) && (added < mMaxResults); i++) {
- String suggestion = jsonSuggestions.getString(i);
- if (!suggestion.equalsIgnoreCase(query)) {
- suggestions.add(suggestion);
- added++;
- }
- }
- } else {
- Log.e(LOGTAG, "Suggestion query failed");
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Error", e);
- }
-
- mPrevQuery = query;
- mPrevResults = suggestions;
- return suggestions;
- }
-
- private String convertStreamToString(java.io.InputStream is) {
- try {
- return new java.util.Scanner(is).useDelimiter("\\A").next();
- } catch (java.util.NoSuchElementException e) {
- return "";
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/Tab.java b/mobile/android/base/java/org/mozilla/gecko/Tab.java
deleted file mode 100644
index 6010a3dd9..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/Tab.java
+++ /dev/null
@@ -1,843 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Future;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.URLMetadata;
-import org.mozilla.gecko.gfx.BitmapUtils;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconDescriptor;
-import org.mozilla.gecko.icons.IconRequestBuilder;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.reader.ReaderModeUtils;
-import org.mozilla.gecko.reader.ReadingListHelper;
-import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.widget.SiteLogins;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.drawable.BitmapDrawable;
-import android.os.Build;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-
-public class Tab {
- private static final String LOGTAG = "GeckoTab";
-
- private static Pattern sColorPattern;
- private final int mId;
- private final BrowserDB mDB;
- private long mLastUsed;
- private String mUrl;
- private String mBaseDomain;
- private String mUserRequested; // The original url requested. May be typed by the user or sent by an extneral app for example.
- private String mTitle;
- private Bitmap mFavicon;
- private String mFaviconUrl;
- private String mApplicationId; // Intended to be null after explicit user action.
-
- private IconRequestBuilder mIconRequestBuilder;
- private Future<IconResponse> mRunningIconRequest;
-
- private boolean mHasFeeds;
- private boolean mHasOpenSearch;
- private final SiteIdentity mSiteIdentity;
- private SiteLogins mSiteLogins;
- private BitmapDrawable mThumbnail;
- private final int mParentId;
- // Indicates the url was loaded from a source external to the app. This will be cleared
- // when the user explicitly loads a new url (e.g. clicking a link is not explicit).
- private final boolean mExternal;
- private boolean mBookmark;
- private int mFaviconLoadId;
- private String mContentType;
- private boolean mHasTouchListeners;
- private final ArrayList<View> mPluginViews;
- private int mState;
- private Bitmap mThumbnailBitmap;
- private boolean mDesktopMode;
- private boolean mEnteringReaderMode;
- private final Context mAppContext;
- private ErrorType mErrorType = ErrorType.NONE;
- private volatile int mLoadProgress;
- private volatile int mRecordingCount;
- private volatile boolean mIsAudioPlaying;
- private volatile boolean mIsMediaPlaying;
- private String mMostRecentHomePanel;
- private boolean mShouldShowToolbarWithoutAnimationOnFirstSelection;
-
- /*
- * Bundle containing restore data for the panel referenced in mMostRecentHomePanel. This can be
- * e.g. the most recent folder for the bookmarks panel, or any other state that should be
- * persisted. This is then used e.g. when returning to homepanels via history.
- */
- private Bundle mMostRecentHomePanelData;
-
- private int mHistoryIndex;
- private int mHistorySize;
- private boolean mCanDoBack;
- private boolean mCanDoForward;
-
- private boolean mIsEditing;
- private final TabEditingState mEditingState = new TabEditingState();
-
- // Will be true when tab is loaded from cache while device was offline.
- private boolean mLoadedFromCache;
-
- public static final int STATE_DELAYED = 0;
- public static final int STATE_LOADING = 1;
- public static final int STATE_SUCCESS = 2;
- public static final int STATE_ERROR = 3;
-
- public static final int LOAD_PROGRESS_INIT = 10;
- public static final int LOAD_PROGRESS_START = 20;
- public static final int LOAD_PROGRESS_LOCATION_CHANGE = 60;
- public static final int LOAD_PROGRESS_LOADED = 80;
- public static final int LOAD_PROGRESS_STOP = 100;
-
- public enum ErrorType {
- CERT_ERROR, // Pages with certificate problems
- BLOCKED, // Pages blocked for phishing or malware warnings
- NET_ERROR, // All other types of error
- NONE // Non error pages
- }
-
- public Tab(Context context, int id, String url, boolean external, int parentId, String title) {
- mAppContext = context.getApplicationContext();
- mDB = BrowserDB.from(context);
- mId = id;
- mUrl = url;
- mBaseDomain = "";
- mUserRequested = "";
- mExternal = external;
- mParentId = parentId;
- mTitle = title == null ? "" : title;
- mSiteIdentity = new SiteIdentity();
- mHistoryIndex = -1;
- mContentType = "";
- mPluginViews = new ArrayList<View>();
- mState = shouldShowProgress(url) ? STATE_LOADING : STATE_SUCCESS;
- mLoadProgress = LOAD_PROGRESS_INIT;
- mIconRequestBuilder = Icons.with(mAppContext).pageUrl(mUrl);
-
- updateBookmark();
- }
-
- private ContentResolver getContentResolver() {
- return mAppContext.getContentResolver();
- }
-
- public void onDestroy() {
- Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.CLOSED);
- }
-
- @RobocopTarget
- public int getId() {
- return mId;
- }
-
- public synchronized void onChange() {
- mLastUsed = System.currentTimeMillis();
- }
-
- public synchronized long getLastUsed() {
- return mLastUsed;
- }
-
- public int getParentId() {
- return mParentId;
- }
-
- // may be null if user-entered query hasn't yet been resolved to a URI
- public synchronized String getURL() {
- return mUrl;
- }
-
- // mUserRequested should never be null, but it may be an empty string
- public synchronized String getUserRequested() {
- return mUserRequested;
- }
-
- // mTitle should never be null, but it may be an empty string
- public synchronized String getTitle() {
- return mTitle;
- }
-
- public String getDisplayTitle() {
- if (mTitle != null && mTitle.length() > 0) {
- return mTitle;
- }
-
- return mUrl;
- }
-
- /**
- * Returns the base domain of the loaded uri. Note that if the page is
- * a Reader mode uri, the base domain returned is that of the original uri.
- */
- public String getBaseDomain() {
- return mBaseDomain;
- }
-
- public Bitmap getFavicon() {
- return mFavicon;
- }
-
- protected String getApplicationId() {
- return mApplicationId;
- }
-
- protected void setApplicationId(final String applicationId) {
- mApplicationId = applicationId;
- }
-
- public BitmapDrawable getThumbnail() {
- return mThumbnail;
- }
-
- public String getMostRecentHomePanel() {
- return mMostRecentHomePanel;
- }
-
- public Bundle getMostRecentHomePanelData() {
- return mMostRecentHomePanelData;
- }
-
- public void setMostRecentHomePanel(String panelId) {
- mMostRecentHomePanel = panelId;
- mMostRecentHomePanelData = null;
- }
-
- public void setMostRecentHomePanelData(Bundle data) {
- mMostRecentHomePanelData = data;
- }
-
- public Bitmap getThumbnailBitmap(int width, int height) {
- if (mThumbnailBitmap != null) {
- // Bug 787318 - Honeycomb has a bug with bitmap caching, we can't
- // reuse the bitmap there.
- boolean honeycomb = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB
- && Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR2);
- boolean sizeChange = mThumbnailBitmap.getWidth() != width
- || mThumbnailBitmap.getHeight() != height;
- if (honeycomb || sizeChange) {
- mThumbnailBitmap = null;
- }
- }
-
- if (mThumbnailBitmap == null) {
- Bitmap.Config config = (GeckoAppShell.getScreenDepth() == 24) ?
- Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
- mThumbnailBitmap = Bitmap.createBitmap(width, height, config);
- }
-
- return mThumbnailBitmap;
- }
-
- public void updateThumbnail(final Bitmap b, final ThumbnailHelper.CachePolicy cachePolicy) {
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- if (b != null) {
- try {
- mThumbnail = new BitmapDrawable(mAppContext.getResources(), b);
- if (mState == Tab.STATE_SUCCESS && cachePolicy == ThumbnailHelper.CachePolicy.STORE) {
- saveThumbnailToDB(mDB);
- } else {
- // If the page failed to load, or requested that we not cache info about it, clear any previous
- // thumbnails we've stored.
- clearThumbnailFromDB(mDB);
- }
- } catch (OutOfMemoryError oom) {
- Log.w(LOGTAG, "Unable to create/scale bitmap.", oom);
- mThumbnail = null;
- }
- } else {
- mThumbnail = null;
- }
-
- Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.THUMBNAIL);
- }
- });
- }
-
- public synchronized String getFaviconURL() {
- return mFaviconUrl;
- }
-
- public boolean hasFeeds() {
- return mHasFeeds;
- }
-
- public boolean hasOpenSearch() {
- return mHasOpenSearch;
- }
-
- public boolean hasLoadedFromCache() {
- return mLoadedFromCache;
- }
-
- public SiteIdentity getSiteIdentity() {
- return mSiteIdentity;
- }
-
- public void resetSiteIdentity() {
- if (mSiteIdentity != null) {
- mSiteIdentity.reset();
- Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.SECURITY_CHANGE);
- }
- }
-
- public SiteLogins getSiteLogins() {
- return mSiteLogins;
- }
-
- public boolean isBookmark() {
- return mBookmark;
- }
-
- public boolean isExternal() {
- return mExternal;
- }
-
- public synchronized void updateURL(String url) {
- if (url != null && url.length() > 0) {
- mUrl = url;
- }
- }
-
- public synchronized void updateUserRequested(String userRequested) {
- mUserRequested = userRequested;
- }
-
- public void setErrorType(String type) {
- if ("blocked".equals(type))
- setErrorType(ErrorType.BLOCKED);
- else if ("certerror".equals(type))
- setErrorType(ErrorType.CERT_ERROR);
- else if ("neterror".equals(type))
- setErrorType(ErrorType.NET_ERROR);
- else
- setErrorType(ErrorType.NONE);
- }
-
- public void setErrorType(ErrorType type) {
- mErrorType = type;
- }
-
- public void setMetadata(JSONObject metadata) {
- if (metadata == null) {
- return;
- }
-
- final ContentResolver cr = mAppContext.getContentResolver();
- final URLMetadata urlMetadata = mDB.getURLMetadata();
-
- final Map<String, Object> data = urlMetadata.fromJSON(metadata);
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- urlMetadata.save(cr, data);
- }
- });
- }
-
- public ErrorType getErrorType() {
- return mErrorType;
- }
-
- public void setContentType(String contentType) {
- mContentType = (contentType == null) ? "" : contentType;
- }
-
- public String getContentType() {
- return mContentType;
- }
-
- public int getHistoryIndex() {
- return mHistoryIndex;
- }
-
- public int getHistorySize() {
- return mHistorySize;
- }
-
- public synchronized void updateTitle(String title) {
- // Keep the title unchanged while entering reader mode.
- if (mEnteringReaderMode) {
- return;
- }
-
- // If there was a title, but it hasn't changed, do nothing.
- if (mTitle != null &&
- TextUtils.equals(mTitle, title)) {
- return;
- }
-
- mTitle = (title == null ? "" : title);
- Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.TITLE);
- }
-
- public void setState(int state) {
- mState = state;
-
- if (mState != Tab.STATE_LOADING)
- mEnteringReaderMode = false;
- }
-
- public int getState() {
- return mState;
- }
-
- public void setHasTouchListeners(boolean aValue) {
- mHasTouchListeners = aValue;
- }
-
- public boolean getHasTouchListeners() {
- return mHasTouchListeners;
- }
-
- public synchronized void addFavicon(String faviconURL, int faviconSize, String mimeType) {
- mIconRequestBuilder
- .icon(IconDescriptor.createFavicon(faviconURL, faviconSize, mimeType))
- .deferBuild();
- }
-
- public synchronized void addTouchicon(String iconUrl, int faviconSize, String mimeType) {
- mIconRequestBuilder
- .icon(IconDescriptor.createTouchicon(iconUrl, faviconSize, mimeType))
- .deferBuild();
- }
-
- public void loadFavicon() {
- // Static Favicons never change
- if (AboutPages.isBuiltinIconPage(mUrl) && mFavicon != null) {
- return;
- }
-
- mRunningIconRequest = mIconRequestBuilder
- .build()
- .execute(new IconCallback() {
- @Override
- public void onIconResponse(IconResponse response) {
- mFavicon = response.getBitmap();
-
- Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.FAVICON);
- }
- });
- }
-
- public synchronized void clearFavicon() {
- // Cancel any ongoing favicon load (if we never finished downloading the old favicon before
- // we changed page).
- if (mRunningIconRequest != null) {
- mRunningIconRequest.cancel(true);
- }
-
- // Keep the favicon unchanged while entering reader mode
- if (mEnteringReaderMode)
- return;
-
- mFavicon = null;
- mFaviconUrl = null;
- }
-
- public void setHasFeeds(boolean hasFeeds) {
- mHasFeeds = hasFeeds;
- }
-
- public void setHasOpenSearch(boolean hasOpenSearch) {
- mHasOpenSearch = hasOpenSearch;
- }
-
- public void setLoadedFromCache(boolean loadedFromCache) {
- mLoadedFromCache = loadedFromCache;
- }
-
- public void updateIdentityData(JSONObject identityData) {
- mSiteIdentity.update(identityData);
- }
-
- public void setSiteLogins(SiteLogins siteLogins) {
- mSiteLogins = siteLogins;
- }
-
- void updateBookmark() {
- if (getURL() == null) {
- return;
- }
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- final String url = getURL();
- if (url == null) {
- return;
- }
- final String pageUrl = ReaderModeUtils.stripAboutReaderUrl(url);
-
- mBookmark = mDB.isBookmark(getContentResolver(), pageUrl);
- Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.MENU_UPDATED);
- }
- });
- }
-
- public void addBookmark() {
- final String url = getURL();
- if (url == null) {
- return;
- }
-
- final String pageUrl = ReaderModeUtils.stripAboutReaderUrl(getURL());
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- mDB.addBookmark(getContentResolver(), mTitle, pageUrl);
- Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.BOOKMARK_ADDED);
- }
- });
-
- if (AboutPages.isAboutReader(url)) {
- ReadingListHelper.cacheReaderItem(pageUrl, mId, mAppContext);
- }
- }
-
- public void removeBookmark() {
- final String url = getURL();
- if (url == null) {
- return;
- }
-
- final String pageUrl = ReaderModeUtils.stripAboutReaderUrl(getURL());
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- mDB.removeBookmarksWithURL(getContentResolver(), pageUrl);
- Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.BOOKMARK_REMOVED);
- }
- });
-
- // We need to ensure we remove readercached items here - we could have switched out of readermode
- // before unbookmarking, so we don't necessarily have an about:reader URL here.
- ReadingListHelper.removeCachedReaderItem(pageUrl, mAppContext);
- }
-
- public boolean isEnteringReaderMode() {
- return mEnteringReaderMode;
- }
-
- public void doReload(boolean bypassCache) {
- GeckoAppShell.notifyObservers("Session:Reload", "{\"bypassCache\":" + String.valueOf(bypassCache) + "}");
- }
-
- // Our version of nsSHistory::GetCanGoBack
- public boolean canDoBack() {
- return mCanDoBack;
- }
-
- public boolean doBack() {
- if (!canDoBack())
- return false;
-
- GeckoAppShell.notifyObservers("Session:Back", "");
- return true;
- }
-
- public void doStop() {
- GeckoAppShell.notifyObservers("Session:Stop", "");
- }
-
- // Our version of nsSHistory::GetCanGoForward
- public boolean canDoForward() {
- return mCanDoForward;
- }
-
- public boolean doForward() {
- if (!canDoForward())
- return false;
-
- GeckoAppShell.notifyObservers("Session:Forward", "");
- return true;
- }
-
- void handleLocationChange(JSONObject message) throws JSONException {
- final String uri = message.getString("uri");
- final String oldUrl = getURL();
- final boolean sameDocument = message.getBoolean("sameDocument");
- mEnteringReaderMode = ReaderModeUtils.isEnteringReaderMode(oldUrl, uri);
- mHistoryIndex = message.getInt("historyIndex");
- mHistorySize = message.getInt("historySize");
- mCanDoBack = message.getBoolean("canGoBack");
- mCanDoForward = message.getBoolean("canGoForward");
-
- if (!TextUtils.equals(oldUrl, uri)) {
- updateURL(uri);
- updateBookmark();
- if (!sameDocument) {
- // We can unconditionally clear the favicon and title here: we
- // already filtered both cases in which this was a (pseudo-)
- // spurious location change, so we're definitely loading a new
- // page.
- clearFavicon();
-
- // Start to build a new request to load a favicon.
- mIconRequestBuilder = Icons.with(mAppContext)
- .pageUrl(uri);
-
- // Load local static Favicons immediately
- if (AboutPages.isBuiltinIconPage(uri)) {
- loadFavicon();
- }
-
- updateTitle(null);
- }
- }
-
- if (sameDocument) {
- // We can get a location change event for the same document with an anchor tag
- // Notify listeners so that buttons like back or forward will update themselves
- Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.LOCATION_CHANGE, oldUrl);
- return;
- }
-
- setContentType(message.getString("contentType"));
- updateUserRequested(message.getString("userRequested"));
- mBaseDomain = message.optString("baseDomain");
-
- setHasFeeds(false);
- setHasOpenSearch(false);
- mSiteIdentity.reset();
- setSiteLogins(null);
- setHasTouchListeners(false);
- setErrorType(ErrorType.NONE);
- setLoadProgressIfLoading(LOAD_PROGRESS_LOCATION_CHANGE);
-
- Tabs.getInstance().notifyListeners(this, Tabs.TabEvents.LOCATION_CHANGE, oldUrl);
- }
-
- private static boolean shouldShowProgress(final String url) {
- return !AboutPages.isAboutPage(url);
- }
-
- void handleDocumentStart(boolean restoring, String url) {
- setLoadProgress(LOAD_PROGRESS_START);
- setState((!restoring && shouldShowProgress(url)) ? STATE_LOADING : STATE_SUCCESS);
- mSiteIdentity.reset();
- }
-
- void handleDocumentStop(boolean success) {
- setState(success ? STATE_SUCCESS : STATE_ERROR);
-
- final String oldURL = getURL();
- final Tab tab = this;
- tab.setLoadProgress(LOAD_PROGRESS_STOP);
-
- ThreadUtils.getBackgroundHandler().postDelayed(new Runnable() {
- @Override
- public void run() {
- // tab.getURL() may return null
- if (!TextUtils.equals(oldURL, getURL()))
- return;
-
- ThumbnailHelper.getInstance().getAndProcessThumbnailFor(tab);
- }
- }, 500);
- }
-
- void handleContentLoaded() {
- setLoadProgressIfLoading(LOAD_PROGRESS_LOADED);
- }
-
- protected void saveThumbnailToDB(final BrowserDB db) {
- final BitmapDrawable thumbnail = mThumbnail;
- if (thumbnail == null) {
- return;
- }
-
- try {
- final String url = getURL();
- if (url == null) {
- return;
- }
-
- db.updateThumbnailForUrl(getContentResolver(), url, thumbnail);
- } catch (Exception e) {
- // ignore
- }
- }
-
- public void loadThumbnailFromDB(final BrowserDB db) {
- try {
- final String url = getURL();
- if (url == null) {
- return;
- }
-
- byte[] thumbnail = db.getThumbnailForUrl(getContentResolver(), url);
- if (thumbnail == null) {
- return;
- }
-
- Bitmap bitmap = BitmapUtils.decodeByteArray(thumbnail);
- mThumbnail = new BitmapDrawable(mAppContext.getResources(), bitmap);
-
- Tabs.getInstance().notifyListeners(Tab.this, Tabs.TabEvents.THUMBNAIL);
- } catch (Exception e) {
- // ignore
- }
- }
-
- private void clearThumbnailFromDB(final BrowserDB db) {
- try {
- final String url = getURL();
- if (url == null) {
- return;
- }
-
- // Passing in a null thumbnail will delete the stored thumbnail for this url
- db.updateThumbnailForUrl(getContentResolver(), url, null);
- } catch (Exception e) {
- // ignore
- }
- }
-
- public void addPluginView(View view) {
- mPluginViews.add(view);
- }
-
- public void removePluginView(View view) {
- mPluginViews.remove(view);
- }
-
- public View[] getPluginViews() {
- return mPluginViews.toArray(new View[mPluginViews.size()]);
- }
-
- public void setDesktopMode(boolean enabled) {
- mDesktopMode = enabled;
- }
-
- public boolean getDesktopMode() {
- return mDesktopMode;
- }
-
- public boolean isPrivate() {
- return false;
- }
-
- /**
- * Sets the tab load progress to the given percentage.
- *
- * @param progressPercentage Percentage to set progress to (0-100)
- */
- void setLoadProgress(int progressPercentage) {
- mLoadProgress = progressPercentage;
- }
-
- /**
- * Sets the tab load progress to the given percentage only if the tab is
- * currently loading.
- *
- * about:neterror can trigger a STOP before other page load events (bug
- * 976426), so any post-START events should make sure the page is loading
- * before updating progress.
- *
- * @param progressPercentage Percentage to set progress to (0-100)
- */
- void setLoadProgressIfLoading(int progressPercentage) {
- if (getState() == STATE_LOADING) {
- setLoadProgress(progressPercentage);
- }
- }
-
- /**
- * Gets the tab load progress percentage.
- *
- * @return Current progress percentage
- */
- public int getLoadProgress() {
- return mLoadProgress;
- }
-
- public void setRecording(boolean isRecording) {
- if (isRecording) {
- mRecordingCount++;
- } else {
- mRecordingCount--;
- }
- }
-
- public boolean isRecording() {
- return mRecordingCount > 0;
- }
-
- /**
- * The "MediaPlaying" is used for controling media control interface and
- * means the tab has playing media.
- *
- * @param isMediaPlaying the tab has any playing media or not
- */
- public void setIsMediaPlaying(boolean isMediaPlaying) {
- mIsMediaPlaying = isMediaPlaying;
- }
-
- public boolean isMediaPlaying() {
- return mIsMediaPlaying;
- }
-
- /**
- * The "AudioPlaying" is used for showing the tab sound indicator and means
- * the tab has playing media and the media is audible.
- *
- * @param isAudioPlaying the tab has any audible playing media or not
- */
- public void setIsAudioPlaying(boolean isAudioPlaying) {
- mIsAudioPlaying = isAudioPlaying;
- }
-
- public boolean isAudioPlaying() {
- return mIsAudioPlaying;
- }
-
- public boolean isEditing() {
- return mIsEditing;
- }
-
- public void setIsEditing(final boolean isEditing) {
- this.mIsEditing = isEditing;
- }
-
- public TabEditingState getEditingState() {
- return mEditingState;
- }
-
- public void setShouldShowToolbarWithoutAnimationOnFirstSelection(final boolean shouldShowWithoutAnimation) {
- mShouldShowToolbarWithoutAnimationOnFirstSelection = shouldShowWithoutAnimation;
- }
-
- public boolean getShouldShowToolbarWithoutAnimationOnFirstSelection() {
- return mShouldShowToolbarWithoutAnimationOnFirstSelection;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/Tabs.java b/mobile/android/base/java/org/mozilla/gecko/Tabs.java
deleted file mode 100644
index c7e024fe0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/Tabs.java
+++ /dev/null
@@ -1,1021 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import android.support.annotation.Nullable;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import org.mozilla.gecko.annotation.JNITarget;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.gfx.LayerView;
-import org.mozilla.gecko.mozglue.SafeIntent;
-import org.mozilla.gecko.notifications.WhatsNewReceiver;
-import org.mozilla.gecko.reader.ReaderModeUtils;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.OnAccountsUpdateListener;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.database.sqlite.SQLiteException;
-import android.graphics.Color;
-import android.net.Uri;
-import android.os.Handler;
-import android.provider.Browser;
-import android.support.v4.content.ContextCompat;
-import android.util.Log;
-
-public class Tabs implements GeckoEventListener {
- private static final String LOGTAG = "GeckoTabs";
-
- // mOrder and mTabs are always of the same cardinality, and contain the same values.
- private final CopyOnWriteArrayList<Tab> mOrder = new CopyOnWriteArrayList<Tab>();
-
- // All writes to mSelectedTab must be synchronized on the Tabs instance.
- // In general, it's preferred to always use selectTab()).
- private volatile Tab mSelectedTab;
-
- // All accesses to mTabs must be synchronized on the Tabs instance.
- private final HashMap<Integer, Tab> mTabs = new HashMap<Integer, Tab>();
-
- private AccountManager mAccountManager;
- private OnAccountsUpdateListener mAccountListener;
-
- public static final int LOADURL_NONE = 0;
- public static final int LOADURL_NEW_TAB = 1 << 0;
- public static final int LOADURL_USER_ENTERED = 1 << 1;
- public static final int LOADURL_PRIVATE = 1 << 2;
- public static final int LOADURL_PINNED = 1 << 3;
- public static final int LOADURL_DELAY_LOAD = 1 << 4;
- public static final int LOADURL_DESKTOP = 1 << 5;
- public static final int LOADURL_BACKGROUND = 1 << 6;
- /** Indicates the url has been specified by a source external to the app. */
- public static final int LOADURL_EXTERNAL = 1 << 7;
- /** Indicates the tab is the first shown after Firefox is hidden and restored. */
- public static final int LOADURL_FIRST_AFTER_ACTIVITY_UNHIDDEN = 1 << 8;
-
- private static final long PERSIST_TABS_AFTER_MILLISECONDS = 1000 * 2;
-
- public static final int INVALID_TAB_ID = -1;
-
- private static final AtomicInteger sTabId = new AtomicInteger(0);
- private volatile boolean mInitialTabsAdded;
-
- private Context mAppContext;
- private LayerView mLayerView;
- private ContentObserver mBookmarksContentObserver;
- private PersistTabsRunnable mPersistTabsRunnable;
- private int mPrivateClearColor;
-
- private static class PersistTabsRunnable implements Runnable {
- private final BrowserDB db;
- private final Context context;
- private final Iterable<Tab> tabs;
-
- public PersistTabsRunnable(final Context context, Iterable<Tab> tabsInOrder) {
- this.context = context;
- this.db = BrowserDB.from(context);
- this.tabs = tabsInOrder;
- }
-
- @Override
- public void run() {
- try {
- db.getTabsAccessor().persistLocalTabs(context.getContentResolver(), tabs);
- } catch (SQLiteException e) {
- Log.w(LOGTAG, "Error persisting local tabs", e);
- }
- }
- };
-
- private Tabs() {
- EventDispatcher.getInstance().registerGeckoThreadListener(this,
- "Tab:Added",
- "Tab:Close",
- "Tab:Select",
- "Content:LocationChange",
- "Content:SecurityChange",
- "Content:StateChange",
- "Content:LoadError",
- "Content:PageShow",
- "DOMTitleChanged",
- "Link:Favicon",
- "Link:Touchicon",
- "Link:Feed",
- "Link:OpenSearch",
- "DesktopMode:Changed",
- "Tab:StreamStart",
- "Tab:StreamStop",
- "Tab:AudioPlayingChange",
- "Tab:MediaPlaybackChange");
-
- mPrivateClearColor = Color.RED;
-
- }
-
- public synchronized void attachToContext(Context context, LayerView layerView) {
- final Context appContext = context.getApplicationContext();
- if (mAppContext == appContext) {
- return;
- }
-
- if (mAppContext != null) {
- // This should never happen.
- Log.w(LOGTAG, "The application context has changed!");
- }
-
- mAppContext = appContext;
- mLayerView = layerView;
- mPrivateClearColor = ContextCompat.getColor(context, R.color.tabs_tray_grey_pressed);
- mAccountManager = AccountManager.get(appContext);
-
- mAccountListener = new OnAccountsUpdateListener() {
- @Override
- public void onAccountsUpdated(Account[] accounts) {
- queuePersistAllTabs();
- }
- };
-
- // The listener will run on the background thread (see 2nd argument).
- mAccountManager.addOnAccountsUpdatedListener(mAccountListener, ThreadUtils.getBackgroundHandler(), false);
-
- if (mBookmarksContentObserver != null) {
- // It's safe to use the db here since we aren't doing any I/O.
- final GeckoProfile profile = GeckoProfile.get(context);
- BrowserDB.from(profile).registerBookmarkObserver(getContentResolver(), mBookmarksContentObserver);
- }
- }
-
- /**
- * Gets the tab count corresponding to the private state of the selected
- * tab.
- *
- * If the selected tab is a non-private tab, this will return the number of
- * non-private tabs; likewise, if this is a private tab, this will return
- * the number of private tabs.
- *
- * @return the number of tabs in the current private state
- */
- public synchronized int getDisplayCount() {
- // Once mSelectedTab is non-null, it cannot be null for the remainder
- // of the object's lifetime.
- boolean getPrivate = mSelectedTab != null && mSelectedTab.isPrivate();
- int count = 0;
- for (Tab tab : mOrder) {
- if (tab.isPrivate() == getPrivate) {
- count++;
- }
- }
- return count;
- }
-
- public int isOpen(String url) {
- for (Tab tab : mOrder) {
- if (tab.getURL().equals(url)) {
- return tab.getId();
- }
- }
- return -1;
- }
-
- // Must be synchronized to avoid racing on mBookmarksContentObserver.
- private void lazyRegisterBookmarkObserver() {
- if (mBookmarksContentObserver == null) {
- mBookmarksContentObserver = new ContentObserver(null) {
- @Override
- public void onChange(boolean selfChange) {
- for (Tab tab : mOrder) {
- tab.updateBookmark();
- }
- }
- };
-
- // It's safe to use the db here since we aren't doing any I/O.
- final GeckoProfile profile = GeckoProfile.get(mAppContext);
- BrowserDB.from(profile).registerBookmarkObserver(getContentResolver(), mBookmarksContentObserver);
- }
- }
-
- private Tab addTab(int id, String url, boolean external, int parentId, String title, boolean isPrivate, int tabIndex) {
- final Tab tab = isPrivate ? new PrivateTab(mAppContext, id, url, external, parentId, title) :
- new Tab(mAppContext, id, url, external, parentId, title);
- synchronized (this) {
- lazyRegisterBookmarkObserver();
- mTabs.put(id, tab);
-
- if (tabIndex > -1) {
- mOrder.add(tabIndex, tab);
- } else {
- mOrder.add(tab);
- }
- }
-
- // Suppress the ADDED event to prevent animation of tabs created via session restore.
- if (mInitialTabsAdded) {
- notifyListeners(tab, TabEvents.ADDED,
- Integer.toString(getPrivacySpecificTabIndex(tabIndex, isPrivate)));
- }
-
- return tab;
- }
-
- // Return the index, among those tabs whose privacy setting matches isPrivate, of the tab at
- // position index in mOrder. Returns -1, for "new last tab", when index is -1.
- private int getPrivacySpecificTabIndex(int index, boolean isPrivate) {
- int privacySpecificIndex = -1;
- for (int i = 0; i <= index; i++) {
- final Tab tab = mOrder.get(i);
- if (tab.isPrivate() == isPrivate) {
- privacySpecificIndex++;
- }
- }
- return privacySpecificIndex;
- }
-
- public synchronized void removeTab(int id) {
- if (mTabs.containsKey(id)) {
- Tab tab = getTab(id);
- mOrder.remove(tab);
- mTabs.remove(id);
- }
- }
-
- public synchronized Tab selectTab(int id) {
- if (!mTabs.containsKey(id))
- return null;
-
- final Tab oldTab = getSelectedTab();
- final Tab tab = mTabs.get(id);
-
- // This avoids a NPE below, but callers need to be careful to
- // handle this case.
- if (tab == null || oldTab == tab) {
- return tab;
- }
-
- mSelectedTab = tab;
- notifyListeners(tab, TabEvents.SELECTED);
-
- if (mLayerView != null) {
- mLayerView.setClearColor(getTabColor(tab));
- }
-
- if (oldTab != null) {
- notifyListeners(oldTab, TabEvents.UNSELECTED);
- }
-
- // Pass a message to Gecko to update tab state in BrowserApp.
- GeckoAppShell.notifyObservers("Tab:Selected", String.valueOf(tab.getId()));
- return tab;
- }
-
- public synchronized boolean selectLastTab() {
- if (mOrder.isEmpty()) {
- return false;
- }
-
- selectTab(mOrder.get(mOrder.size() - 1).getId());
- return true;
- }
-
- private int getIndexOf(Tab tab) {
- return mOrder.lastIndexOf(tab);
- }
-
- private Tab getNextTabFrom(Tab tab, boolean getPrivate) {
- int numTabs = mOrder.size();
- int index = getIndexOf(tab);
- for (int i = index + 1; i < numTabs; i++) {
- Tab next = mOrder.get(i);
- if (next.isPrivate() == getPrivate) {
- return next;
- }
- }
- return null;
- }
-
- private Tab getPreviousTabFrom(Tab tab, boolean getPrivate) {
- int index = getIndexOf(tab);
- for (int i = index - 1; i >= 0; i--) {
- Tab prev = mOrder.get(i);
- if (prev.isPrivate() == getPrivate) {
- return prev;
- }
- }
- return null;
- }
-
- /**
- * Gets the selected tab.
- *
- * The selected tab can be null if we're doing a session restore after a
- * crash and Gecko isn't ready yet.
- *
- * @return the selected tab, or null if no tabs exist
- */
- @Nullable
- public Tab getSelectedTab() {
- return mSelectedTab;
- }
-
- public boolean isSelectedTab(Tab tab) {
- return tab != null && tab == mSelectedTab;
- }
-
- public boolean isSelectedTabId(int tabId) {
- final Tab selected = mSelectedTab;
- return selected != null && selected.getId() == tabId;
- }
-
- @RobocopTarget
- public synchronized Tab getTab(int id) {
- if (id == -1)
- return null;
-
- if (mTabs.size() == 0)
- return null;
-
- if (!mTabs.containsKey(id))
- return null;
-
- return mTabs.get(id);
- }
-
- public synchronized Tab getTabForApplicationId(final String applicationId) {
- if (applicationId == null) {
- return null;
- }
-
- for (final Tab tab : mOrder) {
- if (applicationId.equals(tab.getApplicationId())) {
- return tab;
- }
- }
-
- return null;
- }
-
- /** Close tab and then select the default next tab */
- @RobocopTarget
- public synchronized void closeTab(Tab tab) {
- closeTab(tab, getNextTab(tab));
- }
-
- public synchronized void closeTab(Tab tab, Tab nextTab) {
- closeTab(tab, nextTab, false);
- }
-
- public synchronized void closeTab(Tab tab, boolean showUndoToast) {
- closeTab(tab, getNextTab(tab), showUndoToast);
- }
-
- /** Close tab and then select nextTab */
- public synchronized void closeTab(final Tab tab, Tab nextTab, boolean showUndoToast) {
- if (tab == null)
- return;
-
- int tabId = tab.getId();
- removeTab(tabId);
-
- if (nextTab == null) {
- nextTab = loadUrl(AboutPages.HOME, LOADURL_NEW_TAB);
- }
-
- selectTab(nextTab.getId());
-
- tab.onDestroy();
-
- final JSONObject args = new JSONObject();
- try {
- args.put("tabId", String.valueOf(tabId));
- args.put("showUndoToast", showUndoToast);
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error building Tab:Closed arguments: " + e);
- }
-
- // Pass a message to Gecko to update tab state in BrowserApp
- GeckoAppShell.notifyObservers("Tab:Closed", args.toString());
- }
-
- /** Return the tab that will be selected by default after this one is closed */
- public Tab getNextTab(Tab tab) {
- Tab selectedTab = getSelectedTab();
- if (selectedTab != tab)
- return selectedTab;
-
- boolean getPrivate = tab.isPrivate();
- Tab nextTab = getNextTabFrom(tab, getPrivate);
- if (nextTab == null)
- nextTab = getPreviousTabFrom(tab, getPrivate);
- if (nextTab == null && getPrivate) {
- // If there are no private tabs remaining, get the last normal tab
- Tab lastTab = mOrder.get(mOrder.size() - 1);
- if (!lastTab.isPrivate()) {
- nextTab = lastTab;
- } else {
- nextTab = getPreviousTabFrom(lastTab, false);
- }
- }
-
- Tab parent = getTab(tab.getParentId());
- if (parent != null) {
- // If the next tab is a sibling, switch to it. Otherwise go back to the parent.
- if (nextTab != null && nextTab.getParentId() == tab.getParentId())
- return nextTab;
- else
- return parent;
- }
- return nextTab;
- }
-
- public Iterable<Tab> getTabsInOrder() {
- return mOrder;
- }
-
- /**
- * @return the current GeckoApp instance, or throws if
- * we aren't correctly initialized.
- */
- private synchronized Context getAppContext() {
- if (mAppContext == null) {
- throw new IllegalStateException("Tabs not initialized with a GeckoApp instance.");
- }
- return mAppContext;
- }
-
- public ContentResolver getContentResolver() {
- return getAppContext().getContentResolver();
- }
-
- // Make Tabs a singleton class.
- private static class TabsInstanceHolder {
- private static final Tabs INSTANCE = new Tabs();
- }
-
- @RobocopTarget
- public static Tabs getInstance() {
- return Tabs.TabsInstanceHolder.INSTANCE;
- }
-
- // GeckoEventListener implementation
- @Override
- public void handleMessage(String event, JSONObject message) {
- Log.d(LOGTAG, "handleMessage: " + event);
- try {
- // All other events handled below should contain a tabID property
- int id = message.getInt("tabID");
- Tab tab = getTab(id);
-
- // "Tab:Added" is a special case because tab will be null if the tab was just added
- if (event.equals("Tab:Added")) {
- String url = message.isNull("uri") ? null : message.getString("uri");
-
- if (message.getBoolean("cancelEditMode")) {
- final Tab oldTab = getSelectedTab();
- if (oldTab != null) {
- oldTab.setIsEditing(false);
- }
- }
-
- if (message.getBoolean("stub")) {
- if (tab == null) {
- // Tab was already closed; abort
- return;
- }
- } else {
- tab = addTab(id, url, message.getBoolean("external"),
- message.getInt("parentId"),
- message.getString("title"),
- message.getBoolean("isPrivate"),
- message.getInt("tabIndex"));
- // If we added the tab as a stub, we should have already
- // selected it, so ignore this flag for stubbed tabs.
- if (message.getBoolean("selected"))
- selectTab(id);
- }
-
- if (message.getBoolean("delayLoad"))
- tab.setState(Tab.STATE_DELAYED);
- if (message.getBoolean("desktopMode"))
- tab.setDesktopMode(true);
- return;
- }
-
- // Tab was already closed; abort
- if (tab == null)
- return;
-
- if (event.equals("Tab:Close")) {
- closeTab(tab);
- } else if (event.equals("Tab:Select")) {
- selectTab(tab.getId());
- } else if (event.equals("Content:LocationChange")) {
- tab.handleLocationChange(message);
- } else if (event.equals("Content:SecurityChange")) {
- tab.updateIdentityData(message.getJSONObject("identity"));
- notifyListeners(tab, TabEvents.SECURITY_CHANGE);
- } else if (event.equals("Content:StateChange")) {
- int state = message.getInt("state");
- if ((state & GeckoAppShell.WPL_STATE_IS_NETWORK) != 0) {
- if ((state & GeckoAppShell.WPL_STATE_START) != 0) {
- boolean restoring = message.getBoolean("restoring");
- tab.handleDocumentStart(restoring, message.getString("uri"));
- notifyListeners(tab, Tabs.TabEvents.START);
- } else if ((state & GeckoAppShell.WPL_STATE_STOP) != 0) {
- tab.handleDocumentStop(message.getBoolean("success"));
- notifyListeners(tab, Tabs.TabEvents.STOP);
- }
- }
- } else if (event.equals("Content:LoadError")) {
- tab.handleContentLoaded();
- notifyListeners(tab, Tabs.TabEvents.LOAD_ERROR);
- } else if (event.equals("Content:PageShow")) {
- tab.setLoadedFromCache(message.getBoolean("fromCache"));
- tab.updateUserRequested(message.getString("userRequested"));
- notifyListeners(tab, TabEvents.PAGE_SHOW);
- } else if (event.equals("DOMTitleChanged")) {
- tab.updateTitle(message.getString("title"));
- } else if (event.equals("Link:Favicon")) {
- // Add the favicon to the set of available icons for this tab.
-
- tab.addFavicon(message.getString("href"), message.getInt("size"), message.getString("mime"));
-
- // Load the favicon. If the tab is still loading, we actually do the load once the
- // page has loaded, in an attempt to prevent the favicon load from having a
- // detrimental effect on page load time.
- if (tab.getState() != Tab.STATE_LOADING) {
- tab.loadFavicon();
- }
- } else if (event.equals("Link:Touchicon")) {
- tab.addTouchicon(message.getString("href"), message.getInt("size"), message.getString("mime"));
- } else if (event.equals("Link:Feed")) {
- tab.setHasFeeds(true);
- notifyListeners(tab, TabEvents.LINK_FEED);
- } else if (event.equals("Link:OpenSearch")) {
- boolean visible = message.getBoolean("visible");
- tab.setHasOpenSearch(visible);
- } else if (event.equals("DesktopMode:Changed")) {
- tab.setDesktopMode(message.getBoolean("desktopMode"));
- notifyListeners(tab, TabEvents.DESKTOP_MODE_CHANGE);
- } else if (event.equals("Tab:StreamStart")) {
- tab.setRecording(true);
- notifyListeners(tab, TabEvents.RECORDING_CHANGE);
- } else if (event.equals("Tab:StreamStop")) {
- tab.setRecording(false);
- notifyListeners(tab, TabEvents.RECORDING_CHANGE);
- } else if (event.equals("Tab:AudioPlayingChange")) {
- tab.setIsAudioPlaying(message.getBoolean("isAudioPlaying"));
- notifyListeners(tab, TabEvents.AUDIO_PLAYING_CHANGE);
- } else if (event.equals("Tab:MediaPlaybackChange")) {
- final String status = message.getString("status");
- if (status.equals("resume")) {
- notifyListeners(tab, TabEvents.MEDIA_PLAYING_RESUME);
- } else {
- tab.setIsMediaPlaying(status.equals("start"));
- notifyListeners(tab, TabEvents.MEDIA_PLAYING_CHANGE);
- }
- }
-
- } catch (Exception e) {
- Log.w(LOGTAG, "handleMessage threw for " + event, e);
- }
- }
-
- public void refreshThumbnails() {
- final BrowserDB db = BrowserDB.from(mAppContext);
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- for (final Tab tab : mOrder) {
- if (tab.getThumbnail() == null) {
- tab.loadThumbnailFromDB(db);
- }
- }
- }
- });
- }
-
- public interface OnTabsChangedListener {
- void onTabChanged(Tab tab, TabEvents msg, String data);
- }
-
- private static final List<OnTabsChangedListener> TABS_CHANGED_LISTENERS = new CopyOnWriteArrayList<OnTabsChangedListener>();
-
- public static void registerOnTabsChangedListener(OnTabsChangedListener listener) {
- TABS_CHANGED_LISTENERS.add(listener);
- }
-
- public static void unregisterOnTabsChangedListener(OnTabsChangedListener listener) {
- TABS_CHANGED_LISTENERS.remove(listener);
- }
-
- public enum TabEvents {
- CLOSED,
- START,
- LOADED,
- LOAD_ERROR,
- STOP,
- FAVICON,
- THUMBNAIL,
- TITLE,
- SELECTED,
- UNSELECTED,
- ADDED,
- RESTORED,
- LOCATION_CHANGE,
- MENU_UPDATED,
- PAGE_SHOW,
- LINK_FEED,
- SECURITY_CHANGE,
- DESKTOP_MODE_CHANGE,
- RECORDING_CHANGE,
- BOOKMARK_ADDED,
- BOOKMARK_REMOVED,
- AUDIO_PLAYING_CHANGE,
- OPENED_FROM_TABS_TRAY,
- MEDIA_PLAYING_CHANGE,
- MEDIA_PLAYING_RESUME
- }
-
- public void notifyListeners(Tab tab, TabEvents msg) {
- notifyListeners(tab, msg, "");
- }
-
- public void notifyListeners(final Tab tab, final TabEvents msg, final String data) {
- if (tab == null &&
- msg != TabEvents.RESTORED) {
- throw new IllegalArgumentException("onTabChanged:" + msg + " must specify a tab.");
- }
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- onTabChanged(tab, msg, data);
-
- if (TABS_CHANGED_LISTENERS.isEmpty()) {
- return;
- }
-
- Iterator<OnTabsChangedListener> items = TABS_CHANGED_LISTENERS.iterator();
- while (items.hasNext()) {
- items.next().onTabChanged(tab, msg, data);
- }
- }
- });
- }
-
- private void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
- switch (msg) {
- // We want the tab record to have an accurate favicon, so queue
- // the persisting of tabs when it changes.
- case FAVICON:
- case LOCATION_CHANGE:
- queuePersistAllTabs();
- break;
- case RESTORED:
- mInitialTabsAdded = true;
- break;
-
- // When one tab is deselected, another one is always selected, so only
- // queue a single persist operation. When tabs are added/closed, they
- // are also selected/unselected, so it would be redundant to also listen
- // for ADDED/CLOSED events.
- case SELECTED:
- if (mLayerView != null) {
- mLayerView.setSurfaceBackgroundColor(getTabColor(tab));
- mLayerView.setPaintState(LayerView.PAINT_START);
- }
- queuePersistAllTabs();
- case UNSELECTED:
- tab.onChange();
- break;
- default:
- break;
- }
- }
-
- /**
- * Queues a request to persist tabs after PERSIST_TABS_AFTER_MILLISECONDS
- * milliseconds have elapsed. If any existing requests are already queued then
- * those requests are removed.
- */
- private void queuePersistAllTabs() {
- final Handler backgroundHandler = ThreadUtils.getBackgroundHandler();
-
- // Note: Its safe to modify the runnable here because all of the callers are on the same thread.
- if (mPersistTabsRunnable != null) {
- backgroundHandler.removeCallbacks(mPersistTabsRunnable);
- mPersistTabsRunnable = null;
- }
-
- mPersistTabsRunnable = new PersistTabsRunnable(mAppContext, getTabsInOrder());
- backgroundHandler.postDelayed(mPersistTabsRunnable, PERSIST_TABS_AFTER_MILLISECONDS);
- }
-
- /**
- * Looks for an open tab with the given URL.
- * @param url the URL of the tab we're looking for
- *
- * @return first Tab with the given URL, or null if there is no such tab.
- */
- public Tab getFirstTabForUrl(String url) {
- return getFirstTabForUrlHelper(url, null);
- }
-
- /**
- * Looks for an open tab with the given URL and private state.
- * @param url the URL of the tab we're looking for
- * @param isPrivate if true, only look for tabs that are private. if false,
- * only look for tabs that are non-private.
- *
- * @return first Tab with the given URL, or null if there is no such tab.
- */
- public Tab getFirstTabForUrl(String url, boolean isPrivate) {
- return getFirstTabForUrlHelper(url, isPrivate);
- }
-
- private Tab getFirstTabForUrlHelper(String url, Boolean isPrivate) {
- if (url == null) {
- return null;
- }
-
- for (Tab tab : mOrder) {
- if (isPrivate != null && isPrivate != tab.isPrivate()) {
- continue;
- }
- if (url.equals(tab.getURL())) {
- return tab;
- }
- }
-
- return null;
- }
-
- /**
- * Looks for a reader mode enabled open tab with the given URL and private
- * state.
- *
- * @param url
- * The URL of the tab we're looking for. The url parameter can be
- * the actual article URL or the reader mode article URL.
- * @param isPrivate
- * If true, only look for tabs that are private. If false, only
- * look for tabs that are not private.
- *
- * @return The first Tab with the given URL, or null if there is no such
- * tab.
- */
- public Tab getFirstReaderTabForUrl(String url, boolean isPrivate) {
- if (url == null) {
- return null;
- }
-
- url = ReaderModeUtils.stripAboutReaderUrl(url);
-
- for (Tab tab : mOrder) {
- if (isPrivate != tab.isPrivate()) {
- continue;
- }
- String tabUrl = tab.getURL();
- if (AboutPages.isAboutReader(tabUrl)) {
- tabUrl = ReaderModeUtils.stripAboutReaderUrl(tabUrl);
- if (url.equals(tabUrl)) {
- return tab;
- }
- }
- }
-
- return null;
- }
-
- /**
- * Loads a tab with the given URL in the currently selected tab.
- *
- * @param url URL of page to load, or search term used if searchEngine is given
- */
- @RobocopTarget
- public Tab loadUrl(String url) {
- return loadUrl(url, LOADURL_NONE);
- }
-
- /**
- * Loads a tab with the given URL.
- *
- * @param url URL of page to load, or search term used if searchEngine is given
- * @param flags flags used to load tab
- *
- * @return the Tab if a new one was created; null otherwise
- */
- @RobocopTarget
- public Tab loadUrl(String url, int flags) {
- return loadUrl(url, null, -1, null, flags);
- }
-
- public Tab loadUrlWithIntentExtras(final String url, final SafeIntent intent, final int flags) {
- // We can't directly create a listener to tell when the user taps on the "What's new"
- // notification, so we use this intent handling as a signal that they tapped the notification.
- if (intent.getBooleanExtra(WhatsNewReceiver.EXTRA_WHATSNEW_NOTIFICATION, false)) {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.NOTIFICATION,
- WhatsNewReceiver.EXTRA_WHATSNEW_NOTIFICATION);
- }
-
- // Note: we don't get the URL from the intent so the calling
- // method has the opportunity to change the URL if applicable.
- return loadUrl(url, null, -1, intent, flags);
- }
-
- public Tab loadUrl(final String url, final String searchEngine, final int parentId, final int flags) {
- return loadUrl(url, searchEngine, parentId, null, flags);
- }
-
- /**
- * Loads a tab with the given URL.
- *
- * @param url URL of page to load, or search term used if searchEngine is given
- * @param searchEngine if given, the search engine with this name is used
- * to search for the url string; if null, the URL is loaded directly
- * @param parentId ID of this tab's parent, or -1 if it has no parent
- * @param intent an intent whose extras are used to modify the request
- * @param flags flags used to load tab
- *
- * @return the Tab if a new one was created; null otherwise
- */
- public Tab loadUrl(final String url, final String searchEngine, final int parentId,
- final SafeIntent intent, final int flags) {
- JSONObject args = new JSONObject();
- Tab tabToSelect = null;
- boolean delayLoad = (flags & LOADURL_DELAY_LOAD) != 0;
-
- // delayLoad implies background tab
- boolean background = delayLoad || (flags & LOADURL_BACKGROUND) != 0;
-
- try {
- boolean isPrivate = (flags & LOADURL_PRIVATE) != 0;
- boolean userEntered = (flags & LOADURL_USER_ENTERED) != 0;
- boolean desktopMode = (flags & LOADURL_DESKTOP) != 0;
- boolean external = (flags & LOADURL_EXTERNAL) != 0;
- final boolean isFirstShownAfterActivityUnhidden = (flags & LOADURL_FIRST_AFTER_ACTIVITY_UNHIDDEN) != 0;
-
- args.put("url", url);
- args.put("engine", searchEngine);
- args.put("parentId", parentId);
- args.put("userEntered", userEntered);
- args.put("isPrivate", isPrivate);
- args.put("pinned", (flags & LOADURL_PINNED) != 0);
- args.put("desktopMode", desktopMode);
-
- final boolean needsNewTab;
- final String applicationId = (intent == null) ? null :
- intent.getStringExtra(Browser.EXTRA_APPLICATION_ID);
- if (applicationId == null) {
- needsNewTab = (flags & LOADURL_NEW_TAB) != 0;
- } else {
- // If you modify this code, be careful that intent != null.
- final boolean extraCreateNewTab = intent.getBooleanExtra(Browser.EXTRA_CREATE_NEW_TAB, false);
- final Tab applicationTab = getTabForApplicationId(applicationId);
- if (applicationTab == null || extraCreateNewTab) {
- needsNewTab = true;
- } else {
- needsNewTab = false;
- delayLoad = false;
- background = false;
-
- tabToSelect = applicationTab;
- final int tabToSelectId = tabToSelect.getId();
- args.put("tabID", tabToSelectId);
-
- // This must be called before the "Tab:Load" event is sent. I think addTab gets
- // away with it because having "newTab" == true causes the selected tab to be
- // updated in JS for the "Tab:Load" event but "newTab" is false in our case.
- // This makes me think the other selectTab is not necessary (bug 1160673).
- //
- // Note: that makes the later call redundant but selectTab exits early so I'm
- // fine not adding the complex logic to avoid calling it again.
- selectTab(tabToSelect.getId());
- }
- }
-
- args.put("newTab", needsNewTab);
- args.put("delayLoad", delayLoad);
- args.put("selected", !background);
-
- if (needsNewTab) {
- int tabId = getNextTabId();
- args.put("tabID", tabId);
-
- // The URL is updated for the tab once Gecko responds with the
- // Tab:Added message. We can preliminarily set the tab's URL as
- // long as it's a valid URI.
- String tabUrl = (url != null && Uri.parse(url).getScheme() != null) ? url : null;
-
- // Add the new tab to the end of the tab order.
- final int tabIndex = -1;
-
- tabToSelect = addTab(tabId, tabUrl, external, parentId, url, isPrivate, tabIndex);
- tabToSelect.setDesktopMode(desktopMode);
- tabToSelect.setApplicationId(applicationId);
- if (isFirstShownAfterActivityUnhidden) {
- // We just opened Firefox so we want to show
- // the toolbar but not animate it to avoid jank.
- tabToSelect.setShouldShowToolbarWithoutAnimationOnFirstSelection(true);
- }
- }
- } catch (Exception e) {
- Log.w(LOGTAG, "Error building JSON arguments for loadUrl.", e);
- }
-
- GeckoAppShell.notifyObservers("Tab:Load", args.toString());
-
- if (tabToSelect == null) {
- return null;
- }
-
- if (!delayLoad && !background) {
- selectTab(tabToSelect.getId());
- }
-
- // Load favicon instantly for about:home page because it's already cached
- if (AboutPages.isBuiltinIconPage(url)) {
- tabToSelect.loadFavicon();
- }
-
- return tabToSelect;
- }
-
- public Tab addTab() {
- return loadUrl(AboutPages.HOME, Tabs.LOADURL_NEW_TAB);
- }
-
- public Tab addPrivateTab() {
- return loadUrl(AboutPages.PRIVATEBROWSING, Tabs.LOADURL_NEW_TAB | Tabs.LOADURL_PRIVATE);
- }
-
- /**
- * Open the url as a new tab, and mark the selected tab as its "parent".
- *
- * If the url is already open in a tab, the existing tab is selected.
- * Use this for tabs opened by the browser chrome, so users can press the
- * "Back" button to return to the previous tab.
- *
- * This method will open a new private tab if the currently selected tab
- * is also private.
- *
- * @param url URL of page to load
- */
- public void loadUrlInTab(String url) {
- Iterable<Tab> tabs = getTabsInOrder();
- for (Tab tab : tabs) {
- if (url.equals(tab.getURL())) {
- selectTab(tab.getId());
- return;
- }
- }
-
- // getSelectedTab() can return null if no tab has been created yet
- // (i.e., we're restoring a session after a crash). In these cases,
- // don't mark any tabs as a parent.
- int parentId = -1;
- int flags = LOADURL_NEW_TAB;
-
- final Tab selectedTab = getSelectedTab();
- if (selectedTab != null) {
- parentId = selectedTab.getId();
- if (selectedTab.isPrivate()) {
- flags = flags | LOADURL_PRIVATE;
- }
- }
-
- loadUrl(url, null, parentId, flags);
- }
-
- /**
- * Gets the next tab ID.
- */
- @JNITarget
- public static int getNextTabId() {
- return sTabId.getAndIncrement();
- }
-
- private int getTabColor(Tab tab) {
- if (tab != null) {
- return tab.isPrivate() ? mPrivateClearColor : Color.WHITE;
- }
-
- return Color.WHITE;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/Telemetry.java b/mobile/android/base/java/org/mozilla/gecko/Telemetry.java
deleted file mode 100644
index 342445bf2..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/Telemetry.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.annotation.WrapForJNI;
-import org.mozilla.gecko.TelemetryContract.Event;
-import org.mozilla.gecko.TelemetryContract.Method;
-import org.mozilla.gecko.TelemetryContract.Reason;
-import org.mozilla.gecko.TelemetryContract.Session;
-
-import android.os.SystemClock;
-import android.util.Log;
-
-/**
- * All telemetry times are relative to one of two clocks:
- *
- * * Real time since the device was booted, including deep sleep. Use this
- * as a substitute for wall clock.
- * * Uptime since the device was booted, excluding deep sleep. Use this to
- * avoid timing a user activity when their phone is in their pocket!
- *
- * The majority of methods in this class are defined in terms of real time.
- */
-@RobocopTarget
-public class Telemetry {
- private static final String LOGTAG = "Telemetry";
-
- @WrapForJNI(stubName = "AddHistogram", dispatchTo = "gecko")
- private static native void nativeAddHistogram(String name, int value);
- @WrapForJNI(stubName = "AddKeyedHistogram", dispatchTo = "gecko")
- private static native void nativeAddKeyedHistogram(String name, String key, int value);
- @WrapForJNI(stubName = "StartUISession", dispatchTo = "gecko")
- private static native void nativeStartUiSession(String name, long timestamp);
- @WrapForJNI(stubName = "StopUISession", dispatchTo = "gecko")
- private static native void nativeStopUiSession(String name, String reason, long timestamp);
- @WrapForJNI(stubName = "AddUIEvent", dispatchTo = "gecko")
- private static native void nativeAddUiEvent(String action, String method,
- long timestamp, String extras);
-
- public static long uptime() {
- return SystemClock.uptimeMillis();
- }
-
- public static long realtime() {
- return SystemClock.elapsedRealtime();
- }
-
- // Define new histograms in:
- // toolkit/components/telemetry/Histograms.json
- public static void addToHistogram(String name, int value) {
- if (GeckoThread.isRunning()) {
- nativeAddHistogram(name, value);
- } else {
- GeckoThread.queueNativeCall(Telemetry.class, "nativeAddHistogram",
- String.class, name, value);
- }
- }
-
- public static void addToKeyedHistogram(String name, String key, int value) {
- if (GeckoThread.isRunning()) {
- nativeAddKeyedHistogram(name, key, value);
- } else {
- GeckoThread.queueNativeCall(Telemetry.class, "nativeAddKeyedHistogram",
- String.class, name, String.class, key, value);
- }
- }
-
- public abstract static class Timer {
- private final long mStartTime;
- private final String mName;
-
- private volatile boolean mHasFinished;
- private volatile long mElapsed = -1;
-
- protected abstract long now();
-
- public Timer(String name) {
- mName = name;
- mStartTime = now();
- }
-
- public void cancel() {
- mHasFinished = true;
- }
-
- public long getElapsed() {
- return mElapsed;
- }
-
- public void stop() {
- // Only the first stop counts.
- if (mHasFinished) {
- return;
- }
-
- mHasFinished = true;
-
- final long elapsed = now() - mStartTime;
- if (elapsed < 0) {
- Log.e(LOGTAG, "Current time less than start time -- clock shenanigans?");
- return;
- }
-
- mElapsed = elapsed;
- if (elapsed > Integer.MAX_VALUE) {
- Log.e(LOGTAG, "Duration of " + elapsed + "ms is too great to add to histogram.");
- return;
- }
-
- addToHistogram(mName, (int) (elapsed));
- }
- }
-
- public static class RealtimeTimer extends Timer {
- public RealtimeTimer(String name) {
- super(name);
- }
-
- @Override
- protected long now() {
- return Telemetry.realtime();
- }
- }
-
- public static class UptimeTimer extends Timer {
- public UptimeTimer(String name) {
- super(name);
- }
-
- @Override
- protected long now() {
- return Telemetry.uptime();
- }
- }
-
- public static void startUISession(final Session session, final String sessionNameSuffix) {
- final String sessionName = getSessionName(session, sessionNameSuffix);
-
- Log.d(LOGTAG, "StartUISession: " + sessionName);
- if (GeckoThread.isRunning()) {
- nativeStartUiSession(sessionName, realtime());
- } else {
- GeckoThread.queueNativeCall(Telemetry.class, "nativeStartUiSession",
- String.class, sessionName, realtime());
- }
- }
-
- public static void startUISession(final Session session) {
- startUISession(session, null);
- }
-
- public static void stopUISession(final Session session, final String sessionNameSuffix,
- final Reason reason) {
- final String sessionName = getSessionName(session, sessionNameSuffix);
-
- Log.d(LOGTAG, "StopUISession: " + sessionName + ", reason=" + reason);
- if (GeckoThread.isRunning()) {
- nativeStopUiSession(sessionName, reason.toString(), realtime());
- } else {
- GeckoThread.queueNativeCall(Telemetry.class, "nativeStopUiSession",
- String.class, sessionName,
- String.class, reason.toString(), realtime());
- }
- }
-
- public static void stopUISession(final Session session, final Reason reason) {
- stopUISession(session, null, reason);
- }
-
- public static void stopUISession(final Session session, final String sessionNameSuffix) {
- stopUISession(session, sessionNameSuffix, Reason.NONE);
- }
-
- public static void stopUISession(final Session session) {
- stopUISession(session, null, Reason.NONE);
- }
-
- private static String getSessionName(final Session session, final String sessionNameSuffix) {
- if (sessionNameSuffix != null) {
- return session.toString() + ":" + sessionNameSuffix;
- } else {
- return session.toString();
- }
- }
-
- /**
- * @param method A non-null method (if null is desired, consider using Method.NONE)
- */
- private static void sendUIEvent(final String eventName, final Method method,
- final long timestamp, final String extras) {
- if (method == null) {
- throw new IllegalArgumentException("Expected non-null method - use Method.NONE?");
- }
-
- if (!AppConstants.RELEASE_OR_BETA) {
- final String logString = "SendUIEvent: event = " + eventName + " method = " + method + " timestamp = " +
- timestamp + " extras = " + extras;
- Log.d(LOGTAG, logString);
- }
- if (GeckoThread.isRunning()) {
- nativeAddUiEvent(eventName, method.toString(), timestamp, extras);
- } else {
- GeckoThread.queueNativeCall(Telemetry.class, "nativeAddUiEvent",
- String.class, eventName, String.class, method.toString(),
- timestamp, String.class, extras);
- }
- }
-
- public static void sendUIEvent(final Event event, final Method method, final long timestamp,
- final String extras) {
- sendUIEvent(event.toString(), method, timestamp, extras);
- }
-
- public static void sendUIEvent(final Event event, final Method method, final long timestamp) {
- sendUIEvent(event, method, timestamp, null);
- }
-
- public static void sendUIEvent(final Event event, final Method method, final String extras) {
- sendUIEvent(event, method, realtime(), extras);
- }
-
- public static void sendUIEvent(final Event event, final Method method) {
- sendUIEvent(event, method, realtime(), null);
- }
-
- public static void sendUIEvent(final Event event) {
- sendUIEvent(event, Method.NONE, realtime(), null);
- }
-
- /**
- * Sends a UIEvent with the given status appended to the event name.
- *
- * This method is a slight bend of the Telemetry framework so chances
- * are that you don't want to use this: please think really hard before you do.
- *
- * Intended for use with data policy notifications.
- */
- public static void sendUIEvent(final Event event, final boolean eventStatus) {
- final String eventName = event + ":" + eventStatus;
- sendUIEvent(eventName, Method.NONE, realtime(), null);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/TelemetryContract.java b/mobile/android/base/java/org/mozilla/gecko/TelemetryContract.java
deleted file mode 100644
index 0c2051a9d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/TelemetryContract.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.annotation.RobocopTarget;
-
-/**
- * Holds data definitions for our UI Telemetry implementation.
- *
- * Note that enum values of "_TEST*" are reserved for testing and
- * should not be changed without changing the associated tests.
- *
- * See mobile/android/base/docs/index.rst for a full dictionary.
- */
-@RobocopTarget
-public interface TelemetryContract {
-
- /**
- * Holds event names. Intended for use with
- * Telemetry.sendUIEvent() as the "action" parameter.
- *
- * Please keep this list sorted.
- */
- public enum Event {
- // Generic action, usually for tracking menu and toolbar actions.
- ACTION("action.1"),
-
- // Cancel a state, action, etc.
- CANCEL("cancel.1"),
-
- // Start casting a video.
- // Note: Only used in JavaScript for now, but here for completeness.
- CAST("cast.1"),
-
- // Editing an item.
- EDIT("edit.1"),
-
- // Launching (opening) an external application.
- // Note: Only used in JavaScript for now, but here for completeness.
- LAUNCH("launch.1"),
-
- // Loading a URL.
- LOAD_URL("loadurl.1"),
-
- LOCALE_BROWSER_RESET("locale.browser.reset.1"),
- LOCALE_BROWSER_SELECTED("locale.browser.selected.1"),
- LOCALE_BROWSER_UNSELECTED("locale.browser.unselected.1"),
-
- // Hide a built-in home panel.
- PANEL_HIDE("panel.hide.1"),
-
- // Move a home panel up or down.
- PANEL_MOVE("panel.move.1"),
-
- // Remove a custom home panel.
- PANEL_REMOVE("panel.remove.1"),
-
- // Set default home panel.
- PANEL_SET_DEFAULT("panel.setdefault.1"),
-
- // Show a hidden built-in home panel.
- PANEL_SHOW("panel.show.1"),
-
- // Pinning an item.
- PIN("pin.1"),
-
- // Outcome of data policy notification: can be true or false.
- POLICY_NOTIFICATION_SUCCESS("policynotification.success.1"),
-
- // Sanitizing private data.
- SANITIZE("sanitize.1"),
-
- // Saving a resource (reader, bookmark, etc) for viewing later.
- SAVE("save.1"),
-
- // Perform a search -- currently used when starting a search in the search activity.
- SEARCH("search.1"),
-
- // Remove a search engine.
- SEARCH_REMOVE("search.remove.1"),
-
- // Restore default search engines.
- SEARCH_RESTORE_DEFAULTS("search.restoredefaults.1"),
-
- // Set default search engine.
- SEARCH_SET_DEFAULT("search.setdefault.1"),
-
- // Sharing content.
- SHARE("share.1"),
-
- // Show a UI element.
- SHOW("show.1"),
-
- // Undoing a user action.
- // Note: Only used in JavaScript for now, but here for completeness.
- UNDO("undo.1"),
-
- // Unpinning an item.
- UNPIN("unpin.1"),
-
- // Stop holding a resource (reader, bookmark, etc) for viewing later.
- UNSAVE("unsave.1"),
-
- // When the user performs actions on the in-content network error page.
- NETERROR("neterror.1"),
-
- // VALUES BELOW THIS LINE ARE EXCLUSIVE TO TESTING.
- _TEST1("_test_event_1.1"),
- _TEST2("_test_event_2.1"),
- _TEST3("_test_event_3.1"),
- _TEST4("_test_event_4.1"),
- ;
-
- private final String string;
-
- Event(final String string) {
- this.string = string;
- }
-
- @Override
- public String toString() {
- return string;
- }
- }
-
- /**
- * Holds event methods. Intended for use in
- * Telemetry.sendUIEvent() as the "method" parameter.
- *
- * Please keep this list sorted.
- */
- public enum Method {
- // Action triggered from the action bar (including the toolbar).
- ACTIONBAR("actionbar"),
-
- // Action triggered by hitting the Android back button.
- BACK("back"),
-
- // Action triggered from a button.
- BUTTON("button"),
-
- // Action taken from a content page -- for example, a search results web page.
- CONTENT("content"),
-
- // Action occurred via a context menu.
- CONTEXT_MENU("contextmenu"),
-
- // Action triggered from a dialog.
- DIALOG("dialog"),
-
- // Action triggered from a doorhanger popup prompt.
- DOORHANGER("doorhanger"),
-
- // Action triggered from a view grid item, like a thumbnail.
- GRID_ITEM("griditem"),
-
- // Action occurred via an intent.
- INTENT("intent"),
-
- // Action occurred via a homescreen launcher.
- HOMESCREEN("homescreen"),
-
- // Action triggered from a list.
- LIST("list"),
-
- // Action triggered from a view list item, like a row of a list.
- LIST_ITEM("listitem"),
-
- // Action occurred via the main menu.
- MENU("menu"),
-
- // No method is specified.
- NONE(null),
-
- // Action triggered from a notification in the Android notification bar.
- NOTIFICATION("notification"),
-
- // Action triggered from a pageaction in the URLBar.
- // Note: Only used in JavaScript for now, but here for completeness.
- PAGEACTION("pageaction"),
-
- // Action triggered from one of a series of views, such as ViewPager.
- PANEL("panel"),
-
- // Action triggered by a background service / automatic system making a decision.
- SERVICE("service"),
-
- // Action triggered from a settings screen.
- SETTINGS("settings"),
-
- // Actions triggered from the share overlay.
- SHARE_OVERLAY("shareoverlay"),
-
- // Action triggered from a suggestion provided to the user.
- SUGGESTION("suggestion"),
-
- // Action triggered from an OS system action.
- SYSTEM("system"),
-
- // Action triggered from a SuperToast.
- // Note: Only used in JavaScript for now, but here for completeness.
- TOAST("toast"),
-
- // Action triggerred by pressing a SearchWidget button
- WIDGET("widget"),
-
- // VALUES BELOW THIS LINE ARE EXCLUSIVE TO TESTING.
- _TEST1("_test_method_1"),
- _TEST2("_test_method_2"),
- ;
-
- private final String string;
-
- Method(final String string) {
- this.string = string;
- }
-
- @Override
- public String toString() {
- return string;
- }
- }
-
- /**
- * Holds session names. Intended for use with
- * Telemetry.startUISession() as the "sessionName" parameter.
- *
- * Please keep this list sorted.
- */
- public enum Session {
- // Awesomescreen (including frecency search) is active.
- AWESOMESCREEN("awesomescreen.1"),
-
- // Used to tag experiments being run.
- EXPERIMENT("experiment.1"),
-
- // Started the very first time we believe the application has been launched.
- FIRSTRUN("firstrun.1"),
-
- // Awesomescreen frecency search is active.
- FRECENCY("frecency.1"),
-
- // Started when a user enters a given home panel.
- // Session name is dynamic, encoded as "homepanel.1:<panel_id>"
- HOME_PANEL("homepanel.1"),
-
- // Started when a Reader viewer becomes active in the foreground.
- // Note: Only used in JavaScript for now, but here for completeness.
- READER("reader.1"),
-
- // Started when the search activity launches.
- SEARCH_ACTIVITY("searchactivity.1"),
-
- // Settings activity is active.
- SETTINGS("settings.1"),
-
- // VALUES BELOW THIS LINE ARE EXCLUSIVE TO TESTING.
- _TEST_STARTED_TWICE("_test_session_started_twice.1"),
- _TEST_STOPPED_TWICE("_test_session_stopped_twice.1"),
- ;
-
- private final String string;
-
- Session(final String string) {
- this.string = string;
- }
-
- @Override
- public String toString() {
- return string;
- }
- }
-
- /**
- * Holds reasons for stopping a session. Intended for use in
- * Telemetry.stopUISession() as the "reason" parameter.
- *
- * Please keep this list sorted.
- */
- public enum Reason {
- // Changes were committed.
- COMMIT("commit"),
-
- // No reason is specified.
- NONE(null),
-
- // VALUES BELOW THIS LINE ARE EXCLUSIVE TO TESTING.
- _TEST1("_test_reason_1"),
- _TEST2("_test_reason_2"),
- _TEST_IGNORED("_test_reason_ignored"),
- ;
-
- private final String string;
-
- Reason(final String string) {
- this.string = string;
- }
-
- @Override
- public String toString() {
- return string;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/ThumbnailHelper.java b/mobile/android/base/java/org/mozilla/gecko/ThumbnailHelper.java
deleted file mode 100644
index 3a7012431..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/ThumbnailHelper.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.annotation.WrapForJNI;
-import org.mozilla.gecko.gfx.BitmapUtils;
-import org.mozilla.gecko.util.ResourceDrawableUtils;
-import org.mozilla.gecko.mozglue.DirectBufferAllocator;
-
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.util.Log;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-
-/**
- * Helper class to generate thumbnails for tabs.
- * Internally, a queue of pending thumbnails is maintained in mPendingThumbnails.
- * The head of the queue is the thumbnail that is currently being processed; upon
- * completion of the current thumbnail the next one is automatically processed.
- * Changes to the thumbnail width are stashed in mPendingWidth and the change is
- * applied between thumbnail processing. This allows a single thumbnail buffer to
- * be used for all thumbnails.
- */
-public final class ThumbnailHelper {
- private static final boolean DEBUG = false;
- private static final String LOGTAG = "GeckoThumbnailHelper";
-
- public static final float TABS_PANEL_THUMBNAIL_ASPECT_RATIO = 0.8333333f;
- public static final float TOP_SITES_THUMBNAIL_ASPECT_RATIO = 0.571428571f; // this is a 4:7 ratio (as per UX decision)
- public static final float THUMBNAIL_ASPECT_RATIO;
-
- static {
- // As we only want to generate one thumbnail for each tab, we calculate the
- // largest aspect ratio required and create the thumbnail based off that.
- // Any views with a smaller aspect ratio will use a cropped version of the
- // same image.
- THUMBNAIL_ASPECT_RATIO = Math.max(TABS_PANEL_THUMBNAIL_ASPECT_RATIO, TOP_SITES_THUMBNAIL_ASPECT_RATIO);
- }
-
- public enum CachePolicy {
- STORE,
- NO_STORE
- }
-
- // static singleton stuff
-
- private static ThumbnailHelper sInstance;
-
- public static synchronized ThumbnailHelper getInstance() {
- if (sInstance == null) {
- sInstance = new ThumbnailHelper();
- }
- return sInstance;
- }
-
- // instance stuff
-
- private final ArrayList<Tab> mPendingThumbnails; // synchronized access only
- private volatile int mPendingWidth;
- private int mWidth;
- private int mHeight;
- private ByteBuffer mBuffer;
-
- private ThumbnailHelper() {
- final Resources res = GeckoAppShell.getContext().getResources();
-
- mPendingThumbnails = new ArrayList<>();
- try {
- mPendingWidth = (int) res.getDimension(R.dimen.tab_thumbnail_width);
- } catch (Resources.NotFoundException nfe) {
- }
- mWidth = -1;
- mHeight = -1;
- }
-
- public void getAndProcessThumbnailFor(final int tabId, final ResourceDrawableUtils.BitmapLoader loader) {
- final Tab tab = Tabs.getInstance().getTab(tabId);
- if (tab != null) {
- getAndProcessThumbnailFor(tab, loader);
- }
- }
-
- public void getAndProcessThumbnailFor(final Tab tab, final ResourceDrawableUtils.BitmapLoader loader) {
- ResourceDrawableUtils.runOnBitmapFoundOnUiThread(loader, tab.getThumbnail());
-
- Tabs.registerOnTabsChangedListener(new Tabs.OnTabsChangedListener() {
- @Override
- public void onTabChanged(final Tab t, final Tabs.TabEvents msg, final String data) {
- if (tab != t || msg != Tabs.TabEvents.THUMBNAIL) {
- return;
- }
- Tabs.unregisterOnTabsChangedListener(this);
- ResourceDrawableUtils.runOnBitmapFoundOnUiThread(loader, t.getThumbnail());
- }
- });
- getAndProcessThumbnailFor(tab);
- }
-
- public void getAndProcessThumbnailFor(Tab tab) {
- if (AboutPages.isAboutHome(tab.getURL()) || AboutPages.isAboutPrivateBrowsing(tab.getURL())) {
- tab.updateThumbnail(null, CachePolicy.NO_STORE);
- return;
- }
-
- synchronized (mPendingThumbnails) {
- if (mPendingThumbnails.lastIndexOf(tab) > 0) {
- // This tab is already in the queue, so don't add it again.
- // Note that if this tab is only at the *head* of the queue,
- // (i.e. mPendingThumbnails.lastIndexOf(tab) == 0) then we do
- // add it again because it may have already been thumbnailed
- // and now we need to do it again.
- return;
- }
-
- mPendingThumbnails.add(tab);
- if (mPendingThumbnails.size() > 1) {
- // Some thumbnail was already being processed, so wait
- // for that to be done.
- return;
- }
-
- requestThumbnailLocked(tab);
- }
- }
-
- public void setThumbnailWidth(int width) {
- // Check inverted for safety: Bug 803299 Comment 34.
- if (GeckoAppShell.getScreenDepth() == 24) {
- mPendingWidth = width;
- } else {
- // Bug 776906: on 16-bit screens we need to ensure an even width.
- mPendingWidth = (width + 1) & (~1);
- }
- }
-
- private void updateThumbnailSizeLocked() {
- // Apply any pending width updates.
- mWidth = mPendingWidth;
- mHeight = Math.round(mWidth * THUMBNAIL_ASPECT_RATIO);
-
- int pixelSize = (GeckoAppShell.getScreenDepth() == 24) ? 4 : 2;
- int capacity = mWidth * mHeight * pixelSize;
- if (DEBUG) {
- Log.d(LOGTAG, "Using new thumbnail size: " + capacity +
- " (width " + mWidth + " - height " + mHeight + ")");
- }
- if (mBuffer == null || mBuffer.capacity() != capacity) {
- if (mBuffer != null) {
- mBuffer = DirectBufferAllocator.free(mBuffer);
- }
- try {
- mBuffer = DirectBufferAllocator.allocate(capacity);
- } catch (IllegalArgumentException iae) {
- Log.w(LOGTAG, iae.toString());
- } catch (OutOfMemoryError oom) {
- Log.w(LOGTAG, "Unable to allocate thumbnail buffer of capacity " + capacity);
- }
- // If we hit an error above, mBuffer will be pointing to null, so we are in a sane state.
- }
- }
-
- private void requestThumbnailLocked(Tab tab) {
- updateThumbnailSizeLocked();
-
- if (mBuffer == null) {
- // Buffer allocation may have failed. In this case we can't send the
- // event requesting the screenshot which means we won't get back a response
- // and so our queue will grow unboundedly. Handle this scenario by clearing
- // the queue (no point trying more thumbnailing right now since we're likely
- // low on memory). We will try again normally on the next call to
- // getAndProcessThumbnailFor which will hopefully be when we have more free memory.
- mPendingThumbnails.clear();
- return;
- }
-
- if (DEBUG) {
- Log.d(LOGTAG, "Sending thumbnail event: " + mWidth + ", " + mHeight);
- }
- requestThumbnailLocked(mBuffer, tab, tab.getId(), mWidth, mHeight);
- }
-
- @WrapForJNI(stubName = "RequestThumbnail", dispatchTo = "proxy")
- private static native void requestThumbnailLocked(ByteBuffer data, Tab tab, int tabId,
- int width, int height);
-
- /* This method is invoked by JNI once the thumbnail data is ready. */
- @WrapForJNI(calledFrom = "gecko")
- private static void notifyThumbnail(final ByteBuffer data, final Tab tab,
- final boolean success, final boolean shouldStore) {
- final ThumbnailHelper helper = ThumbnailHelper.getInstance();
- if (success) {
- helper.handleThumbnailData(
- tab, data, shouldStore ? CachePolicy.STORE : CachePolicy.NO_STORE);
- }
- helper.processNextThumbnail();
- }
-
- private void processNextThumbnail() {
- synchronized (mPendingThumbnails) {
- if (mPendingThumbnails.isEmpty()) {
- return;
- }
-
- mPendingThumbnails.remove(0);
-
- if (!mPendingThumbnails.isEmpty()) {
- requestThumbnailLocked(mPendingThumbnails.get(0));
- }
- }
- }
-
- private void handleThumbnailData(Tab tab, ByteBuffer data, CachePolicy cachePolicy) {
- if (DEBUG) {
- Log.d(LOGTAG, "handleThumbnailData: " + data.capacity());
- }
- if (data != mBuffer) {
- // This should never happen, but log it and recover gracefully
- Log.e(LOGTAG, "handleThumbnailData called with an unexpected ByteBuffer!");
- }
-
- processThumbnailData(tab, data, cachePolicy);
- }
-
- private void processThumbnailData(Tab tab, ByteBuffer data, CachePolicy cachePolicy) {
- Bitmap b = tab.getThumbnailBitmap(mWidth, mHeight);
- data.position(0);
- b.copyPixelsFromBuffer(data);
- setTabThumbnail(tab, b, null, cachePolicy);
- }
-
- private void setTabThumbnail(Tab tab, Bitmap bitmap, byte[] compressed, CachePolicy cachePolicy) {
- if (bitmap == null) {
- if (compressed == null) {
- Log.w(LOGTAG, "setTabThumbnail: one of bitmap or compressed must be non-null!");
- return;
- }
- bitmap = BitmapUtils.decodeByteArray(compressed);
- }
- tab.updateThumbnail(bitmap, cachePolicy);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/ZoomedView.java b/mobile/android/base/java/org/mozilla/gecko/ZoomedView.java
deleted file mode 100644
index c0c9307dc..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/ZoomedView.java
+++ /dev/null
@@ -1,838 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko;
-
-import org.mozilla.gecko.animation.ViewHelper;
-import org.mozilla.gecko.annotation.WrapForJNI;
-import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
-import org.mozilla.gecko.gfx.LayerView;
-import org.mozilla.gecko.gfx.PanZoomController;
-import org.mozilla.gecko.gfx.PointUtils;
-import org.mozilla.gecko.mozglue.DirectBufferAllocator;
-import org.mozilla.gecko.PrefsHelper;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.Configuration;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.RectF;
-import android.graphics.Shader;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.animation.Animation;
-import android.view.animation.Animation.AnimationListener;
-import android.view.animation.OvershootInterpolator;
-import android.view.animation.ScaleAnimation;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import java.nio.ByteBuffer;
-import java.text.DecimalFormat;
-
-public class ZoomedView extends FrameLayout implements LayerView.DynamicToolbarListener,
- LayerView.ZoomedViewListener, GeckoEventListener {
- private static final String LOGTAG = "Gecko" + ZoomedView.class.getSimpleName();
-
- private static final float[] ZOOM_FACTORS_LIST = {2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 1.5f};
- private static final int W_CAPTURED_VIEW_IN_PERCENT = 50;
- private static final int H_CAPTURED_VIEW_IN_PERCENT = 50;
- private static final int MINIMUM_DELAY_BETWEEN_TWO_RENDER_CALLS_NS = 1000000;
- private static final int DELAY_BEFORE_NEXT_RENDER_REQUEST_MS = 2000;
- private static final int OPENING_ANIMATION_DURATION_MS = 250;
- private static final int CLOSING_ANIMATION_DURATION_MS = 150;
- private static final float OVERSHOOT_INTERPOLATOR_TENSION = 1.5f;
-
- private float zoomFactor;
- private int currentZoomFactorIndex;
- private boolean isSimplifiedUI;
- private int defaultZoomFactor;
- private PrefsHelper.PrefHandler prefObserver;
-
- private ImageView zoomedImageView;
- private LayerView layerView;
- private int viewWidth;
- private int viewHeight; // Only the zoomed view height, no toolbar, no shadow ...
- private int viewContainerWidth;
- private int viewContainerHeight; // Zoomed view height with toolbar and other elements like shadow, ...
- private int containterSize; // shadow, margin, ...
- private Point lastPosition;
- private boolean shouldSetVisibleOnUpdate;
- private boolean isBlockedFromAppearing; // Prevent the display of the zoomedview while FormAssistantPopup is visible
- private PointF returnValue;
- private final PointF animationStart;
- private ImageView closeButton;
- private TextView changeZoomFactorButton;
- private boolean toolbarOnTop;
- private float offsetDueToToolBarPosition;
- private int toolbarHeight;
- private int cornerRadius;
- private float dynamicToolbarOverlap;
-
- private boolean stopUpdateView;
-
- private int lastOrientation;
-
- private ByteBuffer buffer;
- private Runnable requestRenderRunnable;
- private long startTimeReRender;
- private long lastStartTimeReRender;
-
- private ZoomedViewTouchListener touchListener;
-
- private enum StartPointUpdate {
- GECKO_POSITION, CENTER, NO_CHANGE
- }
-
- private class RoundedBitmapDrawable extends BitmapDrawable {
- private Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
- final float cornerRadius;
- final boolean squareOnTopOfDrawable;
-
- RoundedBitmapDrawable(Resources res, Bitmap bitmap, boolean squareOnTop, int radius) {
- super(res, bitmap);
- squareOnTopOfDrawable = squareOnTop;
- final BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP,
- Shader.TileMode.CLAMP);
- paint.setAntiAlias(true);
- paint.setShader(shader);
- cornerRadius = radius;
- }
-
- @Override
- public void draw(Canvas canvas) {
- int height = getBounds().height();
- int width = getBounds().width();
- RectF rect = new RectF(0.0f, 0.0f, width, height);
- canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint);
-
- //draw rectangles over the corners we want to be square
- if (squareOnTopOfDrawable) {
- canvas.drawRect(0, 0, cornerRadius, cornerRadius, paint);
- canvas.drawRect(width - cornerRadius, 0, width, cornerRadius, paint);
- } else {
- canvas.drawRect(0, height - cornerRadius, cornerRadius, height, paint);
- canvas.drawRect(width - cornerRadius, height - cornerRadius, width, height, paint);
- }
- }
- }
-
- private class ZoomedViewTouchListener implements View.OnTouchListener {
- private float originRawX;
- private float originRawY;
- private boolean dragged;
- private MotionEvent actionDownEvent;
-
- @Override
- public boolean onTouch(View view, MotionEvent event) {
- if (layerView == null) {
- return false;
- }
-
- switch (event.getAction()) {
- case MotionEvent.ACTION_MOVE:
- if (moveZoomedView(event)) {
- dragged = true;
- }
- break;
-
- case MotionEvent.ACTION_UP:
- if (dragged) {
- dragged = false;
- } else {
- if (isClickInZoomedView(event.getY())) {
- GeckoAppShell.notifyObservers("Gesture:ClickInZoomedView", "");
- layerView.dispatchTouchEvent(actionDownEvent);
- actionDownEvent.recycle();
- PointF convertedPosition = getUnzoomedPositionFromPointInZoomedView(event.getX(), event.getY());
- // the LayerView expects the coordinates relative to the window, not the surface, so we need
- // to adjust that here.
- convertedPosition.y += layerView.getSurfaceTranslation();
- MotionEvent e = MotionEvent.obtain(event.getDownTime(), event.getEventTime(),
- MotionEvent.ACTION_UP, convertedPosition.x, convertedPosition.y,
- event.getMetaState());
- layerView.dispatchTouchEvent(e);
- e.recycle();
- }
- }
- break;
-
- case MotionEvent.ACTION_DOWN:
- dragged = false;
- originRawX = event.getRawX();
- originRawY = event.getRawY();
- PointF convertedPosition = getUnzoomedPositionFromPointInZoomedView(event.getX(), event.getY());
- // the LayerView expects the coordinates relative to the window, not the surface, so we need
- // to adjust that here.
- convertedPosition.y += layerView.getSurfaceTranslation();
- actionDownEvent = MotionEvent.obtain(event.getDownTime(), event.getEventTime(),
- MotionEvent.ACTION_DOWN, convertedPosition.x, convertedPosition.y,
- event.getMetaState());
- break;
- }
- return true;
- }
-
- private boolean isClickInZoomedView(float y) {
- return ((toolbarOnTop && y > toolbarHeight) ||
- (!toolbarOnTop && y < ZoomedView.this.viewHeight));
- }
-
- private boolean moveZoomedView(MotionEvent event) {
- RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) ZoomedView.this.getLayoutParams();
- if ((!dragged) && (Math.abs((int) (event.getRawX() - originRawX)) < PanZoomController.CLICK_THRESHOLD)
- && (Math.abs((int) (event.getRawY() - originRawY)) < PanZoomController.CLICK_THRESHOLD)) {
- // When the user just touches the screen ACTION_MOVE can be detected for a very small delta on position.
- // In this case, the move is ignored if the delta is lower than 1 unit.
- return false;
- }
-
- float newLeftMargin = params.leftMargin + event.getRawX() - originRawX;
- float newTopMargin = params.topMargin + event.getRawY() - originRawY;
- ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
- ZoomedView.this.moveZoomedView(metrics, newLeftMargin, newTopMargin, StartPointUpdate.CENTER);
- originRawX = event.getRawX();
- originRawY = event.getRawY();
- return true;
- }
- }
-
- public ZoomedView(Context context) {
- this(context, null, 0);
- }
-
- public ZoomedView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ZoomedView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- isSimplifiedUI = true;
- isBlockedFromAppearing = false;
- getPrefs();
- currentZoomFactorIndex = 0;
- returnValue = new PointF();
- animationStart = new PointF();
- requestRenderRunnable = new Runnable() {
- @Override
- public void run() {
- requestZoomedViewRender();
- }
- };
- touchListener = new ZoomedViewTouchListener();
- GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
- "Gesture:clusteredLinksClicked", "Window:Resize", "Content:LocationChange",
- "Gesture:CloseZoomedView", "Browser:ZoomToPageWidth", "Browser:ZoomToRect",
- "FormAssist:AutoComplete", "FormAssist:Hide");
- }
-
- void destroy() {
- if (prefObserver != null) {
- PrefsHelper.removeObserver(prefObserver);
- prefObserver = null;
- }
- ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
- "Gesture:clusteredLinksClicked", "Window:Resize", "Content:LocationChange",
- "Gesture:CloseZoomedView", "Browser:ZoomToPageWidth", "Browser:ZoomToRect",
- "FormAssist:AutoComplete", "FormAssist:Hide");
- }
-
- // This method (onFinishInflate) is called only when the zoomed view class is used inside
- // an xml structure <org.mozilla.gecko.ZoomedView ...
- // It won't be called if the class is used from java code like "new ZoomedView(context);"
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- closeButton = (ImageView) findViewById(R.id.dialog_close);
- changeZoomFactorButton = (TextView) findViewById(R.id.change_zoom_factor);
- zoomedImageView = (ImageView) findViewById(R.id.zoomed_image_view);
-
- updateUI();
-
- toolbarHeight = getResources().getDimensionPixelSize(R.dimen.zoomed_view_toolbar_height);
- containterSize = getResources().getDimensionPixelSize(R.dimen.drawable_dropshadow_size);
- cornerRadius = getResources().getDimensionPixelSize(R.dimen.standard_corner_radius);
-
- moveToolbar(true);
- }
-
- private void setListeners() {
- closeButton.setOnClickListener(new View.OnClickListener() {
- public void onClick(View view) {
- stopZoomDisplay(true);
- }
- });
-
- changeZoomFactorButton.setOnTouchListener(new OnTouchListener() {
- public boolean onTouch(View v, MotionEvent event) {
-
- if (event.getAction() == MotionEvent.ACTION_UP) {
- if (event.getX() >= (changeZoomFactorButton.getLeft() + changeZoomFactorButton.getWidth() / 2)) {
- changeZoomFactor(true);
- } else {
- changeZoomFactor(false);
- }
- }
- return true;
- }
- });
-
- setOnTouchListener(touchListener);
- }
-
- private void removeListeners() {
- closeButton.setOnClickListener(null);
-
- changeZoomFactorButton.setOnTouchListener(null);
-
- setOnTouchListener(null);
- }
- /*
- * Convert a click from ZoomedView. Return the position of the click in the
- * LayerView
- */
- private PointF getUnzoomedPositionFromPointInZoomedView(float x, float y) {
- ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
- final float parentWidth = metrics.getWidth();
- final float parentHeight = metrics.getHeight();
- RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams();
-
- // The number of unzoomed content pixels that can be displayed in the
- // zoomed area.
- float visibleContentPixels = viewWidth / zoomFactor;
- // The offset in content pixels of the leftmost zoomed pixel from the
- // layerview's left edge when the zoomed view is moved to the right as
- // far as it can go.
- float maxContentOffset = parentWidth - visibleContentPixels;
- // The maximum offset in screen pixels that the zoomed view can have
- float maxZoomedViewOffset = parentWidth - viewContainerWidth;
-
- // The above values allow us to compute the term
- // maxContentOffset / maxZoomedViewOffset
- // which is the number of content pixels that we should move over by
- // for every screen pixel that the zoomed view is moved over by.
- // This allows a smooth transition from when the zoomed view is at the
- // leftmost extent to when it is at the rightmost extent.
-
- // This is the offset in content pixels of the leftmost zoomed pixel
- // visible in the zoomed view. This value is relative to the layerview
- // edge.
- float zoomedContentOffset = ((float)params.leftMargin) * maxContentOffset / maxZoomedViewOffset;
- returnValue.x = (int)(zoomedContentOffset + (x / zoomFactor));
-
- // Same comments here vertically
- visibleContentPixels = viewHeight / zoomFactor;
- maxContentOffset = parentHeight - visibleContentPixels;
- maxZoomedViewOffset = parentHeight - (viewContainerHeight - toolbarHeight);
- float zoomedAreaOffset = (float)params.topMargin + offsetDueToToolBarPosition - layerView.getSurfaceTranslation();
- zoomedContentOffset = zoomedAreaOffset * maxContentOffset / maxZoomedViewOffset;
- returnValue.y = (int)(zoomedContentOffset + ((y - offsetDueToToolBarPosition) / zoomFactor));
-
- return returnValue;
- }
-
- /*
- * A touch point (x,y) occurs in LayerView, this point should be displayed
- * in the center of the zoomed view. The returned point is the position of
- * the Top-Left zoomed view point on the screen device
- */
- private PointF getZoomedViewTopLeftPositionFromTouchPosition(float x, float y) {
- ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
- final float parentWidth = metrics.getWidth();
- final float parentHeight = metrics.getHeight();
-
- // See comments in getUnzoomedPositionFromPointInZoomedView, but the
- // transformations here are largely the reverse of that function.
-
- float visibleContentPixels = viewWidth / zoomFactor;
- float maxContentOffset = parentWidth - visibleContentPixels;
- float maxZoomedViewOffset = parentWidth - viewContainerWidth;
- float contentPixelOffset = x - (visibleContentPixels / 2.0f);
- returnValue.x = (int)(contentPixelOffset * (maxZoomedViewOffset / maxContentOffset));
-
- visibleContentPixels = viewHeight / zoomFactor;
- maxContentOffset = parentHeight - visibleContentPixels;
- maxZoomedViewOffset = parentHeight - (viewContainerHeight - toolbarHeight);
- contentPixelOffset = y - (visibleContentPixels / 2.0f);
- float unscaledViewOffset = layerView.getSurfaceTranslation() - offsetDueToToolBarPosition;
- returnValue.y = (int)((contentPixelOffset * (maxZoomedViewOffset / maxContentOffset)) + unscaledViewOffset);
-
- return returnValue;
- }
-
- private void moveZoomedView(ImmutableViewportMetrics metrics, float newLeftMargin, float newTopMargin,
- StartPointUpdate animateStartPoint) {
- RelativeLayout.LayoutParams newLayoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
- newLayoutParams.leftMargin = (int) newLeftMargin;
- newLayoutParams.topMargin = (int) newTopMargin;
- int topMarginMin = (int)(layerView.getSurfaceTranslation() + dynamicToolbarOverlap);
- int topMarginMax = layerView.getHeight() - viewContainerHeight;
- int leftMarginMin = 0;
- int leftMarginMax = layerView.getWidth() - viewContainerWidth;
-
- if (newTopMargin < topMarginMin) {
- newLayoutParams.topMargin = topMarginMin;
- } else if (newTopMargin > topMarginMax) {
- newLayoutParams.topMargin = topMarginMax;
- }
-
- if (newLeftMargin < leftMarginMin) {
- newLayoutParams.leftMargin = leftMarginMin;
- } else if (newLeftMargin > leftMarginMax) {
- newLayoutParams.leftMargin = leftMarginMax;
- }
-
- if (newLayoutParams.topMargin < topMarginMin + 1) {
- moveToolbar(false);
- } else if (newLayoutParams.topMargin > topMarginMax - 1) {
- moveToolbar(true);
- }
-
- if (animateStartPoint == StartPointUpdate.GECKO_POSITION) {
- // Before this point, the animationStart point is relative to the layerView.
- // The value is initialized in startZoomDisplay using the click point position coming from Gecko.
- // The position of the zoomed view is now calculated, so the position of the animation
- // can now be correctly set relative to the zoomed view
- animationStart.x = animationStart.x - newLayoutParams.leftMargin;
- animationStart.y = animationStart.y - newLayoutParams.topMargin;
- } else if (animateStartPoint == StartPointUpdate.CENTER) {
- // At this point, the animationStart point is no more valid probably because
- // the zoomed view has been moved by the user.
- // In this case, the animationStart point is set to the center point of the zoomed view.
- PointF convertedPosition = getUnzoomedPositionFromPointInZoomedView(viewContainerWidth / 2, viewContainerHeight / 2);
- animationStart.x = convertedPosition.x - newLayoutParams.leftMargin;
- animationStart.y = convertedPosition.y - newLayoutParams.topMargin;
- }
-
- setLayoutParams(newLayoutParams);
- PointF convertedPosition = getUnzoomedPositionFromPointInZoomedView(0, offsetDueToToolBarPosition);
- lastPosition = PointUtils.round(convertedPosition);
- requestZoomedViewRender();
- }
-
- private void moveToolbar(boolean moveTop) {
- if (toolbarOnTop == moveTop) {
- return;
- }
- toolbarOnTop = moveTop;
- if (toolbarOnTop) {
- offsetDueToToolBarPosition = toolbarHeight;
- } else {
- offsetDueToToolBarPosition = 0;
- }
-
- RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) zoomedImageView.getLayoutParams();
- RelativeLayout.LayoutParams pChangeZoomFactorButton = (RelativeLayout.LayoutParams) changeZoomFactorButton.getLayoutParams();
- RelativeLayout.LayoutParams pCloseButton = (RelativeLayout.LayoutParams) closeButton.getLayoutParams();
-
- if (moveTop) {
- p.addRule(RelativeLayout.BELOW, R.id.change_zoom_factor);
- pChangeZoomFactorButton.addRule(RelativeLayout.BELOW, 0);
- pCloseButton.addRule(RelativeLayout.BELOW, 0);
- } else {
- p.addRule(RelativeLayout.BELOW, 0);
- pChangeZoomFactorButton.addRule(RelativeLayout.BELOW, R.id.zoomed_image_view);
- pCloseButton.addRule(RelativeLayout.BELOW, R.id.zoomed_image_view);
- }
- pChangeZoomFactorButton.addRule(RelativeLayout.ALIGN_LEFT, R.id.zoomed_image_view);
- pCloseButton.addRule(RelativeLayout.ALIGN_RIGHT, R.id.zoomed_image_view);
- zoomedImageView.setLayoutParams(p);
- changeZoomFactorButton.setLayoutParams(pChangeZoomFactorButton);
- closeButton.setLayoutParams(pCloseButton);
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- // In case of orientation change, the zoomed view update is stopped until the orientation change
- // is completed. At this time, the function onMetricsChanged is called and the
- // zoomed view update is restarted again.
- if (lastOrientation != newConfig.orientation) {
- shouldBlockUpdate(true);
- lastOrientation = newConfig.orientation;
- }
- }
-
- private void refreshZoomedViewSize(ImmutableViewportMetrics viewport) {
- if (layerView == null) {
- return;
- }
-
- RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams();
- setCapturedSize(viewport);
- moveZoomedView(viewport, params.leftMargin, params.topMargin, StartPointUpdate.NO_CHANGE);
- }
-
- private void setCapturedSize(ImmutableViewportMetrics metrics) {
- float parentMinSize = Math.min(metrics.getWidth(), metrics.getHeight());
- viewWidth = (int) ((parentMinSize * W_CAPTURED_VIEW_IN_PERCENT / (zoomFactor * 100.0)) * zoomFactor);
- viewHeight = (int) ((parentMinSize * H_CAPTURED_VIEW_IN_PERCENT / (zoomFactor * 100.0)) * zoomFactor);
- viewContainerHeight = viewHeight + toolbarHeight +
- 2 * containterSize; // Top and bottom shadows
- viewContainerWidth = viewWidth +
- 2 * containterSize; // Right and left shadows
- // Display in zoomedview is corrupted when width is an odd number
- // More details about this issue here: bug 776906 comment 11
- viewWidth &= ~0x1;
- }
-
- private void shouldBlockUpdate(boolean shouldBlockUpdate) {
- stopUpdateView = shouldBlockUpdate;
- }
-
- private Bitmap.Config getBitmapConfig() {
- return (GeckoAppShell.getScreenDepth() == 24) ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
- }
-
- private void updateUI() {
- // onFinishInflate is not yet completed, the update of the UI will be done later
- if (changeZoomFactorButton == null) {
- return;
- }
- if (isSimplifiedUI) {
- changeZoomFactorButton.setVisibility(View.INVISIBLE);
- } else {
- setTextInZoomFactorButton(zoomFactor);
- changeZoomFactorButton.setVisibility(View.VISIBLE);
- }
- }
-
- private void getPrefs() {
- prefObserver = new PrefsHelper.PrefHandlerBase() {
- @Override
- public void prefValue(String pref, boolean simplified) {
- isSimplifiedUI = simplified;
- if (simplified) {
- zoomFactor = (float) defaultZoomFactor;
- } else {
- zoomFactor = ZOOM_FACTORS_LIST[currentZoomFactorIndex];
- }
- updateUI();
- }
-
- @Override
- public void prefValue(String pref, int defaultZoomFactorFromSettings) {
- defaultZoomFactor = defaultZoomFactorFromSettings;
- if (isSimplifiedUI) {
- zoomFactor = (float) defaultZoomFactor;
- } else {
- zoomFactor = ZOOM_FACTORS_LIST[currentZoomFactorIndex];
- }
- updateUI();
- }
- };
- PrefsHelper.addObserver(new String[] { "ui.zoomedview.simplified",
- "ui.zoomedview.defaultZoomFactor" },
- prefObserver);
- }
-
- private void startZoomDisplay(LayerView aLayerView, final int leftFromGecko, final int topFromGecko) {
- if (isBlockedFromAppearing) {
- return;
- }
- if (layerView == null) {
- layerView = aLayerView;
- layerView.addZoomedViewListener(this);
- layerView.getDynamicToolbarAnimator().addTranslationListener(this);
- ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
- setCapturedSize(metrics);
- }
- startTimeReRender = 0;
- shouldSetVisibleOnUpdate = true;
-
- ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
- // At this point, the start point is relative to the layerView.
- // Later, it will be converted relative to the zoomed view as soon as
- // the position of the zoomed view will be calculated.
- animationStart.x = (float) leftFromGecko * metrics.zoomFactor;
- animationStart.y = (float) topFromGecko * metrics.zoomFactor + layerView.getSurfaceTranslation();
-
- moveUsingGeckoPosition(leftFromGecko, topFromGecko);
- }
-
- public void stopZoomDisplay(boolean withAnimation) {
- // If "startZoomDisplay" is running and not totally completed (Gecko thread is still
- // running and "showZoomedView" has not yet been called), the zoomed view will be
- // displayed after this call and it should not.
- // Force the stop of the zoomed view, changing the shouldSetVisibleOnUpdate flag
- // before the test of the visibility.
- shouldSetVisibleOnUpdate = false;
- if (getVisibility() == View.VISIBLE) {
- hideZoomedView(withAnimation);
- ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
- if (layerView != null) {
- layerView.getDynamicToolbarAnimator().removeTranslationListener(this);
- layerView.removeZoomedViewListener(this);
- layerView = null;
- }
- }
- }
-
- private void changeZoomFactor(boolean zoomIn) {
- if (zoomIn && currentZoomFactorIndex < ZOOM_FACTORS_LIST.length - 1) {
- currentZoomFactorIndex++;
- } else if (zoomIn && currentZoomFactorIndex >= ZOOM_FACTORS_LIST.length - 1) {
- currentZoomFactorIndex = 0;
- } else if (!zoomIn && currentZoomFactorIndex > 0) {
- currentZoomFactorIndex--;
- } else {
- currentZoomFactorIndex = ZOOM_FACTORS_LIST.length - 1;
- }
- zoomFactor = ZOOM_FACTORS_LIST[currentZoomFactorIndex];
-
- ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
- refreshZoomedViewSize(metrics);
- setTextInZoomFactorButton(zoomFactor);
- }
-
- private void setTextInZoomFactorButton(float zoom) {
- final String percentageValue = Integer.toString((int) (100 * zoom));
- changeZoomFactorButton.setText("- " + getResources().getString(R.string.percent, percentageValue) + " +");
- }
-
- @Override
- public void handleMessage(final String event, final JSONObject message) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- try {
- if (event.equals("Gesture:clusteredLinksClicked")) {
- final JSONObject clickPosition = message.getJSONObject("clickPosition");
- int left = clickPosition.getInt("x");
- int top = clickPosition.getInt("y");
- // Start to display inside the zoomedView
- LayerView geckoAppLayerView = GeckoAppShell.getLayerView();
- if (geckoAppLayerView != null) {
- startZoomDisplay(geckoAppLayerView, left, top);
- }
- } else if (event.equals("Window:Resize")) {
- ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
- refreshZoomedViewSize(metrics);
- } else if (event.equals("Content:LocationChange")) {
- stopZoomDisplay(false);
- } else if (event.equals("Gesture:CloseZoomedView") ||
- event.equals("Browser:ZoomToPageWidth") ||
- event.equals("Browser:ZoomToRect")) {
- stopZoomDisplay(true);
- } else if (event.equals("FormAssist:AutoComplete")) {
- isBlockedFromAppearing = true;
- stopZoomDisplay(true);
- } else if (event.equals("FormAssist:Hide")) {
- isBlockedFromAppearing = false;
- }
- } catch (JSONException e) {
- Log.e(LOGTAG, "JSON exception", e);
- }
- }
- });
- }
-
- private void moveUsingGeckoPosition(int leftFromGecko, int topFromGecko) {
- ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
- final float parentHeight = metrics.getHeight();
- // moveToolbar is called before getZoomedViewTopLeftPositionFromTouchPosition in order to
- // correctly center vertically the zoomed area
- moveToolbar((topFromGecko * metrics.zoomFactor > parentHeight / 2));
- PointF convertedPosition = getZoomedViewTopLeftPositionFromTouchPosition((leftFromGecko * metrics.zoomFactor),
- (topFromGecko * metrics.zoomFactor));
- moveZoomedView(metrics, convertedPosition.x, convertedPosition.y, StartPointUpdate.GECKO_POSITION);
- }
-
- @Override
- public void onTranslationChanged(float aToolbarTranslation, float aLayerViewTranslation) {
- ThreadUtils.assertOnUiThread();
- if (layerView != null) {
- dynamicToolbarOverlap = aLayerViewTranslation - aToolbarTranslation;
- refreshZoomedViewSize(layerView.getViewportMetrics());
- }
- }
-
- @Override
- public void onMetricsChanged(final ImmutableViewportMetrics viewport) {
- // It can be called from a Gecko thread (forceViewportMetrics in GeckoLayerClient).
- // Post to UI Thread to avoid Exception:
- // "Only the original thread that created a view hierarchy can touch its views."
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- shouldBlockUpdate(false);
- refreshZoomedViewSize(viewport);
- }
- });
- }
-
- @Override
- public void onPanZoomStopped() {
- }
-
- @Override
- public void updateView(ByteBuffer data) {
- final Bitmap sb3 = Bitmap.createBitmap(viewWidth, viewHeight, getBitmapConfig());
- if (sb3 != null) {
- data.rewind();
- try {
- sb3.copyPixelsFromBuffer(data);
- } catch (Exception iae) {
- Log.w(LOGTAG, iae.toString());
- }
- if (zoomedImageView != null) {
- RoundedBitmapDrawable ob3 = new RoundedBitmapDrawable(getResources(), sb3, toolbarOnTop, cornerRadius);
- zoomedImageView.setImageDrawable(ob3);
- }
- }
- if (shouldSetVisibleOnUpdate) {
- this.showZoomedView();
- }
- lastStartTimeReRender = startTimeReRender;
- startTimeReRender = 0;
- }
-
- private void showZoomedView() {
- // no animation if the zoomed view is already visible
- if (getVisibility() != View.VISIBLE) {
- final Animation anim = new ScaleAnimation(
- 0f, 1f, // Start and end values for the X axis scaling
- 0f, 1f, // Start and end values for the Y axis scaling
- Animation.ABSOLUTE, animationStart.x, // Pivot point of X scaling
- Animation.ABSOLUTE, animationStart.y); // Pivot point of Y scaling
- anim.setFillAfter(true); // Needed to keep the result of the animation
- anim.setDuration(OPENING_ANIMATION_DURATION_MS);
- anim.setInterpolator(new OvershootInterpolator(OVERSHOOT_INTERPOLATOR_TENSION));
- anim.setAnimationListener(new AnimationListener() {
- public void onAnimationEnd(Animation animation) {
- setListeners();
- }
- public void onAnimationRepeat(Animation animation) {
- }
- public void onAnimationStart(Animation animation) {
- removeListeners();
- }
- });
- setAnimation(anim);
- }
- setVisibility(View.VISIBLE);
- shouldSetVisibleOnUpdate = false;
- }
-
- private void hideZoomedView(boolean withAnimation) {
- if (withAnimation) {
- final Animation anim = new ScaleAnimation(
- 1f, 0f, // Start and end values for the X axis scaling
- 1f, 0f, // Start and end values for the Y axis scaling
- Animation.ABSOLUTE, animationStart.x, // Pivot point of X scaling
- Animation.ABSOLUTE, animationStart.y); // Pivot point of Y scaling
- anim.setFillAfter(true); // Needed to keep the result of the animation
- anim.setDuration(CLOSING_ANIMATION_DURATION_MS);
- anim.setAnimationListener(new AnimationListener() {
- public void onAnimationEnd(Animation animation) {
- }
- public void onAnimationRepeat(Animation animation) {
- }
- public void onAnimationStart(Animation animation) {
- removeListeners();
- }
- });
- setAnimation(anim);
- } else {
- removeListeners();
- setAnimation(null);
- }
- setVisibility(View.GONE);
- shouldSetVisibleOnUpdate = false;
- }
-
- private void updateBufferSize() {
- int pixelSize = (GeckoAppShell.getScreenDepth() == 24) ? 4 : 2;
- int capacity = viewWidth * viewHeight * pixelSize;
- if (buffer == null || buffer.capacity() != capacity) {
- buffer = DirectBufferAllocator.free(buffer);
- buffer = DirectBufferAllocator.allocate(capacity);
- }
- }
-
- private boolean isRendering() {
- return (startTimeReRender != 0);
- }
-
- private boolean renderFrequencyTooHigh() {
- return ((System.nanoTime() - lastStartTimeReRender) < MINIMUM_DELAY_BETWEEN_TWO_RENDER_CALLS_NS);
- }
-
- @WrapForJNI(dispatchTo = "gecko")
- private static native void requestZoomedViewData(ByteBuffer buffer, int tabId,
- int xPos, int yPos, int width,
- int height, float scale);
-
- @Override
- public void requestZoomedViewRender() {
- if (stopUpdateView) {
- return;
- }
- // remove pending runnable
- ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
-
- // "requestZoomedViewRender" can be called very often by Gecko (endDrawing in LayerRender) without
- // any thing changed in the zoomed area (useless calls from the "zoomed area" point of view).
- // "requestZoomedViewRender" can take time to re-render the zoomed view, it depends of the complexity
- // of the html on this area.
- // To avoid to slow down the application, the 2 following cases are tested:
-
- // 1- Last render is still running, plan another render later.
- if (isRendering()) {
- // post a new runnable DELAY_BEFORE_NEXT_RENDER_REQUEST_MS later
- // We need to post with a delay to be sure that the last call to requestZoomedViewRender will be done.
- // For a static html page WITHOUT any animation/video, there is a last call to endDrawing and we need to make
- // the zoomed render on this last call.
- ThreadUtils.postDelayedToUiThread(requestRenderRunnable, DELAY_BEFORE_NEXT_RENDER_REQUEST_MS);
- return;
- }
-
- // 2- Current render occurs too early, plan another render later.
- if (renderFrequencyTooHigh()) {
- // post a new runnable DELAY_BEFORE_NEXT_RENDER_REQUEST_MS later
- // We need to post with a delay to be sure that the last call to requestZoomedViewRender will be done.
- // For a page WITH animation/video, the animation/video can be stopped, and we need to make
- // the zoomed render on this last call.
- ThreadUtils.postDelayedToUiThread(requestRenderRunnable, DELAY_BEFORE_NEXT_RENDER_REQUEST_MS);
- return;
- }
-
- startTimeReRender = System.nanoTime();
- // Allocate the buffer if it's the first call.
- // Change the buffer size if it's not the right size.
- updateBufferSize();
-
- int tabId = Tabs.getInstance().getSelectedTab().getId();
-
- ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
- PointF origin = metrics.getOrigin();
-
- final int xPos = (int)origin.x + lastPosition.x;
- final int yPos = (int)origin.y + lastPosition.y;
-
- requestZoomedViewData(buffer, tabId, xPos, yPos, viewWidth, viewHeight,
- zoomFactor * metrics.zoomFactor);
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/activitystream/ActivityStream.java b/mobile/android/base/java/org/mozilla/gecko/activitystream/ActivityStream.java
deleted file mode 100644
index d1c3f5916..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/ActivityStream.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.activitystream;
-
-import android.content.Context;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.text.TextUtils;
-
-import com.keepsafe.switchboard.SwitchBoard;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.Experiments;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.util.StringUtils;
-import org.mozilla.gecko.util.publicsuffix.PublicSuffix;
-
-import java.util.Arrays;
-import java.util.List;
-
-public class ActivityStream {
- /**
- * List of undesired prefixes for labels based on a URL.
- *
- * This list is by no means complete and is based on those sources:
- * - https://gist.github.com/nchapman/36502ad115e8825d522a66549971a3f0
- * - https://github.com/mozilla/activity-stream/issues/1311
- */
- private static final List<String> UNDESIRED_LABEL_PREFIXES = Arrays.asList(
- "index.",
- "home."
- );
-
- /**
- * Undesired labels for labels based on a URL.
- *
- * This list is by no means complete and is based on those sources:
- * - https://gist.github.com/nchapman/36502ad115e8825d522a66549971a3f0
- * - https://github.com/mozilla/activity-stream/issues/1311
- */
- private static final List<String> UNDESIRED_LABELS = Arrays.asList(
- "render",
- "login",
- "edit"
- );
-
- public static boolean isEnabled(Context context) {
- if (!isUserEligible(context)) {
- // If the user is not eligible then disable activity stream. Even if it has been
- // enabled before.
- return false;
- }
-
- return GeckoSharedPrefs.forApp(context)
- .getBoolean(GeckoPreferences.PREFS_ACTIVITY_STREAM, false);
- }
-
- /**
- * Is the user eligible to use activity stream or should we hide it from settings etc.?
- */
- public static boolean isUserEligible(Context context) {
- if (AppConstants.MOZ_ANDROID_ACTIVITY_STREAM) {
- // If the build flag is enabled then just show the option to the user.
- return true;
- }
-
- if (AppConstants.NIGHTLY_BUILD && SwitchBoard.isInExperiment(context, Experiments.ACTIVITY_STREAM)) {
- // If this is a nightly build and the user is part of the activity stream experiment then
- // the option should be visible too. The experiment is limited to Nightly too but I want
- // to make really sure that this isn't riding the trains accidentally.
- return true;
- }
-
- // For everyone else activity stream is not available yet.
- return false;
- }
-
- /**
- * Query whether we want to display Activity Stream as a Home Panel (within the HomePager),
- * or as a HomePager replacement.
- */
- public static boolean isHomePanel() {
- return true;
- }
-
- /**
- * Extract a label from a URL to use in Activity Stream.
- *
- * This method implements the proposal from this desktop AS issue:
- * https://github.com/mozilla/activity-stream/issues/1311
- *
- * @param usePath Use the path of the URL to extract a label (if suitable)
- */
- public static void extractLabel(final Context context, final String url, final boolean usePath, final LabelCallback callback) {
- new AsyncTask<Void, Void, String>() {
- @Override
- protected String doInBackground(Void... params) {
- if (TextUtils.isEmpty(url)) {
- return "";
- }
-
- final Uri uri = Uri.parse(url);
-
- // Use last path segment if suitable
- if (usePath) {
- final String segment = uri.getLastPathSegment();
- if (!TextUtils.isEmpty(segment)
- && !UNDESIRED_LABELS.contains(segment)
- && !segment.matches("^[0-9]+$")) {
-
- boolean hasUndesiredPrefix = false;
- for (int i = 0; i < UNDESIRED_LABEL_PREFIXES.size(); i++) {
- if (segment.startsWith(UNDESIRED_LABEL_PREFIXES.get(i))) {
- hasUndesiredPrefix = true;
- break;
- }
- }
-
- if (!hasUndesiredPrefix) {
- return segment;
- }
- }
- }
-
- // If no usable path segment was found then use the host without public suffix and common subdomains
- final String host = uri.getHost();
- if (TextUtils.isEmpty(host)) {
- return url;
- }
-
- return StringUtils.stripCommonSubdomains(
- PublicSuffix.stripPublicSuffix(context, host));
- }
-
- @Override
- protected void onPostExecute(String label) {
- callback.onLabelExtracted(label);
- }
- }.execute();
- }
-
- public abstract static class LabelCallback {
- public abstract void onLabelExtracted(String label);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/adjust/AdjustBrowserAppDelegate.java b/mobile/android/base/java/org/mozilla/gecko/adjust/AdjustBrowserAppDelegate.java
deleted file mode 100644
index aee0bba63..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/adjust/AdjustBrowserAppDelegate.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package org.mozilla.gecko.adjust;
-
-import android.content.SharedPreferences;
-import android.os.Bundle;
-
-import org.mozilla.gecko.AdjustConstants;
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.delegates.BrowserAppDelegate;
-import org.mozilla.gecko.mozglue.SafeIntent;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.util.IntentUtils;
-
-public class AdjustBrowserAppDelegate extends BrowserAppDelegate {
- private final AdjustHelperInterface adjustHelper;
- private final AttributionHelperListener attributionHelperListener;
-
- public AdjustBrowserAppDelegate(AttributionHelperListener attributionHelperListener) {
- this.adjustHelper = AdjustConstants.getAdjustHelper();
- this.attributionHelperListener = attributionHelperListener;
- }
-
- @Override
- public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {
- adjustHelper.onCreate(browserApp,
- AdjustConstants.MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN,
- attributionHelperListener);
-
- final boolean isInAutomation = IntentUtils.getIsInAutomationFromEnvironment(
- new SafeIntent(browserApp.getIntent()));
-
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(browserApp);
-
- // Adjust stores enabled state so this is only necessary because users may have set
- // their data preferences before this feature was implemented and we need to respect
- // those before upload can occur in Adjust.onResume.
- adjustHelper.setEnabled(!isInAutomation
- && prefs.getBoolean(GeckoPreferences.PREFS_HEALTHREPORT_UPLOAD_ENABLED, true));
- }
-
- @Override
- public void onResume(BrowserApp browserApp) {
- // Needed for Adjust to get accurate session measurements
- adjustHelper.onResume();
- }
-
- @Override
- public void onPause(BrowserApp browserApp) {
- // Needed for Adjust to get accurate session measurements
- adjustHelper.onPause();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/adjust/AdjustHelper.java b/mobile/android/base/java/org/mozilla/gecko/adjust/AdjustHelper.java
deleted file mode 100644
index 19399e735..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/adjust/AdjustHelper.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.adjust;
-
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-import com.adjust.sdk.Adjust;
-import com.adjust.sdk.AdjustAttribution;
-import com.adjust.sdk.AdjustConfig;
-import com.adjust.sdk.AdjustReferrerReceiver;
-import com.adjust.sdk.LogLevel;
-import com.adjust.sdk.OnAttributionChangedListener;
-
-import org.mozilla.gecko.AppConstants;
-
-public class AdjustHelper implements AdjustHelperInterface, OnAttributionChangedListener {
-
- private static final String LOGTAG = AdjustHelper.class.getSimpleName();
- private AttributionHelperListener attributionListener;
-
- public void onCreate(final Context context, final String maybeAppToken, final AttributionHelperListener listener) {
- final String environment;
- final LogLevel logLevel;
- if (AppConstants.MOZILLA_OFFICIAL) {
- environment = AdjustConfig.ENVIRONMENT_PRODUCTION;
- logLevel = LogLevel.WARN;
- } else {
- environment = AdjustConfig.ENVIRONMENT_SANDBOX;
- logLevel = LogLevel.VERBOSE;
- }
- if (maybeAppToken == null) {
- // We've got install tracking turned on -- we better have a token!
- throw new IllegalArgumentException("maybeAppToken must not be null");
- }
- attributionListener = listener;
- AdjustConfig config = new AdjustConfig(context, maybeAppToken, environment);
- config.setLogLevel(logLevel);
- config.setOnAttributionChangedListener(this);
- Adjust.onCreate(config);
- }
-
- public void onPause() {
- Adjust.onPause();
- }
-
- public void onResume() {
- Adjust.onResume();
- }
-
- public void setEnabled(final boolean isEnabled) {
- Adjust.setEnabled(isEnabled);
- }
-
- public void onReceive(final Context context, final Intent intent) {
- new AdjustReferrerReceiver().onReceive(context, intent);
- }
-
- @Override
- public void onAttributionChanged(AdjustAttribution attribution) {
- if (attributionListener == null) {
- throw new IllegalStateException("Expected non-null attribution listener.");
- }
-
- if (attribution == null) {
- Log.e(LOGTAG, "Adjust attribution is null; skipping campaign id retrieval.");
- return;
- }
- attributionListener.onCampaignIdChanged(attribution.campaign);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/adjust/AdjustHelperInterface.java b/mobile/android/base/java/org/mozilla/gecko/adjust/AdjustHelperInterface.java
deleted file mode 100644
index aeb7b4334..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/adjust/AdjustHelperInterface.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.adjust;
-
-import android.content.Context;
-import android.content.Intent;
-
-public interface AdjustHelperInterface {
- /**
- * Register the Application with the Adjust SDK.
- * @param appToken the (secret!) Adjust SDK per-application token to register with; may be null.
- */
- void onCreate(final Context context, final String appToken, final AttributionHelperListener listener);
- void onPause();
- void onResume();
-
- void setEnabled(final boolean isEnabled);
- void onReceive(final Context context, final Intent intent);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/adjust/AttributionHelperListener.java b/mobile/android/base/java/org/mozilla/gecko/adjust/AttributionHelperListener.java
deleted file mode 100644
index 6dadd2261..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/adjust/AttributionHelperListener.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.adjust;
-
-/**
- * Because of how our build module dependencies are structured, we aren't able to use
- * the {@link com.adjust.sdk.OnAttributionChangedListener} directly outside of {@link AdjustHelper}.
- * If the Adjust SDK is enabled, this listener should be notified when {@link com.adjust.sdk.OnAttributionChangedListener}
- * is fired (i.e. this listener would be daisy-chained to the Adjust one). The listener also
- * inherits thread-safety from GeckoSharedPrefs which is used to store the campaign ID.
- */
-public interface AttributionHelperListener {
- void onCampaignIdChanged(String campaignId);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/adjust/StubAdjustHelper.java b/mobile/android/base/java/org/mozilla/gecko/adjust/StubAdjustHelper.java
deleted file mode 100644
index ddfed84bd..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/adjust/StubAdjustHelper.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.adjust;
-
-import android.content.Context;
-import android.content.Intent;
-
-public class StubAdjustHelper implements AdjustHelperInterface {
- public void onCreate(final Context context, final String appToken, final AttributionHelperListener listener) {
- // Do nothing.
- }
-
- public void onPause() {
- // Do nothing.
- }
-
- public void onResume() {
- // Do nothing.
- }
-
- public void setEnabled(final boolean isEnabled) {
- // Do nothing.
- }
-
- public void onReceive(final Context context, final Intent intent) {
- // Do nothing.
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/animation/AnimationUtils.java b/mobile/android/base/java/org/mozilla/gecko/animation/AnimationUtils.java
deleted file mode 100644
index 63e8e168e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/animation/AnimationUtils.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-
-package org.mozilla.gecko.animation;
-
-import android.content.Context;
-
-public class AnimationUtils {
- private static long mShortDuration = -1;
-
- public static long getShortDuration(Context context) {
- if (mShortDuration < 0) {
- mShortDuration = context.getResources().getInteger(android.R.integer.config_shortAnimTime);
- }
- return mShortDuration;
- }
-}
-
diff --git a/mobile/android/base/java/org/mozilla/gecko/animation/HeightChangeAnimation.java b/mobile/android/base/java/org/mozilla/gecko/animation/HeightChangeAnimation.java
deleted file mode 100644
index bf8007bbf..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/animation/HeightChangeAnimation.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.animation;
-
-import android.view.View;
-import android.view.animation.Animation;
-import android.view.animation.Transformation;
-
-public class HeightChangeAnimation extends Animation {
- int mFromHeight;
- int mToHeight;
- View mView;
-
- public HeightChangeAnimation(View view, int fromHeight, int toHeight) {
- mView = view;
- mFromHeight = fromHeight;
- mToHeight = toHeight;
- }
-
- @Override
- protected void applyTransformation(float interpolatedTime, Transformation t) {
- mView.getLayoutParams().height = Math.round((mFromHeight * (1 - interpolatedTime)) + (mToHeight * interpolatedTime));
- mView.requestLayout();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/animation/PropertyAnimator.java b/mobile/android/base/java/org/mozilla/gecko/animation/PropertyAnimator.java
deleted file mode 100644
index dc2403bbd..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/animation/PropertyAnimator.java
+++ /dev/null
@@ -1,342 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.animation;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.mozilla.gecko.AppConstants.Versions;
-
-import android.os.Handler;
-import android.support.v4.view.ViewCompat;
-import android.view.Choreographer;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.animation.AnimationUtils;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
-
-public class PropertyAnimator implements Runnable {
- private static final String LOGTAG = "GeckoPropertyAnimator";
-
- public static enum Property {
- ALPHA,
- TRANSLATION_X,
- TRANSLATION_Y,
- SCROLL_X,
- SCROLL_Y,
- WIDTH,
- HEIGHT
- }
-
- private class ElementHolder {
- View view;
- Property property;
- float from;
- float to;
- }
-
- public static interface PropertyAnimationListener {
- public void onPropertyAnimationStart();
- public void onPropertyAnimationEnd();
- }
-
- private final Interpolator mInterpolator;
- private long mStartTime;
- private final long mDuration;
- private final float mDurationReciprocal;
- private final List<ElementHolder> mElementsList;
- private List<PropertyAnimationListener> mListeners;
- FramePoster mFramePoster;
- private boolean mUseHardwareLayer;
-
- public PropertyAnimator(long duration) {
- this(duration, new DecelerateInterpolator());
- }
-
- public PropertyAnimator(long duration, Interpolator interpolator) {
- mDuration = duration;
- mDurationReciprocal = 1.0f / mDuration;
- mInterpolator = interpolator;
- mElementsList = new ArrayList<ElementHolder>();
- mFramePoster = FramePoster.create(this);
- mUseHardwareLayer = true;
- }
-
- public void setUseHardwareLayer(boolean useHardwareLayer) {
- mUseHardwareLayer = useHardwareLayer;
- }
-
- public void attach(View view, Property property, float to) {
- ElementHolder element = new ElementHolder();
-
- element.view = view;
- element.property = property;
- element.to = to;
-
- mElementsList.add(element);
- }
-
- public void addPropertyAnimationListener(PropertyAnimationListener listener) {
- if (mListeners == null) {
- mListeners = new ArrayList<PropertyAnimationListener>();
- }
-
- mListeners.add(listener);
- }
-
- public long getDuration() {
- return mDuration;
- }
-
- public long getRemainingTime() {
- int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
- return mDuration - timePassed;
- }
-
- @Override
- public void run() {
- int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
- if (timePassed >= mDuration) {
- stop();
- return;
- }
-
- float interpolation = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
-
- for (ElementHolder element : mElementsList) {
- float delta = element.from + ((element.to - element.from) * interpolation);
- invalidate(element, delta);
- }
-
- mFramePoster.postNextAnimationFrame();
- }
-
- public void start() {
- if (mDuration == 0) {
- return;
- }
-
- mStartTime = AnimationUtils.currentAnimationTimeMillis();
-
- // Fix the from value based on current position and property
- for (ElementHolder element : mElementsList) {
- if (element.property == Property.ALPHA)
- element.from = ViewHelper.getAlpha(element.view);
- else if (element.property == Property.TRANSLATION_Y)
- element.from = ViewHelper.getTranslationY(element.view);
- else if (element.property == Property.TRANSLATION_X)
- element.from = ViewHelper.getTranslationX(element.view);
- else if (element.property == Property.SCROLL_Y)
- element.from = ViewHelper.getScrollY(element.view);
- else if (element.property == Property.SCROLL_X)
- element.from = ViewHelper.getScrollX(element.view);
- else if (element.property == Property.WIDTH)
- element.from = ViewHelper.getWidth(element.view);
- else if (element.property == Property.HEIGHT)
- element.from = ViewHelper.getHeight(element.view);
-
- ViewCompat.setHasTransientState(element.view, true);
-
- if (shouldEnableHardwareLayer(element))
- element.view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- else
- element.view.setDrawingCacheEnabled(true);
- }
-
- // Get ViewTreeObserver from any of the participant views
- // in the animation.
- final ViewTreeObserver treeObserver;
- if (mElementsList.size() > 0) {
- treeObserver = mElementsList.get(0).view.getViewTreeObserver();
- } else {
- treeObserver = null;
- }
-
- final ViewTreeObserver.OnPreDrawListener preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- if (treeObserver.isAlive()) {
- treeObserver.removeOnPreDrawListener(this);
- }
-
- mFramePoster.postFirstAnimationFrame();
- return true;
- }
- };
-
- // Try to start animation after any on-going layout round
- // in the current view tree. OnPreDrawListener seems broken
- // on pre-Honeycomb devices, start animation immediatelly
- // in this case.
- if (treeObserver != null && treeObserver.isAlive()) {
- treeObserver.addOnPreDrawListener(preDrawListener);
- } else {
- mFramePoster.postFirstAnimationFrame();
- }
-
- if (mListeners != null) {
- for (PropertyAnimationListener listener : mListeners) {
- listener.onPropertyAnimationStart();
- }
- }
- }
-
- /**
- * Stop the animation, optionally snapping to the end position.
- * onPropertyAnimationEnd is only called when snapping to the end position.
- */
- public void stop(boolean snapToEndPosition) {
- mFramePoster.cancelAnimationFrame();
-
- // Make sure to snap to the end position.
- for (ElementHolder element : mElementsList) {
- if (snapToEndPosition)
- invalidate(element, element.to);
-
- ViewCompat.setHasTransientState(element.view, false);
-
- if (shouldEnableHardwareLayer(element)) {
- element.view.setLayerType(View.LAYER_TYPE_NONE, null);
- } else {
- element.view.setDrawingCacheEnabled(false);
- }
- }
-
- mElementsList.clear();
-
- if (mListeners != null) {
- if (snapToEndPosition) {
- for (PropertyAnimationListener listener : mListeners) {
- listener.onPropertyAnimationEnd();
- }
- }
-
- mListeners.clear();
- mListeners = null;
- }
- }
-
- public void stop() {
- stop(true);
- }
-
- private boolean shouldEnableHardwareLayer(ElementHolder element) {
- if (!mUseHardwareLayer) {
- return false;
- }
-
- if (!(element.view instanceof ViewGroup)) {
- return false;
- }
-
- if (element.property == Property.ALPHA ||
- element.property == Property.TRANSLATION_Y ||
- element.property == Property.TRANSLATION_X) {
- return true;
- }
-
- return false;
- }
-
- private void invalidate(final ElementHolder element, final float delta) {
- final View view = element.view;
-
- // check to see if the view was detached between the check above and this code
- // getting run on the UI thread.
- if (view.getHandler() == null)
- return;
-
- if (element.property == Property.ALPHA)
- ViewHelper.setAlpha(element.view, delta);
- else if (element.property == Property.TRANSLATION_Y)
- ViewHelper.setTranslationY(element.view, delta);
- else if (element.property == Property.TRANSLATION_X)
- ViewHelper.setTranslationX(element.view, delta);
- else if (element.property == Property.SCROLL_Y)
- ViewHelper.scrollTo(element.view, ViewHelper.getScrollX(element.view), (int) delta);
- else if (element.property == Property.SCROLL_X)
- ViewHelper.scrollTo(element.view, (int) delta, ViewHelper.getScrollY(element.view));
- else if (element.property == Property.WIDTH)
- ViewHelper.setWidth(element.view, (int) delta);
- else if (element.property == Property.HEIGHT)
- ViewHelper.setHeight(element.view, (int) delta);
- }
-
- private static abstract class FramePoster {
- public static FramePoster create(Runnable r) {
- if (Versions.feature16Plus) {
- return new FramePosterPostJB(r);
- }
-
- return new FramePosterPreJB(r);
- }
-
- public abstract void postFirstAnimationFrame();
- public abstract void postNextAnimationFrame();
- public abstract void cancelAnimationFrame();
- }
-
- private static class FramePosterPreJB extends FramePoster {
- // Default refresh rate in ms.
- private static final int INTERVAL = 10;
-
- private final Handler mHandler;
- private final Runnable mRunnable;
-
- public FramePosterPreJB(Runnable r) {
- mHandler = new Handler();
- mRunnable = r;
- }
-
- @Override
- public void postFirstAnimationFrame() {
- mHandler.post(mRunnable);
- }
-
- @Override
- public void postNextAnimationFrame() {
- mHandler.postDelayed(mRunnable, INTERVAL);
- }
-
- @Override
- public void cancelAnimationFrame() {
- mHandler.removeCallbacks(mRunnable);
- }
- }
-
- private static class FramePosterPostJB extends FramePoster {
- private final Choreographer mChoreographer;
- private final Choreographer.FrameCallback mCallback;
-
- public FramePosterPostJB(final Runnable r) {
- mChoreographer = Choreographer.getInstance();
-
- mCallback = new Choreographer.FrameCallback() {
- @Override
- public void doFrame(long frameTimeNanos) {
- r.run();
- }
- };
- }
-
- @Override
- public void postFirstAnimationFrame() {
- postNextAnimationFrame();
- }
-
- @Override
- public void postNextAnimationFrame() {
- mChoreographer.postFrameCallback(mCallback);
- }
-
- @Override
- public void cancelAnimationFrame() {
- mChoreographer.removeFrameCallback(mCallback);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/animation/Rotate3DAnimation.java b/mobile/android/base/java/org/mozilla/gecko/animation/Rotate3DAnimation.java
deleted file mode 100644
index 7e8377f55..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/animation/Rotate3DAnimation.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.mozilla.gecko.animation;
-
-import android.view.animation.Animation;
-import android.view.animation.Transformation;
-
-import android.graphics.Camera;
-import android.graphics.Matrix;
-
-/**
- * An animation that rotates the view on the Y axis between two specified angles.
- * This animation also adds a translation on the Z axis (depth) to improve the effect.
- */
-public class Rotate3DAnimation extends Animation {
- private final float mFromDegrees;
- private final float mToDegrees;
-
- private final float mCenterX;
- private final float mCenterY;
-
- private final float mDepthZ;
- private final boolean mReverse;
- private Camera mCamera;
-
- private int mWidth = 1;
- private int mHeight = 1;
-
- /**
- * Creates a new 3D rotation on the Y axis. The rotation is defined by its
- * start angle and its end angle. Both angles are in degrees. The rotation
- * is performed around a center point on the 2D space, defined by a pair
- * of X and Y coordinates, called centerX and centerY. When the animation
- * starts, a translation on the Z axis (depth) is performed. The length
- * of the translation can be specified, as well as whether the translation
- * should be reversed in time.
- *
- * @param fromDegrees the start angle of the 3D rotation
- * @param toDegrees the end angle of the 3D rotation
- * @param centerX the X center of the 3D rotation
- * @param centerY the Y center of the 3D rotation
- * @param reverse true if the translation should be reversed, false otherwise
- */
- public Rotate3DAnimation(float fromDegrees, float toDegrees,
- float centerX, float centerY, float depthZ, boolean reverse) {
- mFromDegrees = fromDegrees;
- mToDegrees = toDegrees;
- mCenterX = centerX;
- mCenterY = centerY;
- mDepthZ = depthZ;
- mReverse = reverse;
- }
-
- @Override
- public void initialize(int width, int height, int parentWidth, int parentHeight) {
- super.initialize(width, height, parentWidth, parentHeight);
- mCamera = new Camera();
- mWidth = width;
- mHeight = height;
- }
-
- @Override
- protected void applyTransformation(float interpolatedTime, Transformation t) {
- final float fromDegrees = mFromDegrees;
- float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
-
- final Camera camera = mCamera;
- final Matrix matrix = t.getMatrix();
-
- camera.save();
- if (mReverse) {
- camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
- } else {
- camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
- }
- camera.rotateX(degrees);
- camera.getMatrix(matrix);
- camera.restore();
-
- matrix.preTranslate(-mCenterX * mWidth, -mCenterY * mHeight);
- matrix.postTranslate(mCenterX * mWidth, mCenterY * mHeight);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/animation/ViewHelper.java b/mobile/android/base/java/org/mozilla/gecko/animation/ViewHelper.java
deleted file mode 100644
index 3ea2e8437..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/animation/ViewHelper.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.animation;
-
-import android.view.View;
-import android.view.ViewGroup;
-
-public final class ViewHelper {
- private ViewHelper() {
- }
-
- public static float getTranslationX(View view) {
- if (view != null) {
- return view.getTranslationX();
- }
-
- return 0;
- }
-
- public static void setTranslationX(View view, float translationX) {
- if (view != null) {
- view.setTranslationX(translationX);
- }
- }
-
- public static float getTranslationY(View view) {
- if (view != null) {
- return view.getTranslationY();
- }
-
- return 0;
- }
-
- public static void setTranslationY(View view, float translationY) {
- if (view != null) {
- view.setTranslationY(translationY);
- }
- }
-
- public static float getAlpha(View view) {
- if (view != null) {
- return view.getAlpha();
- }
-
- return 1;
- }
-
- public static void setAlpha(View view, float alpha) {
- if (view != null) {
- view.setAlpha(alpha);
- }
- }
-
- public static int getWidth(View view) {
- if (view != null) {
- return view.getWidth();
- }
-
- return 0;
- }
-
- public static void setWidth(View view, int width) {
- if (view != null) {
- ViewGroup.LayoutParams lp = view.getLayoutParams();
- lp.width = width;
- view.setLayoutParams(lp);
- }
- }
-
- public static int getHeight(View view) {
- if (view != null) {
- return view.getHeight();
- }
-
- return 0;
- }
-
- public static void setHeight(View view, int height) {
- if (view != null) {
- ViewGroup.LayoutParams lp = view.getLayoutParams();
- lp.height = height;
- view.setLayoutParams(lp);
- }
- }
-
- public static int getScrollX(View view) {
- if (view != null) {
- return view.getScrollX();
- }
-
- return 0;
- }
-
- public static int getScrollY(View view) {
- if (view != null) {
- return view.getScrollY();
- }
-
- return 0;
- }
-
- public static void scrollTo(View view, int scrollX, int scrollY) {
- if (view != null) {
- view.scrollTo(scrollX, scrollY);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/cleanup/FileCleanupController.java b/mobile/android/base/java/org/mozilla/gecko/cleanup/FileCleanupController.java
deleted file mode 100644
index 447b837e8..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/cleanup/FileCleanupController.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, you can obtain one at http://mozilla.org/MPL/2.0/.
- */
-
-package org.mozilla.gecko.cleanup;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.support.annotation.VisibleForTesting;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Encapsulates the code to run the {@link FileCleanupService}. Call
- * {@link #startIfReady(Context, SharedPreferences, String)} to start the clean-up.
- *
- * Note: for simplicity, the current implementation does not cache which
- * files have been cleaned up and will attempt to delete the same files
- * each time it is run. If the file deletion list grows large, consider
- * keeping a cache.
- */
-public class FileCleanupController {
-
- private static final long MILLIS_BETWEEN_CLEANUPS = TimeUnit.DAYS.toMillis(7);
- @VisibleForTesting static final String PREF_LAST_CLEANUP_MILLIS = "cleanup.lastFileCleanupMillis";
-
- // These will be prepended with the path of the profile we're cleaning up.
- private static final String[] PROFILE_FILES_TO_CLEANUP = new String[] {
- "health.db",
- "health.db-journal",
- "health.db-shm",
- "health.db-wal",
- };
-
- /**
- * Starts the clean-up if it's time to clean-up, otherwise returns. For simplicity,
- * it does not schedule the cleanup for some point in the future - this method will
- * have to be called again (i.e. polled) in order to run the clean-up service.
- *
- * @param context Context of the calling {@link android.app.Activity}
- * @param sharedPrefs The {@link SharedPreferences} instance to store the controller state to
- * @param profilePath The path to the profile the service should clean-up files from
- */
- public static void startIfReady(final Context context, final SharedPreferences sharedPrefs, final String profilePath) {
- if (!isCleanupReady(sharedPrefs)) {
- return;
- }
-
- recordCleanupScheduled(sharedPrefs);
-
- final Intent fileCleanupIntent = new Intent(context, FileCleanupService.class);
- fileCleanupIntent.setAction(FileCleanupService.ACTION_DELETE_FILES);
- fileCleanupIntent.putExtra(FileCleanupService.EXTRA_FILE_PATHS_TO_DELETE, getFilesToCleanup(profilePath + "/"));
- context.startService(fileCleanupIntent);
- }
-
- private static boolean isCleanupReady(final SharedPreferences sharedPrefs) {
- final long lastCleanupMillis = sharedPrefs.getLong(PREF_LAST_CLEANUP_MILLIS, -1);
- return lastCleanupMillis + MILLIS_BETWEEN_CLEANUPS < System.currentTimeMillis();
- }
-
- private static void recordCleanupScheduled(final SharedPreferences sharedPrefs) {
- final SharedPreferences.Editor editor = sharedPrefs.edit();
- editor.putLong(PREF_LAST_CLEANUP_MILLIS, System.currentTimeMillis()).apply();
- }
-
- @VisibleForTesting
- static ArrayList<String> getFilesToCleanup(final String profilePath) {
- final ArrayList<String> out = new ArrayList<>(PROFILE_FILES_TO_CLEANUP.length);
- for (final String path : PROFILE_FILES_TO_CLEANUP) {
- // Append a file separator, just in-case the caller didn't include one.
- out.add(profilePath + File.separator + path);
- }
- return out;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/cleanup/FileCleanupService.java b/mobile/android/base/java/org/mozilla/gecko/cleanup/FileCleanupService.java
deleted file mode 100644
index 76aff733a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/cleanup/FileCleanupService.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, you can obtain one at http://mozilla.org/MPL/2.0/.
- */
-
-package org.mozilla.gecko.cleanup;
-
-import android.app.IntentService;
-import android.content.Intent;
-import android.util.Log;
-
-import java.io.File;
-import java.util.ArrayList;
-
-/**
- * An IntentService to delete files.
- *
- * It takes an {@link ArrayList} of String file paths to delete via the extra
- * {@link #EXTRA_FILE_PATHS_TO_DELETE}. If these file paths are directories, they will
- * not be traversed recursively and will only be deleted if empty. This is to avoid accidentally
- * trashing a users' profile if a folder is accidentally listed.
- *
- * An IntentService was chosen because:
- * * It generally won't be killed when the Activity is
- * * (unlike HandlerThread) The system handles scheduling, prioritizing,
- * and shutting down the underlying background thread
- * * (unlike an existing background thread) We don't block our background operations
- * for this, which doesn't directly affect the user.
- *
- * The major trade-off is that this Service is very dangerous if it's exported... so don't do that!
- */
-public class FileCleanupService extends IntentService {
- private static final String LOGTAG = "Gecko" + FileCleanupService.class.getSimpleName();
- private static final String WORKER_THREAD_NAME = LOGTAG + "Worker";
-
- public static final String ACTION_DELETE_FILES = "org.mozilla.gecko.intent.action.DELETE_FILES";
- public static final String EXTRA_FILE_PATHS_TO_DELETE = "org.mozilla.gecko.file_paths_to_delete";
-
- public FileCleanupService() {
- super(WORKER_THREAD_NAME);
-
- // We're likely to get scheduled again - let's wait until then in order to avoid:
- // * The coding complexity of re-running this
- // * Consuming system resources: we were probably killed for resource conservation purposes
- setIntentRedelivery(false);
- }
-
- @Override
- protected void onHandleIntent(final Intent intent) {
- if (!isIntentValid(intent)) {
- return;
- }
-
- final ArrayList<String> filesToDelete = intent.getStringArrayListExtra(EXTRA_FILE_PATHS_TO_DELETE);
- for (final String path : filesToDelete) {
- final File file = new File(path);
- file.delete();
- }
- }
-
- private static boolean isIntentValid(final Intent intent) {
- if (intent == null) {
- Log.w(LOGTAG, "Received null intent");
- return false;
- }
-
- if (!intent.getAction().equals(ACTION_DELETE_FILES)) {
- Log.w(LOGTAG, "Received unknown intent action: " + intent.getAction());
- return false;
- }
-
- if (!intent.hasExtra(EXTRA_FILE_PATHS_TO_DELETE)) {
- Log.w(LOGTAG, "Received intent with no files extra");
- return false;
- }
-
- return true;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
deleted file mode 100644
index b1bf567b0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.customtabs;
-
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.support.v7.widget.Toolbar;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.TextView;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.util.ColorUtil;
-import org.mozilla.gecko.util.GeckoRequest;
-import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.lang.reflect.Field;
-
-import static android.support.customtabs.CustomTabsIntent.EXTRA_TOOLBAR_COLOR;
-
-public class CustomTabsActivity extends GeckoApp implements Tabs.OnTabsChangedListener {
- private static final String LOGTAG = "CustomTabsActivity";
- private static final String SAVED_TOOLBAR_COLOR = "SavedToolbarColor";
- private static final String SAVED_TOOLBAR_TITLE = "SavedToolbarTitle";
- private static final int NO_COLOR = -1;
- private Toolbar toolbar;
-
- private ActionBar actionBar;
- private int tabId = -1;
- private boolean useDomainTitle = true;
-
- private int toolbarColor;
- private String toolbarTitle;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- if (savedInstanceState != null) {
- toolbarColor = savedInstanceState.getInt(SAVED_TOOLBAR_COLOR, NO_COLOR);
- toolbarTitle = savedInstanceState.getString(SAVED_TOOLBAR_TITLE, AppConstants.MOZ_APP_BASENAME);
- } else {
- toolbarColor = NO_COLOR;
- toolbarTitle = AppConstants.MOZ_APP_BASENAME;
- }
-
- Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
- updateActionBarWithToolbar(toolbar);
- try {
- // Since we don't create the Toolbar's TextView ourselves, this seems
- // to be the only way of changing the ellipsize setting.
- Field f = toolbar.getClass().getDeclaredField("mTitleTextView");
- f.setAccessible(true);
- TextView textView = (TextView) f.get(toolbar);
- textView.setEllipsize(TextUtils.TruncateAt.START);
- } catch (Exception e) {
- // If we can't ellipsize at the start of the title, we shouldn't display the host
- // so as to avoid displaying a misleadingly truncated host.
- Log.w(LOGTAG, "Failed to get Toolbar TextView, using default title.");
- useDomainTitle = false;
- }
- actionBar = getSupportActionBar();
- actionBar.setTitle(toolbarTitle);
- updateToolbarColor(toolbar);
-
- toolbar.setNavigationOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- onBackPressed();
- }
- });
-
- Tabs.registerOnTabsChangedListener(this);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- Tabs.unregisterOnTabsChangedListener(this);
- }
-
- @Override
- public int getLayout() {
- return R.layout.customtabs_activity;
- }
-
- @Override
- protected void onDone() {
- finish();
- }
-
- @Override
- public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
- if (tab == null) {
- return;
- }
-
- if (tabId >= 0 && tab.getId() != tabId) {
- return;
- }
-
- if (msg == Tabs.TabEvents.LOCATION_CHANGE) {
- tabId = tab.getId();
- final Uri uri = Uri.parse(tab.getURL());
- String title = null;
- if (uri != null) {
- title = uri.getHost();
- }
- if (!useDomainTitle || title == null || title.isEmpty()) {
- toolbarTitle = AppConstants.MOZ_APP_BASENAME;
- } else {
- toolbarTitle = title;
- }
- actionBar.setTitle(toolbarTitle);
- }
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
-
- outState.putInt(SAVED_TOOLBAR_COLOR, toolbarColor);
- outState.putString(SAVED_TOOLBAR_TITLE, toolbarTitle);
- }
-
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- finish();
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- private void updateActionBarWithToolbar(final Toolbar toolbar) {
- setSupportActionBar(toolbar);
- final ActionBar ab = getSupportActionBar();
- if (ab != null) {
- ab.setDisplayHomeAsUpEnabled(true);
- }
- }
-
- private void updateToolbarColor(final Toolbar toolbar) {
- if (toolbarColor == NO_COLOR) {
- final int color = getIntent().getIntExtra(EXTRA_TOOLBAR_COLOR, NO_COLOR);
- if (color == NO_COLOR) {
- return;
- }
- toolbarColor = color;
- }
-
- final int titleTextColor = ColorUtil.getReadableTextColor(toolbarColor);
-
- toolbar.setBackgroundColor(toolbarColor);
- toolbar.setTitleTextColor(titleTextColor);
- final Window window = getWindow();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
- window.setStatusBarColor(ColorUtil.darken(toolbarColor, 0.25));
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/customtabs/GeckoCustomTabsService.java b/mobile/android/base/java/org/mozilla/gecko/customtabs/GeckoCustomTabsService.java
deleted file mode 100644
index 7960f7832..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/GeckoCustomTabsService.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.customtabs;
-
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.customtabs.CustomTabsService;
-import android.support.customtabs.CustomTabsSessionToken;
-import android.util.Log;
-
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.GeckoService;
-
-import java.util.List;
-
-/**
- * Custom tabs service external, third-party apps connect to.
- */
-public class GeckoCustomTabsService extends CustomTabsService {
- private static final String LOGTAG = "GeckoCustomTabsService";
- private static final boolean DEBUG = false;
-
- @Override
- protected boolean updateVisuals(CustomTabsSessionToken sessionToken, Bundle bundle) {
- Log.v(LOGTAG, "updateVisuals()");
-
- return false;
- }
-
- @Override
- protected boolean warmup(long flags) {
- if (DEBUG) {
- Log.v(LOGTAG, "warming up...");
- }
-
- GeckoService.startGecko(GeckoProfile.initFromArgs(this, null), null, getApplicationContext());
-
- return true;
- }
-
- @Override
- protected boolean newSession(CustomTabsSessionToken sessionToken) {
- Log.v(LOGTAG, "newSession()");
-
- // Pretend session has been started
- return true;
- }
-
- @Override
- protected boolean mayLaunchUrl(CustomTabsSessionToken sessionToken, Uri uri, Bundle bundle, List<Bundle> list) {
- Log.v(LOGTAG, "mayLaunchUrl()");
-
- return false;
- }
-
- @Override
- protected Bundle extraCommand(String commandName, Bundle bundle) {
- Log.v(LOGTAG, "extraCommand()");
-
- return null;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/AbstractPerProfileDatabaseProvider.java b/mobile/android/base/java/org/mozilla/gecko/db/AbstractPerProfileDatabaseProvider.java
deleted file mode 100644
index 2e056cc1e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/AbstractPerProfileDatabaseProvider.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import org.mozilla.gecko.annotation.RobocopTarget;
-
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.net.Uri;
-
-/**
- * The base class for ContentProviders that wish to use a different DB
- * for each profile.
- *
- * This class has logic shared between ordinary per-profile CPs and
- * those that wish to share DB connections between CPs.
- */
-public abstract class AbstractPerProfileDatabaseProvider extends AbstractTransactionalProvider {
-
- /**
- * Extend this to provide access to your own map of shared databases. This
- * is a method so that your subclass doesn't collide with others!
- */
- protected abstract PerProfileDatabases<? extends SQLiteOpenHelper> getDatabases();
-
- /*
- * Fetches a readable database based on the profile indicated in the
- * passed URI. If the URI does not contain a profile param, the default profile
- * is used.
- *
- * @param uri content URI optionally indicating the profile of the user
- * @return instance of a readable SQLiteDatabase
- */
- @Override
- protected SQLiteDatabase getReadableDatabase(Uri uri) {
- String profile = null;
- if (uri != null) {
- profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
- }
-
- return getDatabases().getDatabaseHelperForProfile(profile, isTest(uri)).getReadableDatabase();
- }
-
- /*
- * Fetches a writable database based on the profile indicated in the
- * passed URI. If the URI does not contain a profile param, the default profile
- * is used
- *
- * @param uri content URI optionally indicating the profile of the user
- * @return instance of a writable SQLiteDatabase
- */
- @Override
- protected SQLiteDatabase getWritableDatabase(Uri uri) {
- String profile = null;
- if (uri != null) {
- profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
- }
-
- return getDatabases().getDatabaseHelperForProfile(profile, isTest(uri)).getWritableDatabase();
- }
-
- protected SQLiteDatabase getWritableDatabaseForProfile(String profile, boolean isTest) {
- return getDatabases().getDatabaseHelperForProfile(profile, isTest).getWritableDatabase();
- }
-
- /**
- * This method should ONLY be used for testing purposes.
- *
- * @param uri content URI optionally indicating the profile of the user
- * @return instance of a writable SQLiteDatabase
- */
- @Override
- @RobocopTarget
- public SQLiteDatabase getWritableDatabaseForTesting(Uri uri) {
- return getWritableDatabase(uri);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/AbstractTransactionalProvider.java b/mobile/android/base/java/org/mozilla/gecko/db/AbstractTransactionalProvider.java
deleted file mode 100644
index 7e289b76f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/AbstractTransactionalProvider.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import org.mozilla.gecko.AppConstants.Versions;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.SQLException;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.util.Log;
-
-/**
- * This abstract class exists to capture some of the transaction-handling
- * commonalities in Fennec's DB layer.
- *
- * In particular, this abstracts DB access, batching, and a particular
- * transaction approach.
- *
- * That approach is: subclasses implement the abstract methods
- * {@link #insertInTransaction(android.net.Uri, android.content.ContentValues)},
- * {@link #deleteInTransaction(android.net.Uri, String, String[])}, and
- * {@link #updateInTransaction(android.net.Uri, android.content.ContentValues, String, String[])}.
- *
- * These are all called expecting a transaction to be established, so failed
- * modifications can be rolled-back, and work batched.
- *
- * If no transaction is established, that's not a problem. Transaction nesting
- * can be avoided by using {@link #beginWrite(SQLiteDatabase)}.
- *
- * The decision of when to begin a transaction is left to the subclasses,
- * primarily to avoid the pattern of a transaction being begun, a read occurring,
- * and then a write being necessary. This lock upgrade can result in SQLITE_BUSY,
- * which we don't handle well. Better to avoid starting a transaction too soon!
- *
- * You are probably interested in some subclasses:
- *
- * * {@link AbstractPerProfileDatabaseProvider} provides a simple abstraction for
- * querying databases that are stored in the user's profile directory.
- * * {@link PerProfileDatabaseProvider} is a simple version that only allows a
- * single ContentProvider to access each per-profile database.
- * * {@link SharedBrowserDatabaseProvider} is an example of a per-profile provider
- * that allows for multiple providers to safely work with the same databases.
- */
-@SuppressWarnings("javadoc")
-public abstract class AbstractTransactionalProvider extends ContentProvider {
- private static final String LOGTAG = "GeckoTransProvider";
-
- private static final boolean logDebug = Log.isLoggable(LOGTAG, Log.DEBUG);
- private static final boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
-
- protected abstract SQLiteDatabase getReadableDatabase(Uri uri);
- protected abstract SQLiteDatabase getWritableDatabase(Uri uri);
-
- public abstract SQLiteDatabase getWritableDatabaseForTesting(Uri uri);
-
- protected abstract Uri insertInTransaction(Uri uri, ContentValues values);
- protected abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs);
- protected abstract int updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs);
-
- /**
- * Track whether we're in a batch operation.
- *
- * When we're in a batch operation, individual write steps won't even try
- * to start a transaction... and neither will they attempt to finish one.
- *
- * Set this to <code>Boolean.TRUE</code> when you're entering a batch --
- * a section of code in which {@link ContentProvider} methods will be
- * called, but nested transactions should not be started. Callers are
- * responsible for beginning and ending the enclosing transaction, and
- * for setting this to <code>Boolean.FALSE</code> when done.
- *
- * This is a ThreadLocal separate from `db.inTransaction` because batched
- * operations start transactions independent of individual ContentProvider
- * operations. This doesn't work well with the entire concept of this
- * abstract class -- that is, automatically beginning and ending transactions
- * for each insert/delete/update operation -- and doing so without
- * causing arbitrary nesting requires external tracking.
- *
- * Note that beginWrite takes a DB argument, but we don't differentiate
- * between databases in this tracking flag. If your ContentProvider manages
- * multiple database transactions within the same thread, you'll need to
- * amend this scheme -- but then, you're already doing some serious wizardry,
- * so rock on.
- */
- final ThreadLocal<Boolean> isInBatchOperation = new ThreadLocal<Boolean>();
-
- private boolean isInBatch() {
- final Boolean isInBatch = isInBatchOperation.get();
- if (isInBatch == null) {
- return false;
- }
-
- return isInBatch;
- }
-
- /**
- * If we're not currently in a transaction, and we should be, start one.
- */
- protected void beginWrite(final SQLiteDatabase db) {
- if (isInBatch()) {
- trace("Not bothering with an intermediate write transaction: inside batch operation.");
- return;
- }
-
- if (!db.inTransaction()) {
- trace("beginWrite: beginning transaction.");
- db.beginTransaction();
- }
- }
-
- /**
- * If we're not in a batch, but we are in a write transaction, mark it as
- * successful.
- */
- protected void markWriteSuccessful(final SQLiteDatabase db) {
- if (isInBatch()) {
- trace("Not marking write successful: inside batch operation.");
- return;
- }
-
- if (db.inTransaction()) {
- trace("Marking write transaction successful.");
- db.setTransactionSuccessful();
- }
- }
-
- /**
- * If we're not in a batch, but we are in a write transaction,
- * end it.
- *
- * @see PerProfileDatabaseProvider#markWriteSuccessful(SQLiteDatabase)
- */
- protected void endWrite(final SQLiteDatabase db) {
- if (isInBatch()) {
- trace("Not ending write: inside batch operation.");
- return;
- }
-
- if (db.inTransaction()) {
- trace("endWrite: ending transaction.");
- db.endTransaction();
- }
- }
-
- protected void beginBatch(final SQLiteDatabase db) {
- trace("Beginning batch.");
- isInBatchOperation.set(Boolean.TRUE);
- db.beginTransaction();
- }
-
- protected void markBatchSuccessful(final SQLiteDatabase db) {
- if (isInBatch()) {
- trace("Marking batch successful.");
- db.setTransactionSuccessful();
- return;
- }
- Log.w(LOGTAG, "Unexpectedly asked to mark batch successful, but not in batch!");
- throw new IllegalStateException("Not in batch.");
- }
-
- protected void endBatch(final SQLiteDatabase db) {
- trace("Ending batch.");
- db.endTransaction();
- isInBatchOperation.set(Boolean.FALSE);
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- trace("Calling delete on URI: " + uri + ", " + selection + ", " + selectionArgs);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
- int deleted = 0;
-
- try {
- deleted = deleteInTransaction(uri, selection, selectionArgs);
- markWriteSuccessful(db);
- } finally {
- endWrite(db);
- }
-
- if (deleted > 0) {
- final boolean shouldSyncToNetwork = !isCallerSync(uri);
- getContext().getContentResolver().notifyChange(uri, null, shouldSyncToNetwork);
- }
-
- return deleted;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- trace("Calling insert on URI: " + uri);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
- Uri result = null;
- try {
- result = insertInTransaction(uri, values);
- markWriteSuccessful(db);
- } catch (SQLException sqle) {
- Log.e(LOGTAG, "exception in DB operation", sqle);
- } catch (UnsupportedOperationException uoe) {
- Log.e(LOGTAG, "don't know how to perform that insert", uoe);
- } finally {
- endWrite(db);
- }
-
- if (result != null) {
- final boolean shouldSyncToNetwork = !isCallerSync(uri);
- getContext().getContentResolver().notifyChange(uri, null, shouldSyncToNetwork);
- }
-
- return result;
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- trace("Calling update on URI: " + uri + ", " + selection + ", " + selectionArgs);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
- int updated = 0;
-
- try {
- updated = updateInTransaction(uri, values, selection,
- selectionArgs);
- markWriteSuccessful(db);
- } finally {
- endWrite(db);
- }
-
- if (updated > 0) {
- final boolean shouldSyncToNetwork = !isCallerSync(uri);
- getContext().getContentResolver().notifyChange(uri, null, shouldSyncToNetwork);
- }
-
- return updated;
- }
-
- @Override
- public int bulkInsert(Uri uri, ContentValues[] values) {
- if (values == null) {
- return 0;
- }
-
- int numValues = values.length;
- int successes = 0;
-
- final SQLiteDatabase db = getWritableDatabase(uri);
-
- debug("bulkInsert: explicitly starting transaction.");
- beginBatch(db);
-
- try {
- for (int i = 0; i < numValues; i++) {
- insertInTransaction(uri, values[i]);
- successes++;
- }
- trace("Flushing DB bulkinsert...");
- markBatchSuccessful(db);
- } finally {
- debug("bulkInsert: explicitly ending transaction.");
- endBatch(db);
- }
-
- if (successes > 0) {
- final boolean shouldSyncToNetwork = !isCallerSync(uri);
- getContext().getContentResolver().notifyChange(uri, null, shouldSyncToNetwork);
- }
-
- return successes;
- }
-
- /**
- * Indicates whether a query should include deleted fields
- * based on the URI.
- * @param uri query URI
- */
- protected static boolean shouldShowDeleted(Uri uri) {
- String showDeleted = uri.getQueryParameter(BrowserContract.PARAM_SHOW_DELETED);
- return !TextUtils.isEmpty(showDeleted);
- }
-
- /**
- * Indicates whether an insertion should be made if a record doesn't
- * exist, based on the URI.
- * @param uri query URI
- */
- protected static boolean shouldUpdateOrInsert(Uri uri) {
- String insertIfNeeded = uri.getQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED);
- return Boolean.parseBoolean(insertIfNeeded);
- }
-
- /**
- * Indicates whether query is a test based on the URI.
- * @param uri query URI
- */
- protected static boolean isTest(Uri uri) {
- if (uri == null) {
- return false;
- }
- String isTest = uri.getQueryParameter(BrowserContract.PARAM_IS_TEST);
- return !TextUtils.isEmpty(isTest);
- }
-
- /**
- * Return true of the query is from Firefox Sync.
- * @param uri query URI
- */
- protected static boolean isCallerSync(Uri uri) {
- String isSync = uri.getQueryParameter(BrowserContract.PARAM_IS_SYNC);
- return !TextUtils.isEmpty(isSync);
- }
-
- protected static void trace(String message) {
- if (logVerbose) {
- Log.v(LOGTAG, message);
- }
- }
-
- protected static void debug(String message) {
- if (logDebug) {
- Log.d(LOGTAG, message);
- }
- }
-} \ No newline at end of file
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/BaseTable.java b/mobile/android/base/java/org/mozilla/gecko/db/BaseTable.java
deleted file mode 100644
index 418d547ed..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/BaseTable.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.util.Log;
-
-// BaseTable provides a basic implementation of a Table for tables that don't require advanced operations during
-// insert, delete, update, or query operations. Implementors must still provide onCreate and onUpgrade operations.
-public abstract class BaseTable implements Table {
- private static final String LOGTAG = "GeckoBaseTable";
-
- private static final boolean DEBUG = false;
-
- protected static void log(String msg) {
- if (DEBUG) {
- Log.i(LOGTAG, msg);
- }
- }
-
- // Table implementation
- @Override
- public Table.ContentProviderInfo[] getContentProviderInfo() {
- return new Table.ContentProviderInfo[0];
- }
-
- // Returns the name of the table to modify/query
- protected abstract String getTable();
-
- // Table implementation
- @Override
- public Cursor query(SQLiteDatabase db, Uri uri, int dbId, String[] columns, String selection, String[] selectionArgs, String sortOrder, String groupBy, String limit) {
- Cursor c = db.query(getTable(), columns, selection, selectionArgs, groupBy, null, sortOrder, limit);
- log("query " + columns + " in " + selection + " = " + c);
- return c;
- }
-
- @Override
- public int update(SQLiteDatabase db, Uri uri, int dbId, ContentValues values, String selection, String[] selectionArgs) {
- int updated = db.updateWithOnConflict(getTable(), values, selection, selectionArgs, SQLiteDatabase.CONFLICT_REPLACE);
- log("update " + values + " in " + selection + " = " + updated);
- return updated;
- }
-
- @Override
- public long insert(SQLiteDatabase db, Uri uri, int dbId, ContentValues values) {
- long inserted = db.insertOrThrow(getTable(), null, values);
- log("insert " + values + " = " + inserted);
- return inserted;
- }
-
- @Override
- public int delete(SQLiteDatabase db, Uri uri, int dbId, String selection, String[] selectionArgs) {
- int deleted = db.delete(getTable(), selection, selectionArgs);
- log("delete " + selection + " = " + deleted);
- return deleted;
- }
-};
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java b/mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java
deleted file mode 100644
index 51c8d964f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java
+++ /dev/null
@@ -1,785 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import org.mozilla.gecko.AppConstants;
-
-import android.net.Uri;
-import android.support.annotation.NonNull;
-
-import org.mozilla.gecko.annotation.RobocopTarget;
-
-@RobocopTarget
-public class BrowserContract {
- public static final String AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.browser";
- public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
-
- public static final String PASSWORDS_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.passwords";
- public static final Uri PASSWORDS_AUTHORITY_URI = Uri.parse("content://" + PASSWORDS_AUTHORITY);
-
- public static final String FORM_HISTORY_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.formhistory";
- public static final Uri FORM_HISTORY_AUTHORITY_URI = Uri.parse("content://" + FORM_HISTORY_AUTHORITY);
-
- public static final String TABS_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.tabs";
- public static final Uri TABS_AUTHORITY_URI = Uri.parse("content://" + TABS_AUTHORITY);
-
- public static final String HOME_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.home";
- public static final Uri HOME_AUTHORITY_URI = Uri.parse("content://" + HOME_AUTHORITY);
-
- public static final String PROFILES_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".profiles";
- public static final Uri PROFILES_AUTHORITY_URI = Uri.parse("content://" + PROFILES_AUTHORITY);
-
- public static final String READING_LIST_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.readinglist";
- public static final Uri READING_LIST_AUTHORITY_URI = Uri.parse("content://" + READING_LIST_AUTHORITY);
-
- public static final String SEARCH_HISTORY_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.searchhistory";
- public static final Uri SEARCH_HISTORY_AUTHORITY_URI = Uri.parse("content://" + SEARCH_HISTORY_AUTHORITY);
-
- public static final String LOGINS_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.logins";
- public static final Uri LOGINS_AUTHORITY_URI = Uri.parse("content://" + LOGINS_AUTHORITY);
-
- public static final String PARAM_PROFILE = "profile";
- public static final String PARAM_PROFILE_PATH = "profilePath";
- public static final String PARAM_LIMIT = "limit";
- public static final String PARAM_SUGGESTEDSITES_LIMIT = "suggestedsites_limit";
- public static final String PARAM_TOPSITES_DISABLE_PINNED = "topsites_disable_pinned";
- public static final String PARAM_IS_SYNC = "sync";
- public static final String PARAM_SHOW_DELETED = "show_deleted";
- public static final String PARAM_IS_TEST = "test";
- public static final String PARAM_INSERT_IF_NEEDED = "insert_if_needed";
- public static final String PARAM_INCREMENT_VISITS = "increment_visits";
- public static final String PARAM_INCREMENT_REMOTE_AGGREGATES = "increment_remote_aggregates";
- public static final String PARAM_EXPIRE_PRIORITY = "priority";
- public static final String PARAM_DATASET_ID = "dataset_id";
- public static final String PARAM_GROUP_BY = "group_by";
-
- static public enum ExpirePriority {
- NORMAL,
- AGGRESSIVE
- }
-
- /**
- * Produces a SQL expression used for sorting results of the "combined" view by frecency.
- * Combines remote and local frecency calculations, weighting local visits much heavier.
- *
- * @param includesBookmarks When URL is bookmarked, should we give it bonus frecency points?
- * @param ascending Indicates if sorting order ascending
- * @return Combined frecency sorting expression
- */
- static public String getCombinedFrecencySortOrder(boolean includesBookmarks, boolean ascending) {
- final long now = System.currentTimeMillis();
- StringBuilder order = new StringBuilder(getRemoteFrecencySQL(now) + " + " + getLocalFrecencySQL(now));
-
- if (includesBookmarks) {
- order.insert(0, "(CASE WHEN " + Combined.BOOKMARK_ID + " > -1 THEN 100 ELSE 0 END) + ");
- }
-
- order.append(ascending ? " ASC" : " DESC");
- return order.toString();
- }
-
- /**
- * See Bug 1265525 for details (explanation + graphs) on how Remote frecency compares to Local frecency for different
- * combinations of visits count and age.
- *
- * @param now Base time in milliseconds for age calculation
- * @return remote frecency SQL calculation
- */
- static public String getRemoteFrecencySQL(final long now) {
- return getFrecencyCalculation(now, 1, 110, Combined.REMOTE_VISITS_COUNT, Combined.REMOTE_DATE_LAST_VISITED);
- }
-
- /**
- * Local frecency SQL calculation. Note higher scale factor and squared visit count which achieve
- * visits generated locally being much preferred over remote visits.
- * See Bug 1265525 for details (explanation + comparison graphs).
- *
- * @param now Base time in milliseconds for age calculation
- * @return local frecency SQL calculation
- */
- static public String getLocalFrecencySQL(final long now) {
- String visitCountExpr = "(" + Combined.LOCAL_VISITS_COUNT + " + 2)";
- visitCountExpr = visitCountExpr + " * " + visitCountExpr;
-
- return getFrecencyCalculation(now, 2, 225, visitCountExpr, Combined.LOCAL_DATE_LAST_VISITED);
- }
-
- /**
- * Our version of frecency is computed by scaling the number of visits by a multiplier
- * that approximates Gaussian decay, based on how long ago the entry was last visited.
- * Since we're limited by the math we can do with sqlite, we're calculating this
- * approximation using the Cauchy distribution: multiplier = scale_const / (age^2 + scale_const).
- * For example, with 15 as our scale parameter, we get a scale constant 15^2 = 225. Then:
- * frecencyScore = numVisits * max(1, 100 * 225 / (age*age + 225)). (See bug 704977)
- *
- * @param now Base time in milliseconds for age calculation
- * @param minFrecency Minimum allowed frecency value
- * @param multiplier Scale constant
- * @param visitCountExpr Expression which will produce a visit count
- * @param lastVisitExpr Expression which will produce "last-visited" timestamp
- * @return Frecency SQL calculation
- */
- static public String getFrecencyCalculation(final long now, final int minFrecency, final int multiplier, @NonNull final String visitCountExpr, @NonNull final String lastVisitExpr) {
- final long nowInMicroseconds = now * 1000;
- final long microsecondsPerDay = 86400000000L;
- final String ageExpr = "(" + nowInMicroseconds + " - " + lastVisitExpr + ") / " + microsecondsPerDay;
-
- return visitCountExpr + " * MAX(" + minFrecency + ", 100 * " + multiplier + " / (" + ageExpr + " * " + ageExpr + " + " + multiplier + "))";
- }
-
- @RobocopTarget
- public interface CommonColumns {
- public static final String _ID = "_id";
- }
-
- @RobocopTarget
- public interface DateSyncColumns {
- public static final String DATE_CREATED = "created";
- public static final String DATE_MODIFIED = "modified";
- }
-
- @RobocopTarget
- public interface SyncColumns extends DateSyncColumns {
- public static final String GUID = "guid";
- public static final String IS_DELETED = "deleted";
- }
-
- @RobocopTarget
- public interface URLColumns {
- public static final String URL = "url";
- public static final String TITLE = "title";
- }
-
- @RobocopTarget
- public interface FaviconColumns {
- public static final String FAVICON = "favicon";
- public static final String FAVICON_ID = "favicon_id";
- public static final String FAVICON_URL = "favicon_url";
- }
-
- @RobocopTarget
- public interface HistoryColumns {
- public static final String DATE_LAST_VISITED = "date";
- public static final String VISITS = "visits";
- // Aggregates used to speed up top sites and search frecency-powered queries
- public static final String LOCAL_VISITS = "visits_local";
- public static final String REMOTE_VISITS = "visits_remote";
- public static final String LOCAL_DATE_LAST_VISITED = "date_local";
- public static final String REMOTE_DATE_LAST_VISITED = "date_remote";
- }
-
- @RobocopTarget
- public interface VisitsColumns {
- public static final String HISTORY_GUID = "history_guid";
- public static final String VISIT_TYPE = "visit_type";
- public static final String DATE_VISITED = "date";
- // Used to distinguish between visits that were generated locally vs those that came in from Sync.
- // Since we don't track "origin clientID" for visits, this is the best we can do for now.
- public static final String IS_LOCAL = "is_local";
- }
-
- public interface PageMetadataColumns {
- public static final String HISTORY_GUID = "history_guid";
- public static final String DATE_CREATED = "created";
- public static final String HAS_IMAGE = "has_image";
- public static final String JSON = "json";
- }
-
- public interface DeletedColumns {
- public static final String ID = "id";
- public static final String GUID = "guid";
- public static final String TIME_DELETED = "timeDeleted";
- }
-
- @RobocopTarget
- public static final class Favicons implements CommonColumns, DateSyncColumns {
- private Favicons() {}
-
- public static final String TABLE_NAME = "favicons";
-
- public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "favicons");
-
- public static final String URL = "url";
- public static final String DATA = "data";
- public static final String PAGE_URL = "page_url";
- }
-
- @RobocopTarget
- public static final class Thumbnails implements CommonColumns {
- private Thumbnails() {}
-
- public static final String TABLE_NAME = "thumbnails";
-
- public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "thumbnails");
-
- public static final String URL = "url";
- public static final String DATA = "data";
- }
-
- public static final class Profiles {
- private Profiles() {}
- public static final String NAME = "name";
- public static final String PATH = "path";
- }
-
- @RobocopTarget
- public static final class Bookmarks implements CommonColumns, URLColumns, FaviconColumns, SyncColumns {
- private Bookmarks() {}
-
- public static final String TABLE_NAME = "bookmarks";
-
- public static final String VIEW_WITH_FAVICONS = "bookmarks_with_favicons";
-
- public static final String VIEW_WITH_ANNOTATIONS = "bookmarks_with_annotations";
-
- public static final int FIXED_ROOT_ID = 0;
- public static final int FAKE_DESKTOP_FOLDER_ID = -1;
- public static final int FIXED_READING_LIST_ID = -2;
- public static final int FIXED_PINNED_LIST_ID = -3;
- public static final int FIXED_SCREENSHOT_FOLDER_ID = -4;
- public static final int FAKE_READINGLIST_SMARTFOLDER_ID = -5;
-
- /**
- * This ID and the following negative IDs are reserved for bookmarks from Android's partner
- * bookmark provider.
- */
- public static final long FAKE_PARTNER_BOOKMARKS_START = -1000;
-
- public static final String MOBILE_FOLDER_GUID = "mobile";
- public static final String PLACES_FOLDER_GUID = "places";
- public static final String MENU_FOLDER_GUID = "menu";
- public static final String TAGS_FOLDER_GUID = "tags";
- public static final String TOOLBAR_FOLDER_GUID = "toolbar";
- public static final String UNFILED_FOLDER_GUID = "unfiled";
- public static final String FAKE_DESKTOP_FOLDER_GUID = "desktop";
- public static final String PINNED_FOLDER_GUID = "pinned";
- public static final String SCREENSHOT_FOLDER_GUID = "screenshots";
- public static final String FAKE_READINGLIST_SMARTFOLDER_GUID = "readinglist";
-
- public static final int TYPE_FOLDER = 0;
- public static final int TYPE_BOOKMARK = 1;
- public static final int TYPE_SEPARATOR = 2;
- public static final int TYPE_LIVEMARK = 3;
- public static final int TYPE_QUERY = 4;
-
- public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "bookmarks");
- public static final Uri PARENTS_CONTENT_URI = Uri.withAppendedPath(CONTENT_URI, "parents");
- // Hacky API for bulk-updating positions. Bug 728783.
- public static final Uri POSITIONS_CONTENT_URI = Uri.withAppendedPath(CONTENT_URI, "positions");
- public static final long DEFAULT_POSITION = Long.MIN_VALUE;
-
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/bookmark";
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/bookmark";
- public static final String TYPE = "type";
- public static final String PARENT = "parent";
- public static final String POSITION = "position";
- public static final String TAGS = "tags";
- public static final String DESCRIPTION = "description";
- public static final String KEYWORD = "keyword";
-
- public static final String ANNOTATION_KEY = "annotation_key";
- public static final String ANNOTATION_VALUE = "annotation_value";
- }
-
- @RobocopTarget
- public static final class History implements CommonColumns, URLColumns, HistoryColumns, FaviconColumns, SyncColumns {
- private History() {}
-
- public static final String TABLE_NAME = "history";
-
- public static final String VIEW_WITH_FAVICONS = "history_with_favicons";
-
- public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "history");
- public static final Uri CONTENT_OLD_URI = Uri.withAppendedPath(AUTHORITY_URI, "history/old");
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/browser-history";
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/browser-history";
- }
-
- @RobocopTarget
- public static final class Visits implements CommonColumns, VisitsColumns {
- private Visits() {}
-
- public static final String TABLE_NAME = "visits";
-
- public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "visits");
-
- public static final int VISIT_IS_LOCAL = 1;
- public static final int VISIT_IS_REMOTE = 0;
- }
-
- // Combined bookmarks and history
- @RobocopTarget
- public static final class Combined implements CommonColumns, URLColumns, HistoryColumns, FaviconColumns {
- private Combined() {}
-
- public static final String VIEW_NAME = "combined";
-
- public static final String VIEW_WITH_FAVICONS = "combined_with_favicons";
-
- public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "combined");
-
- public static final String BOOKMARK_ID = "bookmark_id";
- public static final String HISTORY_ID = "history_id";
-
- public static final String REMOTE_VISITS_COUNT = "remoteVisitCount";
- public static final String REMOTE_DATE_LAST_VISITED = "remoteDateLastVisited";
-
- public static final String LOCAL_VISITS_COUNT = "localVisitCount";
- public static final String LOCAL_DATE_LAST_VISITED = "localDateLastVisited";
- }
-
- public static final class Schema {
- private Schema() {}
- public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "schema");
-
- public static final String VERSION = "version";
- }
-
- public static final class Passwords {
- private Passwords() {}
- public static final Uri CONTENT_URI = Uri.withAppendedPath(PASSWORDS_AUTHORITY_URI, "passwords");
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/passwords";
-
- public static final String ID = "id";
- public static final String HOSTNAME = "hostname";
- public static final String HTTP_REALM = "httpRealm";
- public static final String FORM_SUBMIT_URL = "formSubmitURL";
- public static final String USERNAME_FIELD = "usernameField";
- public static final String PASSWORD_FIELD = "passwordField";
- public static final String ENCRYPTED_USERNAME = "encryptedUsername";
- public static final String ENCRYPTED_PASSWORD = "encryptedPassword";
- public static final String ENC_TYPE = "encType";
- public static final String TIME_CREATED = "timeCreated";
- public static final String TIME_LAST_USED = "timeLastUsed";
- public static final String TIME_PASSWORD_CHANGED = "timePasswordChanged";
- public static final String TIMES_USED = "timesUsed";
- public static final String GUID = "guid";
-
- // This needs to be kept in sync with the types defined in toolkit/components/passwordmgr/nsILoginManagerCrypto.idl#45
- public static final int ENCTYPE_SDR = 1;
- }
-
- public static final class DeletedPasswords implements DeletedColumns {
- private DeletedPasswords() {}
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/deleted-passwords";
- public static final Uri CONTENT_URI = Uri.withAppendedPath(PASSWORDS_AUTHORITY_URI, "deleted-passwords");
- }
-
- @RobocopTarget
- public static final class GeckoDisabledHosts {
- private GeckoDisabledHosts() {}
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/disabled-hosts";
- public static final Uri CONTENT_URI = Uri.withAppendedPath(PASSWORDS_AUTHORITY_URI, "disabled-hosts");
-
- public static final String HOSTNAME = "hostname";
- }
-
- public static final class FormHistory {
- private FormHistory() {}
- public static final Uri CONTENT_URI = Uri.withAppendedPath(FORM_HISTORY_AUTHORITY_URI, "formhistory");
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/formhistory";
-
- public static final String ID = "id";
- public static final String FIELD_NAME = "fieldname";
- public static final String VALUE = "value";
- public static final String TIMES_USED = "timesUsed";
- public static final String FIRST_USED = "firstUsed";
- public static final String LAST_USED = "lastUsed";
- public static final String GUID = "guid";
- }
-
- public static final class DeletedFormHistory implements DeletedColumns {
- private DeletedFormHistory() {}
- public static final Uri CONTENT_URI = Uri.withAppendedPath(FORM_HISTORY_AUTHORITY_URI, "deleted-formhistory");
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/deleted-formhistory";
- }
-
- @RobocopTarget
- public static final class Tabs implements CommonColumns {
- private Tabs() {}
- public static final String TABLE_NAME = "tabs";
-
- public static final Uri CONTENT_URI = Uri.withAppendedPath(TABS_AUTHORITY_URI, "tabs");
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/tab";
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/tab";
-
- // Title of the tab.
- public static final String TITLE = "title";
-
- // Topmost URL from the history array. Allows processing of this tab without
- // parsing that array.
- public static final String URL = "url";
-
- // Sync-assigned GUID for client device. NULL for local tabs.
- public static final String CLIENT_GUID = "client_guid";
-
- // JSON-encoded array of history URL strings, from most recent to least recent.
- public static final String HISTORY = "history";
-
- // Favicon URL for the tab's topmost history entry.
- public static final String FAVICON = "favicon";
-
- // Last used time of the tab.
- public static final String LAST_USED = "last_used";
-
- // Position of the tab. 0 represents foreground.
- public static final String POSITION = "position";
- }
-
- public static final class Clients implements CommonColumns {
- private Clients() {}
- public static final Uri CONTENT_RECENCY_URI = Uri.withAppendedPath(TABS_AUTHORITY_URI, "clients_recency");
- public static final Uri CONTENT_URI = Uri.withAppendedPath(TABS_AUTHORITY_URI, "clients");
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/client";
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/client";
-
- // Client-provided name string. Could conceivably be null.
- public static final String NAME = "name";
-
- // Sync-assigned GUID for client device. NULL for local tabs.
- public static final String GUID = "guid";
-
- // Last modified time for the client's tab record. For remote records, a server
- // timestamp provided by Sync during insertion.
- public static final String LAST_MODIFIED = "last_modified";
-
- public static final String DEVICE_TYPE = "device_type";
- }
-
- // Data storage for dynamic panels on about:home
- @RobocopTarget
- public static final class HomeItems implements CommonColumns {
- private HomeItems() {}
- public static final Uri CONTENT_FAKE_URI = Uri.withAppendedPath(HOME_AUTHORITY_URI, "items/fake");
- public static final Uri CONTENT_URI = Uri.withAppendedPath(HOME_AUTHORITY_URI, "items");
-
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/homeitem";
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/homeitem";
-
- public static final String DATASET_ID = "dataset_id";
- public static final String URL = "url";
- public static final String TITLE = "title";
- public static final String DESCRIPTION = "description";
- public static final String IMAGE_URL = "image_url";
- public static final String BACKGROUND_COLOR = "background_color";
- public static final String BACKGROUND_URL = "background_url";
- public static final String CREATED = "created";
- public static final String FILTER = "filter";
-
- public static final String[] DEFAULT_PROJECTION =
- new String[] { _ID, DATASET_ID, URL, TITLE, DESCRIPTION, IMAGE_URL, BACKGROUND_COLOR, BACKGROUND_URL, FILTER };
- }
-
- @RobocopTarget
- public static final class ReadingListItems implements CommonColumns, URLColumns {
- public static final String EXCERPT = "excerpt";
- public static final String CLIENT_LAST_MODIFIED = "client_last_modified";
- public static final String GUID = "guid";
- public static final String SERVER_LAST_MODIFIED = "last_modified";
- public static final String SERVER_STORED_ON = "stored_on";
- public static final String ADDED_ON = "added_on";
- public static final String MARKED_READ_ON = "marked_read_on";
- public static final String IS_DELETED = "is_deleted";
- public static final String IS_ARCHIVED = "is_archived";
- public static final String IS_UNREAD = "is_unread";
- public static final String IS_ARTICLE = "is_article";
- public static final String IS_FAVORITE = "is_favorite";
- public static final String RESOLVED_URL = "resolved_url";
- public static final String RESOLVED_TITLE = "resolved_title";
- public static final String ADDED_BY = "added_by";
- public static final String MARKED_READ_BY = "marked_read_by";
- public static final String WORD_COUNT = "word_count";
- public static final String READ_POSITION = "read_position";
- public static final String CONTENT_STATUS = "content_status";
-
- public static final String SYNC_STATUS = "sync_status";
- public static final String SYNC_CHANGE_FLAGS = "sync_change_flags";
-
- private ReadingListItems() {}
- public static final Uri CONTENT_URI = Uri.withAppendedPath(READING_LIST_AUTHORITY_URI, "items");
-
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/readinglistitem";
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/readinglistitem";
-
- // CONTENT_STATUS represents the result of an attempt to fetch content for the reading list item.
- public static final int STATUS_UNFETCHED = 0;
- public static final int STATUS_FETCH_FAILED_TEMPORARY = 1;
- public static final int STATUS_FETCH_FAILED_PERMANENT = 2;
- public static final int STATUS_FETCH_FAILED_UNSUPPORTED_FORMAT = 3;
- public static final int STATUS_FETCHED_ARTICLE = 4;
-
- // See https://github.com/mozilla-services/readinglist/wiki/Client-phases for how this is expected to work.
- //
- // If an item is SYNCED, it doesn't need to be uploaded.
- //
- // If its status is NEW, the entire record should be uploaded.
- //
- // If DELETED, the record should be deleted. A record can only move into this state from SYNCED; NEW records
- // are deleted immediately.
- //
-
- public static final int SYNC_STATUS_SYNCED = 0;
- public static final int SYNC_STATUS_NEW = 1; // Upload everything.
- public static final int SYNC_STATUS_DELETED = 2; // Delete the record from the server.
- public static final int SYNC_STATUS_MODIFIED = 3; // Consult SYNC_CHANGE_FLAGS.
-
- // SYNC_CHANGE_FLAG represents the sets of fields that need to be uploaded.
- // If its status is only UNREAD_CHANGED (and maybe FAVORITE_CHANGED?), then it can easily be uploaded
- // in a fire-and-forget manner. This change can never conflict.
- //
- // If its status is RESOLVED, then one or more of the content-oriented fields has changed, and a full
- // upload of those fields should occur. These can result in conflicts.
- //
- // Note that these are flags; they should be considered together when deciding on a course of action.
- //
- // These flags are meaningless for records in any state other than SYNCED. They can be safely altered in
- // other states (to avoid having to query to pre-fill a ContentValues), but should be ignored.
- public static final int SYNC_CHANGE_NONE = 0;
- public static final int SYNC_CHANGE_UNREAD_CHANGED = 1 << 0; // => marked_read_{on,by}, is_unread
- public static final int SYNC_CHANGE_FAVORITE_CHANGED = 1 << 1; // => is_favorite
- public static final int SYNC_CHANGE_RESOLVED = 1 << 2; // => is_article, resolved_{url,title}, excerpt, word_count
-
-
- public static final String DEFAULT_SORT_ORDER = CLIENT_LAST_MODIFIED + " DESC";
- public static final String[] DEFAULT_PROJECTION = new String[] { _ID, URL, TITLE, EXCERPT, WORD_COUNT, IS_UNREAD };
-
- // Minimum fields required to create a reading list item.
- public static final String[] REQUIRED_FIELDS = { ReadingListItems.URL, ReadingListItems.TITLE };
-
- // All fields that might be mapped from the DB into a record object.
- public static final String[] ALL_FIELDS = {
- CommonColumns._ID,
- URLColumns.URL,
- URLColumns.TITLE,
- EXCERPT,
- CLIENT_LAST_MODIFIED,
- GUID,
- SERVER_LAST_MODIFIED,
- SERVER_STORED_ON,
- ADDED_ON,
- MARKED_READ_ON,
- IS_DELETED,
- IS_ARCHIVED,
- IS_UNREAD,
- IS_ARTICLE,
- IS_FAVORITE,
- RESOLVED_URL,
- RESOLVED_TITLE,
- ADDED_BY,
- MARKED_READ_BY,
- WORD_COUNT,
- READ_POSITION,
- CONTENT_STATUS,
-
- SYNC_STATUS,
- SYNC_CHANGE_FLAGS,
- };
-
- public static final String TABLE_NAME = "reading_list";
- }
-
- @RobocopTarget
- public static final class TopSites implements CommonColumns, URLColumns {
- private TopSites() {}
-
- public static final int TYPE_BLANK = 0;
- public static final int TYPE_TOP = 1;
- public static final int TYPE_PINNED = 2;
- public static final int TYPE_SUGGESTED = 3;
-
- public static final String BOOKMARK_ID = "bookmark_id";
- public static final String HISTORY_ID = "history_id";
- public static final String TYPE = "type";
-
- public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "topsites");
- }
-
- public static final class Highlights {
- public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "highlights");
-
- public static final String DATE = "date";
- }
-
- @RobocopTarget
- public static final class SearchHistory implements CommonColumns, HistoryColumns {
- private SearchHistory() {}
-
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/searchhistory";
- public static final String QUERY = "query";
- public static final String DATE = "date";
- public static final String TABLE_NAME = "searchhistory";
-
- public static final Uri CONTENT_URI = Uri.withAppendedPath(SEARCH_HISTORY_AUTHORITY_URI, "searchhistory");
- }
-
- @RobocopTarget
- public static final class SuggestedSites implements CommonColumns, URLColumns {
- private SuggestedSites() {}
-
- public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "suggestedsites");
- }
-
- public static final class ActivityStreamBlocklist implements CommonColumns {
- private ActivityStreamBlocklist() {}
-
- public static final String TABLE_NAME = "activity_stream_blocklist";
- public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, TABLE_NAME);
-
- public static final String URL = "url";
- public static final String CREATED = "created";
- }
-
- @RobocopTarget
- public static final class UrlAnnotations implements CommonColumns, DateSyncColumns {
- private UrlAnnotations() {}
-
- public static final String TABLE_NAME = "urlannotations";
- public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, TABLE_NAME);
-
- public static final String URL = "url";
- public static final String KEY = "key";
- public static final String VALUE = "value";
- public static final String SYNC_STATUS = "sync_status";
-
- public enum Key {
- // We use a parameter, rather than name(), as defensive coding: we can't let the
- // enum name change because we've already stored values into the DB.
- SCREENSHOT ("screenshot"),
-
- /**
- * This key maps URLs to its feeds.
- *
- * Key: feed
- * Value: URL of feed
- */
- FEED("feed"),
-
- /**
- * This key maps URLs of feeds to an object describing the feed.
- *
- * Key: feed_subscription
- * Value: JSON object describing feed
- */
- FEED_SUBSCRIPTION("feed_subscription"),
-
- /**
- * Indicates that this URL (if stored as a bookmark) should be opened into reader view.
- *
- * Key: reader_view
- * Value: String "true" to indicate that we would like to open into reader view.
- */
- READER_VIEW("reader_view"),
-
- /**
- * Indicator that the user interacted with the URL in regards to home screen shortcuts.
- *
- * Key: home_screen_shortcut
- * Value: True: User created an home screen shortcut for this URL
- * False: User declined to create a shortcut for this URL
- */
- HOME_SCREEN_SHORTCUT("home_screen_shortcut");
-
- private final String dbValue;
-
- Key(final String dbValue) { this.dbValue = dbValue; }
- public String getDbValue() { return dbValue; }
- }
-
- public enum SyncStatus {
- // We use a parameter, rather than ordinal(), as defensive coding: we can't let the
- // ordinal values change because we've already stored values into the DB.
- NEW (0);
-
- // Value stored into the database for this column.
- private final int dbValue;
-
- SyncStatus(final int dbValue) {
- this.dbValue = dbValue;
- }
-
- public int getDBValue() { return dbValue; }
- }
-
- /**
- * Value used to indicate that a reader view item is saved. We use the
- */
- public static final String READER_VIEW_SAVED_VALUE = "true";
- }
-
- public static final class Numbers {
- private Numbers() {}
-
- public static final String TABLE_NAME = "numbers";
-
- public static final String POSITION = "position";
-
- public static final int MAX_VALUE = 50;
- }
-
- @RobocopTarget
- public static final class Logins implements CommonColumns {
- private Logins() {}
-
- public static final Uri CONTENT_URI = Uri.withAppendedPath(LOGINS_AUTHORITY_URI, "logins");
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/logins";
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/logins";
- public static final String TABLE_LOGINS = "logins";
-
- public static final String HOSTNAME = "hostname";
- public static final String HTTP_REALM = "httpRealm";
- public static final String FORM_SUBMIT_URL = "formSubmitURL";
- public static final String USERNAME_FIELD = "usernameField";
- public static final String PASSWORD_FIELD = "passwordField";
- public static final String ENCRYPTED_USERNAME = "encryptedUsername";
- public static final String ENCRYPTED_PASSWORD = "encryptedPassword";
- public static final String ENC_TYPE = "encType";
- public static final String TIME_CREATED = "timeCreated";
- public static final String TIME_LAST_USED = "timeLastUsed";
- public static final String TIME_PASSWORD_CHANGED = "timePasswordChanged";
- public static final String TIMES_USED = "timesUsed";
- public static final String GUID = "guid";
- }
-
- @RobocopTarget
- public static final class DeletedLogins implements CommonColumns {
- private DeletedLogins() {}
-
- public static final Uri CONTENT_URI = Uri.withAppendedPath(LOGINS_AUTHORITY_URI, "deleted-logins");
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/deleted-logins";
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/deleted-logins";
- public static final String TABLE_DELETED_LOGINS = "deleted_logins";
-
- public static final String GUID = "guid";
- public static final String TIME_DELETED = "timeDeleted";
- }
-
- @RobocopTarget
- public static final class LoginsDisabledHosts implements CommonColumns {
- private LoginsDisabledHosts() {}
-
- public static final Uri CONTENT_URI = Uri.withAppendedPath(LOGINS_AUTHORITY_URI, "logins-disabled-hosts");
- public static final String CONTENT_TYPE = "vnd.android.cursor.dir/logins-disabled-hosts";
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/logins-disabled-hosts";
- public static final String TABLE_DISABLED_HOSTS = "logins_disabled_hosts";
-
- public static final String HOSTNAME = "hostname";
- }
-
- @RobocopTarget
- public static final class PageMetadata implements CommonColumns, PageMetadataColumns {
- private PageMetadata() {}
-
- public static final String TABLE_NAME = "page_metadata";
- public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "page_metadata");
- }
-
- // We refer to the service by name to decouple services from the rest of the code base.
- public static final String TAB_RECEIVED_SERVICE_CLASS_NAME = "org.mozilla.gecko.tabqueue.TabReceivedService";
-
- public static final String SKIP_TAB_QUEUE_FLAG = "skip_tab_queue";
-
- public static final String EXTRA_CLIENT_GUID = "org.mozilla.gecko.extra.CLIENT_ID";
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/BrowserDB.java b/mobile/android/base/java/org/mozilla/gecko/db/BrowserDB.java
deleted file mode 100644
index 4219e45b1..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserDB.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.List;
-
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.db.BrowserContract.ExpirePriority;
-import org.mozilla.gecko.distribution.Distribution;
-import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
-
-import android.content.ContentProviderClient;
-import android.content.ContentProviderOperation;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.graphics.drawable.BitmapDrawable;
-import android.support.v4.content.CursorLoader;
-
-/**
- * Interface for interactions with all databases. If you want an instance
- * that implements this, you should go through GeckoProfile. E.g.,
- * <code>BrowserDB.from(context)</code>.
- */
-public abstract class BrowserDB {
- public static enum FilterFlags {
- EXCLUDE_PINNED_SITES
- }
-
- public abstract Searches getSearches();
- public abstract TabsAccessor getTabsAccessor();
- public abstract URLMetadata getURLMetadata();
- @RobocopTarget public abstract UrlAnnotations getUrlAnnotations();
-
- /**
- * Add default bookmarks to the database.
- * Takes an offset; returns a new offset.
- */
- public abstract int addDefaultBookmarks(Context context, ContentResolver cr, int offset);
-
- /**
- * Add bookmarks from the provided distribution.
- * Takes an offset; returns a new offset.
- */
- public abstract int addDistributionBookmarks(ContentResolver cr, Distribution distribution, int offset);
-
- /**
- * Invalidate cached data.
- */
- public abstract void invalidate();
-
- public abstract int getCount(ContentResolver cr, String database);
-
- /**
- * @return a cursor representing the contents of the DB filtered according to the arguments.
- * Can return <code>null</code>. <code>CursorLoader</code> will handle this correctly.
- */
- public abstract Cursor filter(ContentResolver cr, CharSequence constraint,
- int limit, EnumSet<BrowserDB.FilterFlags> flags);
-
- /**
- * @return a cursor over top sites (high-ranking bookmarks and history).
- * Can return <code>null</code>.
- * Returns no more than <code>limit</code> results.
- * Suggested sites will be limited to being within the first <code>suggestedRangeLimit</code> results.
- */
- public abstract Cursor getTopSites(ContentResolver cr, int suggestedRangeLimit, int limit);
-
- public abstract CursorLoader getActivityStreamTopSites(Context context, int limit);
-
- public abstract void updateVisitedHistory(ContentResolver cr, String uri);
-
- public abstract void updateHistoryTitle(ContentResolver cr, String uri, String title);
-
- /**
- * Can return <code>null</code>.
- */
- public abstract Cursor getAllVisitedHistory(ContentResolver cr);
-
- /**
- * Can return <code>null</code>.
- */
- public abstract Cursor getRecentHistory(ContentResolver cr, int limit);
-
- public abstract Cursor getHistoryForURL(ContentResolver cr, String uri);
-
- public abstract Cursor getRecentHistoryBetweenTime(ContentResolver cr, int historyLimit, long start, long end);
-
- public abstract long getPrePathLastVisitedTimeMilliseconds(ContentResolver cr, String prePath);
-
- public abstract void expireHistory(ContentResolver cr, ExpirePriority priority);
-
- public abstract void removeHistoryEntry(ContentResolver cr, String url);
-
- public abstract void clearHistory(ContentResolver cr, boolean clearSearchHistory);
-
-
- public abstract String getUrlForKeyword(ContentResolver cr, String keyword);
-
- public abstract boolean isBookmark(ContentResolver cr, String uri);
- public abstract boolean addBookmark(ContentResolver cr, String title, String uri);
- public abstract Cursor getBookmarkForUrl(ContentResolver cr, String url);
- public abstract Cursor getBookmarksForPartialUrl(ContentResolver cr, String partialUrl);
- public abstract void removeBookmarksWithURL(ContentResolver cr, String uri);
- public abstract void registerBookmarkObserver(ContentResolver cr, ContentObserver observer);
- public abstract void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword);
- public abstract boolean hasBookmarkWithGuid(ContentResolver cr, String guid);
-
- public abstract boolean insertPageMetadata(ContentProviderClient contentProviderClient, String pageUrl, boolean hasImage, String metadataJSON);
- public abstract int deletePageMetadata(ContentProviderClient contentProviderClient, String pageUrl);
- /**
- * Can return <code>null</code>.
- */
- public abstract Cursor getBookmarksInFolder(ContentResolver cr, long folderId);
-
- public abstract int getBookmarkCountForFolder(ContentResolver cr, long folderId);
-
- /**
- * Get the favicon from the database, if any, associated with the given favicon URL. (That is,
- * the URL of the actual favicon image, not the URL of the page with which the favicon is associated.)
- * @param cr The ContentResolver to use.
- * @param faviconURL The URL of the favicon to fetch from the database.
- * @return The decoded Bitmap from the database, if any. null if none is stored.
- */
- public abstract LoadFaviconResult getFaviconForUrl(Context context, ContentResolver cr, String faviconURL);
-
- /**
- * Try to find a usable favicon URL in the history or bookmarks table.
- */
- public abstract String getFaviconURLFromPageURL(ContentResolver cr, String uri);
-
- public abstract byte[] getThumbnailForUrl(ContentResolver cr, String uri);
- public abstract void updateThumbnailForUrl(ContentResolver cr, String uri, BitmapDrawable thumbnail);
-
- /**
- * Query for non-null thumbnails matching the provided <code>urls</code>.
- * The returned cursor will have no more than, but possibly fewer than,
- * the requested number of thumbnails.
- *
- * Returns null if the provided list of URLs is empty or null.
- */
- public abstract Cursor getThumbnailsForUrls(ContentResolver cr,
- List<String> urls);
-
- public abstract void removeThumbnails(ContentResolver cr);
-
- // Utility function for updating existing history using batch operations
- public abstract void updateHistoryInBatch(ContentResolver cr,
- Collection<ContentProviderOperation> operations, String url,
- String title, long date, int visits);
-
- public abstract void updateBookmarkInBatch(ContentResolver cr,
- Collection<ContentProviderOperation> operations, String url,
- String title, String guid, long parent, long added, long modified,
- long position, String keyword, int type);
-
- public abstract void pinSite(ContentResolver cr, String url, String title, int position);
- public abstract void unpinSite(ContentResolver cr, int position);
-
- public abstract boolean hideSuggestedSite(String url);
- public abstract void setSuggestedSites(SuggestedSites suggestedSites);
- public abstract SuggestedSites getSuggestedSites();
- public abstract boolean hasSuggestedImageUrl(String url);
- public abstract String getSuggestedImageUrlForUrl(String url);
- public abstract int getSuggestedBackgroundColorForUrl(String url);
-
- /**
- * Obtain a set of links for highlights from bookmarks and history.
- *
- * @param context The context to load the cursor.
- * @param limit Maximum number of results to return.
- */
- public abstract CursorLoader getHighlights(Context context, int limit);
-
- /**
- * Block a page from the highlights list.
- *
- * @param url The page URL. Only pages exactly matching this URL will be blocked.
- */
- public abstract void blockActivityStreamSite(ContentResolver cr, String url);
-
- public static BrowserDB from(final Context context) {
- return from(GeckoProfile.get(context));
- }
-
- public static BrowserDB from(final GeckoProfile profile) {
- synchronized (profile.getLock()) {
- BrowserDB db = (BrowserDB) profile.getData();
- if (db != null) {
- return db;
- }
-
- db = new LocalBrowserDB(profile.getName());
- profile.setData(db);
- return db;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java b/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
deleted file mode 100644
index f823d9060..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
+++ /dev/null
@@ -1,2237 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import java.io.File;
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.mozilla.apache.commons.codec.binary.Base32;
-import org.json.simple.JSONArray;
-import org.json.simple.JSONObject;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.db.BrowserContract.ActivityStreamBlocklist;
-import org.mozilla.gecko.db.BrowserContract.Bookmarks;
-import org.mozilla.gecko.db.BrowserContract.Combined;
-import org.mozilla.gecko.db.BrowserContract.Favicons;
-import org.mozilla.gecko.db.BrowserContract.History;
-import org.mozilla.gecko.db.BrowserContract.Visits;
-import org.mozilla.gecko.db.BrowserContract.PageMetadata;
-import org.mozilla.gecko.db.BrowserContract.Numbers;
-import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
-import org.mozilla.gecko.db.BrowserContract.SearchHistory;
-import org.mozilla.gecko.db.BrowserContract.Thumbnails;
-import org.mozilla.gecko.db.BrowserContract.UrlAnnotations;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.reader.SavedReaderViewHelper;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.sync.repositories.android.RepoUtils;
-import org.mozilla.gecko.util.FileUtils;
-
-import static org.mozilla.gecko.db.DBUtils.qualifyColumn;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.SQLException;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteStatement;
-import android.net.Uri;
-import android.os.Build;
-import android.util.Log;
-
-
-// public for robocop testing
-public final class BrowserDatabaseHelper extends SQLiteOpenHelper {
- private static final String LOGTAG = "GeckoBrowserDBHelper";
-
- // Replace the Bug number below with your Bug that is conducting a DB upgrade, as to force a merge conflict with any
- // other patches that require a DB upgrade.
- public static final int DATABASE_VERSION = 36; // Bug 1301717
- public static final String DATABASE_NAME = "browser.db";
-
- final protected Context mContext;
-
- static final String TABLE_BOOKMARKS = Bookmarks.TABLE_NAME;
- static final String TABLE_HISTORY = History.TABLE_NAME;
- static final String TABLE_VISITS = Visits.TABLE_NAME;
- static final String TABLE_PAGE_METADATA = PageMetadata.TABLE_NAME;
- static final String TABLE_FAVICONS = Favicons.TABLE_NAME;
- static final String TABLE_THUMBNAILS = Thumbnails.TABLE_NAME;
- static final String TABLE_READING_LIST = ReadingListItems.TABLE_NAME;
- static final String TABLE_TABS = TabsProvider.TABLE_TABS;
- static final String TABLE_CLIENTS = TabsProvider.TABLE_CLIENTS;
- static final String TABLE_LOGINS = BrowserContract.Logins.TABLE_LOGINS;
- static final String TABLE_DELETED_LOGINS = BrowserContract.DeletedLogins.TABLE_DELETED_LOGINS;
- static final String TABLE_DISABLED_HOSTS = BrowserContract.LoginsDisabledHosts.TABLE_DISABLED_HOSTS;
- static final String TABLE_ANNOTATIONS = UrlAnnotations.TABLE_NAME;
-
- static final String VIEW_COMBINED = Combined.VIEW_NAME;
- static final String VIEW_BOOKMARKS_WITH_FAVICONS = Bookmarks.VIEW_WITH_FAVICONS;
- static final String VIEW_BOOKMARKS_WITH_ANNOTATIONS = Bookmarks.VIEW_WITH_ANNOTATIONS;
- static final String VIEW_HISTORY_WITH_FAVICONS = History.VIEW_WITH_FAVICONS;
- static final String VIEW_COMBINED_WITH_FAVICONS = Combined.VIEW_WITH_FAVICONS;
-
- static final String TABLE_BOOKMARKS_JOIN_FAVICONS = TABLE_BOOKMARKS + " LEFT OUTER JOIN " +
- TABLE_FAVICONS + " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.FAVICON_ID) + " = " +
- qualifyColumn(TABLE_FAVICONS, Favicons._ID);
-
- static final String TABLE_BOOKMARKS_JOIN_ANNOTATIONS = TABLE_BOOKMARKS + " JOIN " +
- TABLE_ANNOTATIONS + " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " +
- qualifyColumn(TABLE_ANNOTATIONS, UrlAnnotations.URL);
-
- static final String TABLE_HISTORY_JOIN_FAVICONS = TABLE_HISTORY + " LEFT OUTER JOIN " +
- TABLE_FAVICONS + " ON " + qualifyColumn(TABLE_HISTORY, History.FAVICON_ID) + " = " +
- qualifyColumn(TABLE_FAVICONS, Favicons._ID);
-
- static final String TABLE_BOOKMARKS_TMP = TABLE_BOOKMARKS + "_tmp";
- static final String TABLE_HISTORY_TMP = TABLE_HISTORY + "_tmp";
-
- private static final String[] mobileIdColumns = new String[] { Bookmarks._ID };
- private static final String[] mobileIdSelectionArgs = new String[] { Bookmarks.MOBILE_FOLDER_GUID };
-
- private boolean didCreateTabsTable = false;
- private boolean didCreateCurrentReadingListTable = false;
-
- public BrowserDatabaseHelper(Context context, String databasePath) {
- super(context, databasePath, null, DATABASE_VERSION);
- mContext = context;
- }
-
- private void createBookmarksTable(SQLiteDatabase db) {
- debug("Creating " + TABLE_BOOKMARKS + " table");
-
- db.execSQL("CREATE TABLE " + TABLE_BOOKMARKS + "(" +
- Bookmarks._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
- Bookmarks.TITLE + " TEXT," +
- Bookmarks.URL + " TEXT," +
- Bookmarks.TYPE + " INTEGER NOT NULL DEFAULT " + Bookmarks.TYPE_BOOKMARK + "," +
- Bookmarks.PARENT + " INTEGER," +
- Bookmarks.POSITION + " INTEGER NOT NULL," +
- Bookmarks.KEYWORD + " TEXT," +
- Bookmarks.DESCRIPTION + " TEXT," +
- Bookmarks.TAGS + " TEXT," +
- Bookmarks.FAVICON_ID + " INTEGER," +
- Bookmarks.DATE_CREATED + " INTEGER," +
- Bookmarks.DATE_MODIFIED + " INTEGER," +
- Bookmarks.GUID + " TEXT NOT NULL," +
- Bookmarks.IS_DELETED + " INTEGER NOT NULL DEFAULT 0, " +
- "FOREIGN KEY (" + Bookmarks.PARENT + ") REFERENCES " +
- TABLE_BOOKMARKS + "(" + Bookmarks._ID + ")" +
- ");");
-
- db.execSQL("CREATE INDEX bookmarks_url_index ON " + TABLE_BOOKMARKS + "("
- + Bookmarks.URL + ")");
- db.execSQL("CREATE INDEX bookmarks_type_deleted_index ON " + TABLE_BOOKMARKS + "("
- + Bookmarks.TYPE + ", " + Bookmarks.IS_DELETED + ")");
- db.execSQL("CREATE UNIQUE INDEX bookmarks_guid_index ON " + TABLE_BOOKMARKS + "("
- + Bookmarks.GUID + ")");
- db.execSQL("CREATE INDEX bookmarks_modified_index ON " + TABLE_BOOKMARKS + "("
- + Bookmarks.DATE_MODIFIED + ")");
- }
-
- private void createHistoryTable(SQLiteDatabase db) {
- debug("Creating " + TABLE_HISTORY + " table");
- db.execSQL("CREATE TABLE " + TABLE_HISTORY + "(" +
- History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
- History.TITLE + " TEXT," +
- History.URL + " TEXT NOT NULL," +
- // Can we drop VISITS count? Can we calculate it in the Combined view as a sum?
- // See Bug 1277329.
- History.VISITS + " INTEGER NOT NULL DEFAULT 0," +
- History.LOCAL_VISITS + " INTEGER NOT NULL DEFAULT 0," +
- History.REMOTE_VISITS + " INTEGER NOT NULL DEFAULT 0," +
- History.FAVICON_ID + " INTEGER," +
- History.DATE_LAST_VISITED + " INTEGER," +
- History.LOCAL_DATE_LAST_VISITED + " INTEGER NOT NULL DEFAULT 0," +
- History.REMOTE_DATE_LAST_VISITED + " INTEGER NOT NULL DEFAULT 0," +
- History.DATE_CREATED + " INTEGER," +
- History.DATE_MODIFIED + " INTEGER," +
- History.GUID + " TEXT NOT NULL," +
- History.IS_DELETED + " INTEGER NOT NULL DEFAULT 0" +
- ");");
-
- db.execSQL("CREATE INDEX history_url_index ON " + TABLE_HISTORY + '('
- + History.URL + ')');
- db.execSQL("CREATE UNIQUE INDEX history_guid_index ON " + TABLE_HISTORY + '('
- + History.GUID + ')');
- db.execSQL("CREATE INDEX history_modified_index ON " + TABLE_HISTORY + '('
- + History.DATE_MODIFIED + ')');
- db.execSQL("CREATE INDEX history_visited_index ON " + TABLE_HISTORY + '('
- + History.DATE_LAST_VISITED + ')');
- }
-
- private void createVisitsTable(SQLiteDatabase db) {
- debug("Creating " + TABLE_VISITS + " table");
- db.execSQL("CREATE TABLE " + TABLE_VISITS + "(" +
- Visits._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
- Visits.HISTORY_GUID + " TEXT NOT NULL," +
- Visits.VISIT_TYPE + " TINYINT NOT NULL DEFAULT 1," +
- Visits.DATE_VISITED + " INTEGER NOT NULL, " +
- Visits.IS_LOCAL + " TINYINT NOT NULL DEFAULT 1, " +
-
- "FOREIGN KEY (" + Visits.HISTORY_GUID + ") REFERENCES " +
- TABLE_HISTORY + "(" + History.GUID + ") ON DELETE CASCADE ON UPDATE CASCADE" +
- ");");
-
- db.execSQL("CREATE UNIQUE INDEX visits_history_guid_and_date_visited_index ON " + TABLE_VISITS + "("
- + Visits.HISTORY_GUID + "," + Visits.DATE_VISITED + ")");
- db.execSQL("CREATE INDEX visits_history_guid_index ON " + TABLE_VISITS + "(" + Visits.HISTORY_GUID + ")");
- }
-
- private void createFaviconsTable(SQLiteDatabase db) {
- debug("Creating " + TABLE_FAVICONS + " table");
- db.execSQL("CREATE TABLE " + TABLE_FAVICONS + " (" +
- Favicons._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
- Favicons.URL + " TEXT UNIQUE," +
- Favicons.DATA + " BLOB," +
- Favicons.DATE_CREATED + " INTEGER," +
- Favicons.DATE_MODIFIED + " INTEGER" +
- ");");
-
- db.execSQL("CREATE INDEX favicons_modified_index ON " + TABLE_FAVICONS + "("
- + Favicons.DATE_MODIFIED + ")");
- }
-
- private void createThumbnailsTable(SQLiteDatabase db) {
- debug("Creating " + TABLE_THUMBNAILS + " table");
- db.execSQL("CREATE TABLE " + TABLE_THUMBNAILS + " (" +
- Thumbnails._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
- Thumbnails.URL + " TEXT UNIQUE," +
- Thumbnails.DATA + " BLOB" +
- ");");
- }
-
- private void createPageMetadataTable(SQLiteDatabase db) {
- debug("Creating " + TABLE_PAGE_METADATA + " table");
- db.execSQL("CREATE TABLE " + TABLE_PAGE_METADATA + "(" +
- PageMetadata._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
- PageMetadata.HISTORY_GUID + " TEXT NOT NULL," +
- PageMetadata.DATE_CREATED + " INTEGER NOT NULL, " +
- PageMetadata.HAS_IMAGE + " TINYINT NOT NULL DEFAULT 0, " +
- PageMetadata.JSON + " TEXT NOT NULL, " +
-
- "FOREIGN KEY (" + Visits.HISTORY_GUID + ") REFERENCES " +
- TABLE_HISTORY + "(" + History.GUID + ") ON DELETE CASCADE ON UPDATE CASCADE" +
- ");");
-
- // Establish a 1-to-1 relationship with History table.
- db.execSQL("CREATE UNIQUE INDEX page_metadata_history_guid ON " + TABLE_PAGE_METADATA + "("
- + PageMetadata.HISTORY_GUID + ")");
- // Improve performance of commonly occurring selections.
- db.execSQL("CREATE INDEX page_metadata_history_guid_and_has_image ON " + TABLE_PAGE_METADATA + "("
- + PageMetadata.HISTORY_GUID + ", " + PageMetadata.HAS_IMAGE + ")");
- }
-
- private void createBookmarksWithFaviconsView(SQLiteDatabase db) {
- debug("Creating " + VIEW_BOOKMARKS_WITH_FAVICONS + " view");
-
- db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_BOOKMARKS_WITH_FAVICONS + " AS " +
- "SELECT " + qualifyColumn(TABLE_BOOKMARKS, "*") +
- ", " + qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + Bookmarks.FAVICON +
- ", " + qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + Bookmarks.FAVICON_URL +
- " FROM " + TABLE_BOOKMARKS_JOIN_FAVICONS);
- }
-
- private void createBookmarksWithAnnotationsView(SQLiteDatabase db) {
- debug("Creating " + VIEW_BOOKMARKS_WITH_ANNOTATIONS + " view");
-
- db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_BOOKMARKS_WITH_ANNOTATIONS + " AS " +
- "SELECT " + qualifyColumn(TABLE_BOOKMARKS, "*") +
- ", " + qualifyColumn(TABLE_ANNOTATIONS, UrlAnnotations.KEY) + " AS " + Bookmarks.ANNOTATION_KEY +
- ", " + qualifyColumn(TABLE_ANNOTATIONS, UrlAnnotations.VALUE) + " AS " + Bookmarks.ANNOTATION_VALUE +
- " FROM " + TABLE_BOOKMARKS_JOIN_ANNOTATIONS);
- }
-
- private void createHistoryWithFaviconsView(SQLiteDatabase db) {
- debug("Creating " + VIEW_HISTORY_WITH_FAVICONS + " view");
-
- db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_HISTORY_WITH_FAVICONS + " AS " +
- "SELECT " + qualifyColumn(TABLE_HISTORY, "*") +
- ", " + qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + History.FAVICON +
- ", " + qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + History.FAVICON_URL +
- " FROM " + TABLE_HISTORY_JOIN_FAVICONS);
- }
-
- private void createClientsTable(SQLiteDatabase db) {
- debug("Creating " + TABLE_CLIENTS + " table");
-
- // Table for client's name-guid mapping.
- db.execSQL("CREATE TABLE " + TABLE_CLIENTS + "(" +
- BrowserContract.Clients.GUID + " TEXT PRIMARY KEY," +
- BrowserContract.Clients.NAME + " TEXT," +
- BrowserContract.Clients.LAST_MODIFIED + " INTEGER," +
- BrowserContract.Clients.DEVICE_TYPE + " TEXT" +
- ");");
- }
-
- private void createTabsTable(SQLiteDatabase db, final String tableName) {
- debug("Creating tabs.db: " + db.getPath());
- debug("Creating " + tableName + " table");
-
- // Table for each tab on any client.
- db.execSQL("CREATE TABLE " + tableName + "(" +
- BrowserContract.Tabs._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
- BrowserContract.Tabs.CLIENT_GUID + " TEXT," +
- BrowserContract.Tabs.TITLE + " TEXT," +
- BrowserContract.Tabs.URL + " TEXT," +
- BrowserContract.Tabs.HISTORY + " TEXT," +
- BrowserContract.Tabs.FAVICON + " TEXT," +
- BrowserContract.Tabs.LAST_USED + " INTEGER," +
- BrowserContract.Tabs.POSITION + " INTEGER, " +
- "FOREIGN KEY (" + BrowserContract.Tabs.CLIENT_GUID + ") REFERENCES " +
- TABLE_CLIENTS + "(" + BrowserContract.Clients.GUID + ") ON DELETE CASCADE" +
- ");");
-
- didCreateTabsTable = true;
- }
-
- private void createTabsTableIndices(SQLiteDatabase db, final String tableName) {
- // Indices on CLIENT_GUID and POSITION.
- db.execSQL("CREATE INDEX " + TabsProvider.INDEX_TABS_GUID +
- " ON " + tableName + "(" + BrowserContract.Tabs.CLIENT_GUID + ")");
- db.execSQL("CREATE INDEX " + TabsProvider.INDEX_TABS_POSITION +
- " ON " + tableName + "(" + BrowserContract.Tabs.POSITION + ")");
- }
-
- // Insert a client row for our local Fennec client.
- private void createLocalClient(SQLiteDatabase db) {
- debug("Inserting local Fennec client into " + TABLE_CLIENTS + " table");
-
- ContentValues values = new ContentValues();
- values.put(BrowserContract.Clients.LAST_MODIFIED, System.currentTimeMillis());
- db.insertOrThrow(TABLE_CLIENTS, null, values);
- }
-
- private void createCombinedViewOn19(SQLiteDatabase db) {
- /*
- The v19 combined view removes the redundant subquery from the v16
- combined view and reorders the columns as necessary to prevent this
- from breaking any code that might be referencing columns by index.
-
- The rows in the ensuing view are, in order:
-
- Combined.BOOKMARK_ID
- Combined.HISTORY_ID
- Combined._ID (always 0)
- Combined.URL
- Combined.TITLE
- Combined.VISITS
- Combined.DATE_LAST_VISITED
- Combined.FAVICON_ID
-
- We need to return an _id column because CursorAdapter requires it for its
- default implementation for the getItemId() method. However, since
- we're not using this feature in the parts of the UI using this view,
- we can just use 0 for all rows.
- */
-
- db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED + " AS" +
-
- // Bookmarks without history.
- " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + "," +
- "-1 AS " + Combined.HISTORY_ID + "," +
- "0 AS " + Combined._ID + "," +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
- "-1 AS " + Combined.VISITS + ", " +
- "-1 AS " + Combined.DATE_LAST_VISITED + "," +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.FAVICON_ID) + " AS " + Combined.FAVICON_ID +
- " FROM " + TABLE_BOOKMARKS +
- " WHERE " +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
- // Ignore pinned bookmarks.
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " <> " + Bookmarks.FIXED_PINNED_LIST_ID + " AND " +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " = 0 AND " +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
- " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
- " UNION ALL" +
-
- // History with and without bookmark.
- " SELECT " +
- "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) +
-
- // Give pinned bookmarks a NULL ID so that they're not treated as bookmarks. We can't
- // completely ignore them here because they're joined with history entries we care about.
- " WHEN 0 THEN " +
- "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) +
- " WHEN " + Bookmarks.FIXED_PINNED_LIST_ID + " THEN " +
- "NULL " +
- "ELSE " +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) +
- " END " +
- "ELSE " +
- "NULL " +
- "END AS " + Combined.BOOKMARK_ID + "," +
- qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + "," +
- "0 AS " + Combined._ID + "," +
- qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + "," +
-
- // Prioritize bookmark titles over history titles, since the user may have
- // customized the title for a bookmark.
- "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
- qualifyColumn(TABLE_HISTORY, History.TITLE) +
- ") AS " + Combined.TITLE + "," +
- qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + "," +
- qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED + "," +
- qualifyColumn(TABLE_HISTORY, History.FAVICON_ID) + " AS " + Combined.FAVICON_ID +
-
- // We really shouldn't be selecting deleted bookmarks, but oh well.
- " FROM " + TABLE_HISTORY + " LEFT OUTER JOIN " + TABLE_BOOKMARKS +
- " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
- " WHERE " +
- qualifyColumn(TABLE_HISTORY, History.IS_DELETED) + " = 0 AND " +
- "(" +
- // The left outer join didn't match...
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
-
- // ... or it's a bookmark. This is less efficient than filtering prior
- // to the join if you have lots of folders.
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK +
- ")"
- );
-
- debug("Creating " + VIEW_COMBINED_WITH_FAVICONS + " view");
-
- db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED_WITH_FAVICONS + " AS" +
- " SELECT " + qualifyColumn(VIEW_COMBINED, "*") + ", " +
- qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + Combined.FAVICON_URL + ", " +
- qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + Combined.FAVICON +
- " FROM " + VIEW_COMBINED + " LEFT OUTER JOIN " + TABLE_FAVICONS +
- " ON " + Combined.FAVICON_ID + " = " + qualifyColumn(TABLE_FAVICONS, Favicons._ID));
-
- }
-
- private void createCombinedViewOn33(final SQLiteDatabase db) {
- /*
- Builds on top of v19 combined view, and adds the following aggregates:
- - Combined.LOCAL_DATE_LAST_VISITED - last date visited for all local visits
- - Combined.REMOTE_DATE_LAST_VISITED - last date visited for all remote visits
- - Combined.LOCAL_VISITS_COUNT - total number of local visits
- - Combined.REMOTE_VISITS_COUNT - total number of remote visits
-
- Any code written prior to v33 referencing columns by index directly remains intact
- (yet must die a fiery death), as new columns were added to the end of the list.
-
- The rows in the ensuing view are, in order:
- Combined.BOOKMARK_ID
- Combined.HISTORY_ID
- Combined._ID (always 0)
- Combined.URL
- Combined.TITLE
- Combined.VISITS
- Combined.DATE_LAST_VISITED
- Combined.FAVICON_ID
- Combined.LOCAL_DATE_LAST_VISITED
- Combined.REMOTE_DATE_LAST_VISITED
- Combined.LOCAL_VISITS_COUNT
- Combined.REMOTE_VISITS_COUNT
- */
- db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED + " AS" +
-
- // Bookmarks without history.
- " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + "," +
- "-1 AS " + Combined.HISTORY_ID + "," +
- "0 AS " + Combined._ID + "," +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
- "-1 AS " + Combined.VISITS + ", " +
- "-1 AS " + Combined.DATE_LAST_VISITED + "," +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.FAVICON_ID) + " AS " + Combined.FAVICON_ID + "," +
- "0 AS " + Combined.LOCAL_DATE_LAST_VISITED + ", " +
- "0 AS " + Combined.REMOTE_DATE_LAST_VISITED + ", " +
- "0 AS " + Combined.LOCAL_VISITS_COUNT + ", " +
- "0 AS " + Combined.REMOTE_VISITS_COUNT +
- " FROM " + TABLE_BOOKMARKS +
- " WHERE " +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
- // Ignore pinned bookmarks.
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " <> " + Bookmarks.FIXED_PINNED_LIST_ID + " AND " +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " = 0 AND " +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
- " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
- " UNION ALL" +
-
- // History with and without bookmark.
- " SELECT " +
- "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) +
-
- // Give pinned bookmarks a NULL ID so that they're not treated as bookmarks. We can't
- // completely ignore them here because they're joined with history entries we care about.
- " WHEN 0 THEN " +
- "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) +
- " WHEN " + Bookmarks.FIXED_PINNED_LIST_ID + " THEN " +
- "NULL " +
- "ELSE " +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) +
- " END " +
- "ELSE " +
- "NULL " +
- "END AS " + Combined.BOOKMARK_ID + "," +
- qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + "," +
- "0 AS " + Combined._ID + "," +
- qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + "," +
-
- // Prioritize bookmark titles over history titles, since the user may have
- // customized the title for a bookmark.
- "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
- qualifyColumn(TABLE_HISTORY, History.TITLE) +
- ") AS " + Combined.TITLE + "," +
- qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + "," +
- qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED + "," +
- qualifyColumn(TABLE_HISTORY, History.FAVICON_ID) + " AS " + Combined.FAVICON_ID + "," +
-
- // Figure out "last visited" days using MAX values for visit timestamps.
- // We use CASE statements here to separate local from remote visits.
- "COALESCE(MAX(CASE " + qualifyColumn(TABLE_VISITS, Visits.IS_LOCAL) + " " +
- "WHEN 1 THEN " + qualifyColumn(TABLE_VISITS, Visits.DATE_VISITED) + " " +
- "ELSE 0 END" +
- "), 0) AS " + Combined.LOCAL_DATE_LAST_VISITED + ", " +
-
- "COALESCE(MAX(CASE " + qualifyColumn(TABLE_VISITS, Visits.IS_LOCAL) + " " +
- "WHEN 0 THEN " + qualifyColumn(TABLE_VISITS, Visits.DATE_VISITED) + " " +
- "ELSE 0 END" +
- "), 0) AS " + Combined.REMOTE_DATE_LAST_VISITED + ", " +
-
- // Sum up visit counts for local and remote visit types. Again, use CASE to separate the two.
- "COALESCE(SUM(" + qualifyColumn(TABLE_VISITS, Visits.IS_LOCAL) + "), 0) AS " + Combined.LOCAL_VISITS_COUNT + ", " +
- "COALESCE(SUM(CASE " + qualifyColumn(TABLE_VISITS, Visits.IS_LOCAL) + " WHEN 0 THEN 1 ELSE 0 END), 0) AS " + Combined.REMOTE_VISITS_COUNT +
-
- // We need to JOIN on Visits in order to compute visit counts
- " FROM " + TABLE_HISTORY + " " +
- "LEFT OUTER JOIN " + TABLE_VISITS +
- " ON " + qualifyColumn(TABLE_HISTORY, History.GUID) + " = " + qualifyColumn(TABLE_VISITS, Visits.HISTORY_GUID) + " " +
-
- // We really shouldn't be selecting deleted bookmarks, but oh well.
- "LEFT OUTER JOIN " + TABLE_BOOKMARKS +
- " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
- " WHERE " +
- qualifyColumn(TABLE_HISTORY, History.IS_DELETED) + " = 0 AND " +
- "(" +
- // The left outer join didn't match...
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
-
- // ... or it's a bookmark. This is less efficient than filtering prior
- // to the join if you have lots of folders.
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK +
-
- ") GROUP BY " + qualifyColumn(TABLE_HISTORY, History.GUID)
- );
-
- debug("Creating " + VIEW_COMBINED_WITH_FAVICONS + " view");
-
- db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED_WITH_FAVICONS + " AS" +
- " SELECT " + qualifyColumn(VIEW_COMBINED, "*") + ", " +
- qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + Combined.FAVICON_URL + ", " +
- qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + Combined.FAVICON +
- " FROM " + VIEW_COMBINED + " LEFT OUTER JOIN " + TABLE_FAVICONS +
- " ON " + Combined.FAVICON_ID + " = " + qualifyColumn(TABLE_FAVICONS, Favicons._ID));
- }
-
- private void createCombinedViewOn34(final SQLiteDatabase db) {
- /*
- Builds on top of v33 combined view, and instead of calculating the following aggregates, gets them
- from the history table:
- - Combined.LOCAL_DATE_LAST_VISITED - last date visited for all local visits
- - Combined.REMOTE_DATE_LAST_VISITED - last date visited for all remote visits
- - Combined.LOCAL_VISITS_COUNT - total number of local visits
- - Combined.REMOTE_VISITS_COUNT - total number of remote visits
-
- Any code written prior to v33 referencing columns by index directly remains intact
- (yet must die a fiery death), as new columns were added to the end of the list.
-
- The rows in the ensuing view are, in order:
- Combined.BOOKMARK_ID
- Combined.HISTORY_ID
- Combined._ID (always 0)
- Combined.URL
- Combined.TITLE
- Combined.VISITS
- Combined.DATE_LAST_VISITED
- Combined.FAVICON_ID
- Combined.LOCAL_DATE_LAST_VISITED
- Combined.REMOTE_DATE_LAST_VISITED
- Combined.LOCAL_VISITS_COUNT
- Combined.REMOTE_VISITS_COUNT
- */
- db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED + " AS" +
-
- // Bookmarks without history.
- " SELECT " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + "," +
- "-1 AS " + Combined.HISTORY_ID + "," +
- "0 AS " + Combined._ID + "," +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " AS " + Combined.URL + ", " +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + " AS " + Combined.TITLE + ", " +
- "-1 AS " + Combined.VISITS + ", " +
- "-1 AS " + Combined.DATE_LAST_VISITED + "," +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.FAVICON_ID) + " AS " + Combined.FAVICON_ID + "," +
- "0 AS " + Combined.LOCAL_DATE_LAST_VISITED + ", " +
- "0 AS " + Combined.REMOTE_DATE_LAST_VISITED + ", " +
- "0 AS " + Combined.LOCAL_VISITS_COUNT + ", " +
- "0 AS " + Combined.REMOTE_VISITS_COUNT +
- " FROM " + TABLE_BOOKMARKS +
- " WHERE " +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + " AND " +
- // Ignore pinned bookmarks.
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " <> " + Bookmarks.FIXED_PINNED_LIST_ID + " AND " +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " = 0 AND " +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) +
- " NOT IN (SELECT " + History.URL + " FROM " + TABLE_HISTORY + ")" +
- " UNION ALL" +
-
- // History with and without bookmark.
- " SELECT " +
- "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) +
-
- // Give pinned bookmarks a NULL ID so that they're not treated as bookmarks. We can't
- // completely ignore them here because they're joined with history entries we care about.
- " WHEN 0 THEN " +
- "CASE " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) +
- " WHEN " + Bookmarks.FIXED_PINNED_LIST_ID + " THEN " +
- "NULL " +
- "ELSE " +
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID) +
- " END " +
- "ELSE " +
- "NULL " +
- "END AS " + Combined.BOOKMARK_ID + "," +
- qualifyColumn(TABLE_HISTORY, History._ID) + " AS " + Combined.HISTORY_ID + "," +
- "0 AS " + Combined._ID + "," +
- qualifyColumn(TABLE_HISTORY, History.URL) + " AS " + Combined.URL + "," +
-
- // Prioritize bookmark titles over history titles, since the user may have
- // customized the title for a bookmark.
- "COALESCE(" + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TITLE) + ", " +
- qualifyColumn(TABLE_HISTORY, History.TITLE) +
- ") AS " + Combined.TITLE + "," +
- qualifyColumn(TABLE_HISTORY, History.VISITS) + " AS " + Combined.VISITS + "," +
- qualifyColumn(TABLE_HISTORY, History.DATE_LAST_VISITED) + " AS " + Combined.DATE_LAST_VISITED + "," +
- qualifyColumn(TABLE_HISTORY, History.FAVICON_ID) + " AS " + Combined.FAVICON_ID + "," +
-
- qualifyColumn(TABLE_HISTORY, History.LOCAL_DATE_LAST_VISITED) + " AS " + Combined.LOCAL_DATE_LAST_VISITED + "," +
- qualifyColumn(TABLE_HISTORY, History.REMOTE_DATE_LAST_VISITED) + " AS " + Combined.REMOTE_DATE_LAST_VISITED + "," +
- qualifyColumn(TABLE_HISTORY, History.LOCAL_VISITS) + " AS " + Combined.LOCAL_VISITS_COUNT + "," +
- qualifyColumn(TABLE_HISTORY, History.REMOTE_VISITS) + " AS " + Combined.REMOTE_VISITS_COUNT +
-
- // We need to JOIN on Visits in order to compute visit counts
- " FROM " + TABLE_HISTORY + " " +
-
- // We really shouldn't be selecting deleted bookmarks, but oh well.
- "LEFT OUTER JOIN " + TABLE_BOOKMARKS +
- " ON " + qualifyColumn(TABLE_BOOKMARKS, Bookmarks.URL) + " = " + qualifyColumn(TABLE_HISTORY, History.URL) +
- " WHERE " +
- qualifyColumn(TABLE_HISTORY, History.IS_DELETED) + " = 0 AND " +
- "(" +
- // The left outer join didn't match...
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " IS NULL OR " +
-
- // ... or it's a bookmark. This is less efficient than filtering prior
- // to the join if you have lots of folders.
- qualifyColumn(TABLE_BOOKMARKS, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + ")"
- );
-
- debug("Creating " + VIEW_COMBINED_WITH_FAVICONS + " view");
-
- db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_COMBINED_WITH_FAVICONS + " AS" +
- " SELECT " + qualifyColumn(VIEW_COMBINED, "*") + ", " +
- qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + Combined.FAVICON_URL + ", " +
- qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + Combined.FAVICON +
- " FROM " + VIEW_COMBINED + " LEFT OUTER JOIN " + TABLE_FAVICONS +
- " ON " + Combined.FAVICON_ID + " = " + qualifyColumn(TABLE_FAVICONS, Favicons._ID));
- }
-
- private void createLoginsTable(SQLiteDatabase db, final String tableName) {
- debug("Creating logins.db: " + db.getPath());
- debug("Creating " + tableName + " table");
-
- // Table for each login.
- db.execSQL("CREATE TABLE " + tableName + "(" +
- BrowserContract.Logins._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
- BrowserContract.Logins.HOSTNAME + " TEXT NOT NULL," +
- BrowserContract.Logins.HTTP_REALM + " TEXT," +
- BrowserContract.Logins.FORM_SUBMIT_URL + " TEXT," +
- BrowserContract.Logins.USERNAME_FIELD + " TEXT NOT NULL," +
- BrowserContract.Logins.PASSWORD_FIELD + " TEXT NOT NULL," +
- BrowserContract.Logins.ENCRYPTED_USERNAME + " TEXT NOT NULL," +
- BrowserContract.Logins.ENCRYPTED_PASSWORD + " TEXT NOT NULL," +
- BrowserContract.Logins.GUID + " TEXT UNIQUE NOT NULL," +
- BrowserContract.Logins.ENC_TYPE + " INTEGER NOT NULL, " +
- BrowserContract.Logins.TIME_CREATED + " INTEGER," +
- BrowserContract.Logins.TIME_LAST_USED + " INTEGER," +
- BrowserContract.Logins.TIME_PASSWORD_CHANGED + " INTEGER," +
- BrowserContract.Logins.TIMES_USED + " INTEGER" +
- ");");
- }
-
- private void createLoginsTableIndices(SQLiteDatabase db, final String tableName) {
- // No need to create an index on GUID, it is an unique column.
- db.execSQL("CREATE INDEX " + LoginsProvider.INDEX_LOGINS_HOSTNAME +
- " ON " + tableName + "(" + BrowserContract.Logins.HOSTNAME + ")");
- db.execSQL("CREATE INDEX " + LoginsProvider.INDEX_LOGINS_HOSTNAME_FORM_SUBMIT_URL +
- " ON " + tableName + "(" + BrowserContract.Logins.HOSTNAME + "," + BrowserContract.Logins.FORM_SUBMIT_URL + ")");
- db.execSQL("CREATE INDEX " + LoginsProvider.INDEX_LOGINS_HOSTNAME_HTTP_REALM +
- " ON " + tableName + "(" + BrowserContract.Logins.HOSTNAME + "," + BrowserContract.Logins.HTTP_REALM + ")");
- }
-
- private void createDeletedLoginsTable(SQLiteDatabase db, final String tableName) {
- debug("Creating deleted_logins.db: " + db.getPath());
- debug("Creating " + tableName + " table");
-
- // Table for each deleted login.
- db.execSQL("CREATE TABLE " + tableName + "(" +
- BrowserContract.DeletedLogins._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
- BrowserContract.DeletedLogins.GUID + " TEXT UNIQUE NOT NULL," +
- BrowserContract.DeletedLogins.TIME_DELETED + " INTEGER NOT NULL" +
- ");");
- }
-
- private void createDisabledHostsTable(SQLiteDatabase db, final String tableName) {
- debug("Creating disabled_hosts.db: " + db.getPath());
- debug("Creating " + tableName + " table");
-
- // Table for each disabled host.
- db.execSQL("CREATE TABLE " + tableName + "(" +
- BrowserContract.LoginsDisabledHosts._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
- BrowserContract.LoginsDisabledHosts.HOSTNAME + " TEXT UNIQUE NOT NULL ON CONFLICT REPLACE" +
- ");");
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- debug("Creating browser.db: " + db.getPath());
-
- for (Table table : BrowserProvider.sTables) {
- table.onCreate(db);
- }
-
- createBookmarksTable(db);
- createHistoryTable(db);
- createFaviconsTable(db);
- createThumbnailsTable(db);
- createClientsTable(db);
- createLocalClient(db);
- createTabsTable(db, TABLE_TABS);
- createTabsTableIndices(db, TABLE_TABS);
-
-
- createBookmarksWithFaviconsView(db);
- createHistoryWithFaviconsView(db);
-
- createOrUpdateSpecialFolder(db, Bookmarks.PLACES_FOLDER_GUID,
- R.string.bookmarks_folder_places, 0);
-
- createOrUpdateAllSpecialFolders(db);
- createSearchHistoryTable(db);
- createUrlAnnotationsTable(db);
- createNumbersTable(db);
-
- createDeletedLoginsTable(db, TABLE_DELETED_LOGINS);
- createDisabledHostsTable(db, TABLE_DISABLED_HOSTS);
- createLoginsTable(db, TABLE_LOGINS);
- createLoginsTableIndices(db, TABLE_LOGINS);
-
- createBookmarksWithAnnotationsView(db);
-
- createVisitsTable(db);
- createCombinedViewOn34(db);
-
- createActivityStreamBlocklistTable(db);
-
- createPageMetadataTable(db);
- }
-
- /**
- * Copies the tabs and clients tables out of the given tabs.db file and into the destinationDB.
- *
- * @param tabsDBFile Path to existing tabs.db.
- * @param destinationDB The destination database.
- */
- public void copyTabsDB(File tabsDBFile, SQLiteDatabase destinationDB) {
- createClientsTable(destinationDB);
- createTabsTable(destinationDB, TABLE_TABS);
- createTabsTableIndices(destinationDB, TABLE_TABS);
-
- SQLiteDatabase oldTabsDB = null;
- try {
- oldTabsDB = SQLiteDatabase.openDatabase(tabsDBFile.getPath(), null, SQLiteDatabase.OPEN_READONLY);
-
- if (!DBUtils.copyTable(oldTabsDB, TABLE_CLIENTS, destinationDB, TABLE_CLIENTS)) {
- Log.e(LOGTAG, "Failed to migrate table clients; ignoring.");
- }
- if (!DBUtils.copyTable(oldTabsDB, TABLE_TABS, destinationDB, TABLE_TABS)) {
- Log.e(LOGTAG, "Failed to migrate table tabs; ignoring.");
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception occurred while trying to copy from " + tabsDBFile.getPath() +
- " to " + destinationDB.getPath() + "; ignoring.", e);
- } finally {
- if (oldTabsDB != null) {
- oldTabsDB.close();
- }
- }
- }
-
- /**
- * We used to have a separate history extensions database which was used by Sync to store arrays
- * of visits for individual History GUIDs. It was only used by Sync.
- * This function migrates contents of that database over to the Visits table.
- *
- * Warning to callers: this method might throw IllegalStateException if we fail to allocate a
- * cursor to read HistoryExtensionsDB data for whatever reason. See Bug 1280409.
- *
- * @param historyExtensionDb Source History Extensions database
- * @param db Destination database
- */
- private void copyHistoryExtensionDataToVisitsTable(final SQLiteDatabase historyExtensionDb, final SQLiteDatabase db) {
- final String historyExtensionTable = "HistoryExtension";
- final String columnGuid = "guid";
- final String columnVisits = "visits";
-
- final Cursor historyExtensionCursor = historyExtensionDb.query(historyExtensionTable,
- new String[] {columnGuid, columnVisits},
- null, null, null, null, null);
- // Ignore null or empty cursor, we can't (or have nothing to) copy at this point.
- if (historyExtensionCursor == null) {
- return;
- }
- try {
- if (!historyExtensionCursor.moveToFirst()) {
- return;
- }
-
- final int guidCol = historyExtensionCursor.getColumnIndexOrThrow(columnGuid);
-
- // Use prepared (aka "compiled") SQL statements because they are much faster when we're inserting
- // lots of data. We avoid GC churn and recompilation of SQL statements on every insert.
- // NB #1: OR IGNORE clause applies to UNIQUE, NOT NULL, CHECK, and PRIMARY KEY constraints.
- // It does not apply to Foreign Key constraints, but in our case, at this point in time, foreign key
- // constraints are disabled anyway.
- // We care about OR IGNORE because we want to ensure that in case of (GUID,DATE)
- // clash (the UNIQUE constraint), we will not fail the transaction, and just skip conflicting row.
- // Clash might occur if visits array we got from Sync has duplicate (guid,date) records.
- // NB #2: IS_LOCAL is always 0, since we consider all visits coming from Sync to be remote.
- final String insertSqlStatement = "INSERT OR IGNORE INTO " + Visits.TABLE_NAME + " (" +
- Visits.DATE_VISITED + "," +
- Visits.VISIT_TYPE + "," +
- Visits.HISTORY_GUID + "," +
- Visits.IS_LOCAL + ") VALUES (?, ?, ?, " + Visits.VISIT_IS_REMOTE + ")";
- final SQLiteStatement compiledInsertStatement = db.compileStatement(insertSqlStatement);
-
- do {
- final String guid = historyExtensionCursor.getString(guidCol);
-
- // Sanity check, let's not risk a bad incoming GUID.
- if (guid == null || guid.isEmpty()) {
- continue;
- }
-
- // First, check if history with given GUID exists in the History table.
- // We might have a lot of entries in the HistoryExtensionDatabase whose GUID doesn't
- // match one in the History table. Let's avoid doing unnecessary work by first checking if
- // GUID exists locally.
- // Note that we don't have foreign key constraints enabled at this point.
- // See Bug 1266232 for details.
- if (!isGUIDPresentInHistoryTable(db, guid)) {
- continue;
- }
-
- final JSONArray visitsInHistoryExtensionDB = RepoUtils.getJSONArrayFromCursor(historyExtensionCursor, columnVisits);
-
- if (visitsInHistoryExtensionDB == null) {
- continue;
- }
-
- final int histExtVisitCount = visitsInHistoryExtensionDB.size();
-
- debug("Inserting " + histExtVisitCount + " visits from history extension db for GUID: " + guid);
- for (int i = 0; i < histExtVisitCount; i++) {
- final JSONObject visit = (JSONObject) visitsInHistoryExtensionDB.get(i);
-
- // Sanity check.
- if (visit == null) {
- continue;
- }
-
- // Let's not rely on underlying data being correct, and guard against casting failures.
- // Since we can't recover from this (other than ignoring this visit), let's not fail user's migration.
- final Long date;
- final Long visitType;
- try {
- date = (Long) visit.get("date");
- visitType = (Long) visit.get("type");
- } catch (ClassCastException e) {
- continue;
- }
- // Sanity check our incoming data.
- if (date == null || visitType == null) {
- continue;
- }
-
- // Bind parameters use a 1-based index.
- compiledInsertStatement.clearBindings();
- compiledInsertStatement.bindLong(1, date);
- compiledInsertStatement.bindLong(2, visitType);
- compiledInsertStatement.bindString(3, guid);
- compiledInsertStatement.executeInsert();
- }
- } while (historyExtensionCursor.moveToNext());
- } finally {
- // We return on a null cursor, so don't have to check it here.
- historyExtensionCursor.close();
- }
- }
-
- private boolean isGUIDPresentInHistoryTable(final SQLiteDatabase db, String guid) {
- final Cursor historyCursor = db.query(
- History.TABLE_NAME,
- new String[] {History.GUID}, History.GUID + " = ?", new String[] {guid},
- null, null, null);
- if (historyCursor == null) {
- return false;
- }
- try {
- // No history record found for given GUID
- if (!historyCursor.moveToFirst()) {
- return false;
- }
- } finally {
- historyCursor.close();
- }
-
- return true;
- }
-
- private void createSearchHistoryTable(SQLiteDatabase db) {
- debug("Creating " + SearchHistory.TABLE_NAME + " table");
-
- db.execSQL("CREATE TABLE " + SearchHistory.TABLE_NAME + "(" +
- SearchHistory._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
- SearchHistory.QUERY + " TEXT UNIQUE NOT NULL, " +
- SearchHistory.DATE_LAST_VISITED + " INTEGER, " +
- SearchHistory.VISITS + " INTEGER ) ");
-
- db.execSQL("CREATE INDEX idx_search_history_last_visited ON " +
- SearchHistory.TABLE_NAME + "(" + SearchHistory.DATE_LAST_VISITED + ")");
- }
-
- private void createActivityStreamBlocklistTable(final SQLiteDatabase db) {
- debug("Creating " + ActivityStreamBlocklist.TABLE_NAME + " table");
-
- db.execSQL("CREATE TABLE " + ActivityStreamBlocklist.TABLE_NAME + "(" +
- ActivityStreamBlocklist._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
- ActivityStreamBlocklist.URL + " TEXT UNIQUE NOT NULL, " +
- ActivityStreamBlocklist.CREATED + " INTEGER NOT NULL)");
- }
-
- private void createReadingListTable(final SQLiteDatabase db, final String tableName) {
- debug("Creating " + TABLE_READING_LIST + " table");
-
- db.execSQL("CREATE TABLE " + tableName + "(" +
- ReadingListItems._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
- ReadingListItems.GUID + " TEXT UNIQUE, " + // Server-assigned.
-
- ReadingListItems.CONTENT_STATUS + " TINYINT NOT NULL DEFAULT " + ReadingListItems.STATUS_UNFETCHED + ", " +
- ReadingListItems.SYNC_STATUS + " TINYINT NOT NULL DEFAULT " + ReadingListItems.SYNC_STATUS_NEW + ", " +
- ReadingListItems.SYNC_CHANGE_FLAGS + " TINYINT NOT NULL DEFAULT " + ReadingListItems.SYNC_CHANGE_NONE + ", " +
-
- ReadingListItems.CLIENT_LAST_MODIFIED + " INTEGER NOT NULL, " + // Client time.
- ReadingListItems.SERVER_LAST_MODIFIED + " INTEGER, " + // Server-assigned.
-
- // Server-assigned.
- ReadingListItems.SERVER_STORED_ON + " INTEGER, " +
- ReadingListItems.ADDED_ON + " INTEGER, " + // Client time. Shouldn't be null, but not enforced. Formerly DATE_CREATED.
- ReadingListItems.MARKED_READ_ON + " INTEGER, " +
-
- // These boolean flags represent the server 'status', 'unread', 'is_article', and 'favorite' fields.
- ReadingListItems.IS_DELETED + " TINYINT NOT NULL DEFAULT 0, " +
- ReadingListItems.IS_ARCHIVED + " TINYINT NOT NULL DEFAULT 0, " +
- ReadingListItems.IS_UNREAD + " TINYINT NOT NULL DEFAULT 1, " +
- ReadingListItems.IS_ARTICLE + " TINYINT NOT NULL DEFAULT 0, " +
- ReadingListItems.IS_FAVORITE + " TINYINT NOT NULL DEFAULT 0, " +
-
- ReadingListItems.URL + " TEXT NOT NULL, " +
- ReadingListItems.TITLE + " TEXT, " +
- ReadingListItems.RESOLVED_URL + " TEXT, " +
- ReadingListItems.RESOLVED_TITLE + " TEXT, " +
-
- ReadingListItems.EXCERPT + " TEXT, " +
-
- ReadingListItems.ADDED_BY + " TEXT, " +
- ReadingListItems.MARKED_READ_BY + " TEXT, " +
-
- ReadingListItems.WORD_COUNT + " INTEGER DEFAULT 0, " +
- ReadingListItems.READ_POSITION + " INTEGER DEFAULT 0 " +
- "); ");
-
- didCreateCurrentReadingListTable = true; // Mostly correct, in the absence of transactions.
- }
-
- private void createReadingListIndices(final SQLiteDatabase db, final String tableName) {
- // No need to create an index on GUID; it's a UNIQUE column.
- db.execSQL("CREATE INDEX reading_list_url ON " + tableName + "("
- + ReadingListItems.URL + ")");
- db.execSQL("CREATE INDEX reading_list_content_status ON " + tableName + "("
- + ReadingListItems.CONTENT_STATUS + ")");
- }
-
- private void createUrlAnnotationsTable(final SQLiteDatabase db) {
- debug("Creating " + UrlAnnotations.TABLE_NAME + " table");
-
- db.execSQL("CREATE TABLE " + UrlAnnotations.TABLE_NAME + "(" +
- UrlAnnotations._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
- UrlAnnotations.URL + " TEXT NOT NULL, " +
- UrlAnnotations.KEY + " TEXT NOT NULL, " +
- UrlAnnotations.VALUE + " TEXT, " +
- UrlAnnotations.DATE_CREATED + " INTEGER NOT NULL, " +
- UrlAnnotations.DATE_MODIFIED + " INTEGER NOT NULL, " +
- UrlAnnotations.SYNC_STATUS + " TINYINT NOT NULL DEFAULT " + UrlAnnotations.SyncStatus.NEW.getDBValue() +
- " );");
-
- db.execSQL("CREATE INDEX idx_url_annotations_url_key ON " +
- UrlAnnotations.TABLE_NAME + "(" + UrlAnnotations.URL + ", " + UrlAnnotations.KEY + ")");
- }
-
- private void createOrUpdateAllSpecialFolders(SQLiteDatabase db) {
- createOrUpdateSpecialFolder(db, Bookmarks.MOBILE_FOLDER_GUID,
- R.string.bookmarks_folder_mobile, 0);
- createOrUpdateSpecialFolder(db, Bookmarks.TOOLBAR_FOLDER_GUID,
- R.string.bookmarks_folder_toolbar, 1);
- createOrUpdateSpecialFolder(db, Bookmarks.MENU_FOLDER_GUID,
- R.string.bookmarks_folder_menu, 2);
- createOrUpdateSpecialFolder(db, Bookmarks.TAGS_FOLDER_GUID,
- R.string.bookmarks_folder_tags, 3);
- createOrUpdateSpecialFolder(db, Bookmarks.UNFILED_FOLDER_GUID,
- R.string.bookmarks_folder_unfiled, 4);
- createOrUpdateSpecialFolder(db, Bookmarks.PINNED_FOLDER_GUID,
- R.string.bookmarks_folder_pinned, 5);
- }
-
- private void createOrUpdateSpecialFolder(SQLiteDatabase db,
- String guid, int titleId, int position) {
- ContentValues values = new ContentValues();
- values.put(Bookmarks.GUID, guid);
- values.put(Bookmarks.TYPE, Bookmarks.TYPE_FOLDER);
- values.put(Bookmarks.POSITION, position);
-
- if (guid.equals(Bookmarks.PLACES_FOLDER_GUID)) {
- values.put(Bookmarks._ID, Bookmarks.FIXED_ROOT_ID);
- } else if (guid.equals(Bookmarks.PINNED_FOLDER_GUID)) {
- values.put(Bookmarks._ID, Bookmarks.FIXED_PINNED_LIST_ID);
- }
-
- // Set the parent to 0, which sync assumes is the root
- values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID);
-
- String title = mContext.getResources().getString(titleId);
- values.put(Bookmarks.TITLE, title);
-
- long now = System.currentTimeMillis();
- values.put(Bookmarks.DATE_CREATED, now);
- values.put(Bookmarks.DATE_MODIFIED, now);
-
- int updated = db.update(TABLE_BOOKMARKS, values,
- Bookmarks.GUID + " = ?",
- new String[] { guid });
-
- if (updated == 0) {
- db.insert(TABLE_BOOKMARKS, Bookmarks.GUID, values);
- debug("Inserted special folder: " + guid);
- } else {
- debug("Updated special folder: " + guid);
- }
- }
-
- private void createNumbersTable(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + Numbers.TABLE_NAME + " (" + Numbers.POSITION + " INTEGER PRIMARY KEY AUTOINCREMENT)");
-
- if (db.getVersion() >= 3007011) { // SQLite 3.7.11
- // This is only available in SQLite >= 3.7.11, see release notes:
- // "Enhance the INSERT syntax to allow multiple rows to be inserted via the VALUES clause"
- final String numbers = "(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)," +
- "(10),(11),(12),(13),(14),(15),(16),(17),(18),(19)," +
- "(20),(21),(22),(23),(24),(25),(26),(27),(28),(29)," +
- "(30),(31),(32),(33),(34),(35),(36),(37),(38),(39)," +
- "(40),(41),(42),(43),(44),(45),(46),(47),(48),(49)," +
- "(50)";
-
- db.execSQL("INSERT INTO " + Numbers.TABLE_NAME + " (" + Numbers.POSITION + ") VALUES " + numbers);
- } else {
- final SQLiteStatement statement = db.compileStatement("INSERT INTO " + Numbers.TABLE_NAME + " (" + Numbers.POSITION + ") VALUES (?)");
-
- for (int i = 0; i <= Numbers.MAX_VALUE; i++) {
- statement.bindLong(1, i);
- statement.executeInsert();
- }
- }
- }
-
- private boolean isSpecialFolder(ContentValues values) {
- String guid = values.getAsString(Bookmarks.GUID);
- if (guid == null) {
- return false;
- }
-
- return guid.equals(Bookmarks.MOBILE_FOLDER_GUID) ||
- guid.equals(Bookmarks.MENU_FOLDER_GUID) ||
- guid.equals(Bookmarks.TOOLBAR_FOLDER_GUID) ||
- guid.equals(Bookmarks.UNFILED_FOLDER_GUID) ||
- guid.equals(Bookmarks.TAGS_FOLDER_GUID);
- }
-
- private void migrateBookmarkFolder(SQLiteDatabase db, int folderId,
- BookmarkMigrator migrator) {
- Cursor c = null;
-
- debug("Migrating bookmark folder with id = " + folderId);
-
- String selection = Bookmarks.PARENT + " = " + folderId;
- String[] selectionArgs = null;
-
- boolean isRootFolder = (folderId == Bookmarks.FIXED_ROOT_ID);
-
- // If we're loading the root folder, we have to account for
- // any previously created special folder that was created without
- // setting a parent id (e.g. mobile folder) and making sure we're
- // not adding any infinite recursion as root's parent is root itself.
- if (isRootFolder) {
- selection = Bookmarks.GUID + " != ?" + " AND (" +
- selection + " OR " + Bookmarks.PARENT + " = NULL)";
- selectionArgs = new String[] { Bookmarks.PLACES_FOLDER_GUID };
- }
-
- List<Integer> subFolders = new ArrayList<Integer>();
- List<ContentValues> invalidSpecialEntries = new ArrayList<ContentValues>();
-
- try {
- c = db.query(TABLE_BOOKMARKS_TMP,
- null,
- selection,
- selectionArgs,
- null, null, null);
-
- // The key point here is that bookmarks should be added in
- // parent order to avoid any problems with the foreign key
- // in Bookmarks.PARENT.
- while (c.moveToNext()) {
- ContentValues values = new ContentValues();
-
- // We're using a null projection in the query which
- // means we're getting all columns from the table.
- // It's safe to simply transform the row into the
- // values to be inserted on the new table.
- DatabaseUtils.cursorRowToContentValues(c, values);
-
- boolean isSpecialFolder = isSpecialFolder(values);
-
- // The mobile folder used to be created with PARENT = NULL.
- // We want fix that here.
- if (values.getAsLong(Bookmarks.PARENT) == null && isSpecialFolder)
- values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID);
-
- if (isRootFolder && !isSpecialFolder) {
- invalidSpecialEntries.add(values);
- continue;
- }
-
- if (migrator != null)
- migrator.updateForNewTable(values);
-
- debug("Migrating bookmark: " + values.getAsString(Bookmarks.TITLE));
- db.insert(TABLE_BOOKMARKS, Bookmarks.URL, values);
-
- Integer type = values.getAsInteger(Bookmarks.TYPE);
- if (type != null && type == Bookmarks.TYPE_FOLDER)
- subFolders.add(values.getAsInteger(Bookmarks._ID));
- }
- } finally {
- if (c != null)
- c.close();
- }
-
- // At this point is safe to assume that the mobile folder is
- // in the new table given that we've always created it on
- // database creation time.
- final int nInvalidSpecialEntries = invalidSpecialEntries.size();
- if (nInvalidSpecialEntries > 0) {
- Integer mobileFolderId = getMobileFolderId(db);
- if (mobileFolderId == null) {
- Log.e(LOGTAG, "Error migrating invalid special folder entries: mobile folder id is null");
- return;
- }
-
- debug("Found " + nInvalidSpecialEntries + " invalid special folder entries");
- for (int i = 0; i < nInvalidSpecialEntries; i++) {
- ContentValues values = invalidSpecialEntries.get(i);
- values.put(Bookmarks.PARENT, mobileFolderId);
-
- db.insert(TABLE_BOOKMARKS, Bookmarks.URL, values);
- }
- }
-
- final int nSubFolders = subFolders.size();
- for (int i = 0; i < nSubFolders; i++) {
- int subFolderId = subFolders.get(i);
- migrateBookmarkFolder(db, subFolderId, migrator);
- }
- }
-
- private void migrateBookmarksTable(SQLiteDatabase db) {
- migrateBookmarksTable(db, null);
- }
-
- private void migrateBookmarksTable(SQLiteDatabase db, BookmarkMigrator migrator) {
- debug("Renaming bookmarks table to " + TABLE_BOOKMARKS_TMP);
- db.execSQL("ALTER TABLE " + TABLE_BOOKMARKS +
- " RENAME TO " + TABLE_BOOKMARKS_TMP);
-
- debug("Dropping views and indexes related to " + TABLE_BOOKMARKS);
-
- db.execSQL("DROP INDEX IF EXISTS bookmarks_url_index");
- db.execSQL("DROP INDEX IF EXISTS bookmarks_type_deleted_index");
- db.execSQL("DROP INDEX IF EXISTS bookmarks_guid_index");
- db.execSQL("DROP INDEX IF EXISTS bookmarks_modified_index");
-
- createBookmarksTable(db);
-
- createOrUpdateSpecialFolder(db, Bookmarks.PLACES_FOLDER_GUID,
- R.string.bookmarks_folder_places, 0);
-
- migrateBookmarkFolder(db, Bookmarks.FIXED_ROOT_ID, migrator);
-
- // Ensure all special folders exist and have the
- // right folder hierarchy.
- createOrUpdateAllSpecialFolders(db);
-
- debug("Dropping bookmarks temporary table");
- db.execSQL("DROP TABLE IF EXISTS " + TABLE_BOOKMARKS_TMP);
- }
-
- /**
- * Migrate a history table from some old version to the newest one by creating the new table and
- * copying all the data over.
- */
- private void migrateHistoryTable(SQLiteDatabase db) {
- debug("Renaming history table to " + TABLE_HISTORY_TMP);
- db.execSQL("ALTER TABLE " + TABLE_HISTORY +
- " RENAME TO " + TABLE_HISTORY_TMP);
-
- debug("Dropping views and indexes related to " + TABLE_HISTORY);
-
- db.execSQL("DROP INDEX IF EXISTS history_url_index");
- db.execSQL("DROP INDEX IF EXISTS history_guid_index");
- db.execSQL("DROP INDEX IF EXISTS history_modified_index");
- db.execSQL("DROP INDEX IF EXISTS history_visited_index");
-
- createHistoryTable(db);
-
- db.execSQL("INSERT INTO " + TABLE_HISTORY + " SELECT * FROM " + TABLE_HISTORY_TMP);
-
- debug("Dropping history temporary table");
- db.execSQL("DROP TABLE IF EXISTS " + TABLE_HISTORY_TMP);
- }
-
- private void upgradeDatabaseFrom3to4(SQLiteDatabase db) {
- migrateBookmarksTable(db, new BookmarkMigrator3to4());
- }
-
- private void upgradeDatabaseFrom6to7(SQLiteDatabase db) {
- debug("Removing history visits with NULL GUIDs");
- db.execSQL("DELETE FROM " + TABLE_HISTORY + " WHERE " + History.GUID + " IS NULL");
-
- migrateBookmarksTable(db);
- migrateHistoryTable(db);
- }
-
- private void upgradeDatabaseFrom7to8(SQLiteDatabase db) {
- debug("Combining history entries with the same URL");
-
- final String TABLE_DUPES = "duped_urls";
- final String TOTAL = "total";
- final String LATEST = "latest";
- final String WINNER = "winner";
-
- db.execSQL("CREATE TEMP TABLE " + TABLE_DUPES + " AS" +
- " SELECT " + History.URL + ", " +
- "SUM(" + History.VISITS + ") AS " + TOTAL + ", " +
- "MAX(" + History.DATE_MODIFIED + ") AS " + LATEST + ", " +
- "MAX(" + History._ID + ") AS " + WINNER +
- " FROM " + TABLE_HISTORY +
- " GROUP BY " + History.URL +
- " HAVING count(" + History.URL + ") > 1");
-
- db.execSQL("CREATE UNIQUE INDEX " + TABLE_DUPES + "_url_index ON " +
- TABLE_DUPES + " (" + History.URL + ")");
-
- final String fromClause = " FROM " + TABLE_DUPES + " WHERE " +
- qualifyColumn(TABLE_DUPES, History.URL) + " = " +
- qualifyColumn(TABLE_HISTORY, History.URL);
-
- db.execSQL("UPDATE " + TABLE_HISTORY +
- " SET " + History.VISITS + " = (SELECT " + TOTAL + fromClause + "), " +
- History.DATE_MODIFIED + " = (SELECT " + LATEST + fromClause + "), " +
- History.IS_DELETED + " = " +
- "(" + History._ID + " <> (SELECT " + WINNER + fromClause + "))" +
- " WHERE " + History.URL + " IN (SELECT " + History.URL + " FROM " + TABLE_DUPES + ")");
-
- db.execSQL("DROP TABLE " + TABLE_DUPES);
- }
-
- private void upgradeDatabaseFrom10to11(SQLiteDatabase db) {
- db.execSQL("CREATE INDEX bookmarks_type_deleted_index ON " + TABLE_BOOKMARKS + "("
- + Bookmarks.TYPE + ", " + Bookmarks.IS_DELETED + ")");
- }
-
- private void upgradeDatabaseFrom12to13(SQLiteDatabase db) {
- createFaviconsTable(db);
-
- // Add favicon_id column to the history/bookmarks tables. We wrap this in a try-catch
- // because the column *may* already exist at this point (depending on how many upgrade
- // steps have been performed in this operation). In which case these queries will throw,
- // but we don't care.
- try {
- db.execSQL("ALTER TABLE " + TABLE_HISTORY +
- " ADD COLUMN " + History.FAVICON_ID + " INTEGER");
- db.execSQL("ALTER TABLE " + TABLE_BOOKMARKS +
- " ADD COLUMN " + Bookmarks.FAVICON_ID + " INTEGER");
- } catch (SQLException e) {
- // Don't care.
- debug("Exception adding favicon_id column. We're probably fine." + e);
- }
-
- createThumbnailsTable(db);
-
- db.execSQL("DROP VIEW IF EXISTS bookmarks_with_images");
- db.execSQL("DROP VIEW IF EXISTS history_with_images");
- db.execSQL("DROP VIEW IF EXISTS combined_with_images");
-
- createBookmarksWithFaviconsView(db);
- createHistoryWithFaviconsView(db);
-
- db.execSQL("DROP TABLE IF EXISTS images");
- }
-
- private void upgradeDatabaseFrom13to14(SQLiteDatabase db) {
- createOrUpdateSpecialFolder(db, Bookmarks.PINNED_FOLDER_GUID,
- R.string.bookmarks_folder_pinned, 6);
- }
-
- private void upgradeDatabaseFrom14to15(SQLiteDatabase db) {
- Cursor c = null;
- try {
- // Get all the pinned bookmarks
- c = db.query(TABLE_BOOKMARKS,
- new String[] { Bookmarks._ID, Bookmarks.URL },
- Bookmarks.PARENT + " = ?",
- new String[] { Integer.toString(Bookmarks.FIXED_PINNED_LIST_ID) },
- null, null, null);
-
- while (c.moveToNext()) {
- // Check if this URL can be parsed as a URI with a valid scheme.
- String url = c.getString(c.getColumnIndexOrThrow(Bookmarks.URL));
- if (Uri.parse(url).getScheme() != null) {
- continue;
- }
-
- // If it can't, update the URL to be an encoded "user-entered" value.
- ContentValues values = new ContentValues(1);
- String newUrl = Uri.fromParts("user-entered", url, null).toString();
- values.put(Bookmarks.URL, newUrl);
- db.update(TABLE_BOOKMARKS, values, Bookmarks._ID + " = ?",
- new String[] { Integer.toString(c.getInt(c.getColumnIndexOrThrow(Bookmarks._ID))) });
- }
- } finally {
- if (c != null) {
- c.close();
- }
- }
- }
-
- private void upgradeDatabaseFrom15to16(SQLiteDatabase db) {
- // No harm in creating the v19 combined view here: means we don't need two almost-identical
- // functions to define both the v16 and v19 ones. The upgrade path will redundantly drop
- // and recreate the view again. *shrug*
- createV19CombinedView(db);
- }
-
- private void upgradeDatabaseFrom16to17(SQLiteDatabase db) {
- // Purge any 0-byte favicons/thumbnails
- try {
- db.execSQL("DELETE FROM " + TABLE_FAVICONS +
- " WHERE length(" + Favicons.DATA + ") = 0");
- db.execSQL("DELETE FROM " + TABLE_THUMBNAILS +
- " WHERE length(" + Thumbnails.DATA + ") = 0");
- } catch (SQLException e) {
- Log.e(LOGTAG, "Error purging invalid favicons or thumbnails", e);
- }
- }
-
- /*
- * Moves reading list items from 'bookmarks' table to 'reading_list' table.
- */
- private void upgradeDatabaseFrom17to18(SQLiteDatabase db) {
- debug("Moving reading list items from 'bookmarks' table to 'reading_list' table");
-
- final String selection = Bookmarks.PARENT + " = ? AND " + Bookmarks.IS_DELETED + " = ? ";
- final String[] selectionArgs = { String.valueOf(Bookmarks.FIXED_READING_LIST_ID), "0" };
- final String[] projection = { Bookmarks._ID,
- Bookmarks.GUID,
- Bookmarks.URL,
- Bookmarks.DATE_MODIFIED,
- Bookmarks.DATE_CREATED,
- Bookmarks.TITLE };
-
- try {
- db.beginTransaction();
-
- // Create 'reading_list' table.
- createReadingListTable(db, TABLE_READING_LIST);
-
- // Get all the reading list items from bookmarks table.
- final Cursor cursor = db.query(TABLE_BOOKMARKS, projection, selection, selectionArgs, null, null, null);
-
- if (cursor == null) {
- // This should never happen.
- db.setTransactionSuccessful();
- return;
- }
-
- try {
- // Insert reading list items into reading_list table.
- while (cursor.moveToNext()) {
- debug(DatabaseUtils.dumpCurrentRowToString(cursor));
- final ContentValues values = new ContentValues();
-
- // We don't preserve bookmark GUIDs.
- DatabaseUtils.cursorStringToContentValues(cursor, Bookmarks.URL, values, ReadingListItems.URL);
- DatabaseUtils.cursorStringToContentValues(cursor, Bookmarks.TITLE, values, ReadingListItems.TITLE);
- DatabaseUtils.cursorLongToContentValues(cursor, Bookmarks.DATE_CREATED, values, ReadingListItems.ADDED_ON);
- DatabaseUtils.cursorLongToContentValues(cursor, Bookmarks.DATE_MODIFIED, values, ReadingListItems.CLIENT_LAST_MODIFIED);
-
- db.insertOrThrow(TABLE_READING_LIST, null, values);
- }
- } finally {
- cursor.close();
- }
-
- // Delete reading list items from bookmarks table.
- db.delete(TABLE_BOOKMARKS,
- Bookmarks.PARENT + " = ? ",
- new String[] { String.valueOf(Bookmarks.FIXED_READING_LIST_ID) });
-
- // Delete reading list special folder.
- db.delete(TABLE_BOOKMARKS,
- Bookmarks._ID + " = ? ",
- new String[] { String.valueOf(Bookmarks.FIXED_READING_LIST_ID) });
-
- // Create indices.
- createReadingListIndices(db, TABLE_READING_LIST);
-
- // Done.
- db.setTransactionSuccessful();
- } catch (SQLException e) {
- Log.e(LOGTAG, "Error migrating reading list items", e);
- } finally {
- db.endTransaction();
- }
- }
-
- private void upgradeDatabaseFrom18to19(SQLiteDatabase db) {
- // Redefine the "combined" view...
- createV19CombinedView(db);
-
- // Kill any history entries with NULL URL. This ostensibly can't happen...
- db.execSQL("DELETE FROM " + TABLE_HISTORY + " WHERE " + History.URL + " IS NULL");
-
- // Similar for bookmark types. Replaces logic from the combined view, also shouldn't happen.
- db.execSQL("UPDATE " + TABLE_BOOKMARKS + " SET " +
- Bookmarks.TYPE + " = " + Bookmarks.TYPE_BOOKMARK +
- " WHERE " + Bookmarks.TYPE + " IS NULL");
- }
-
- private void upgradeDatabaseFrom19to20(SQLiteDatabase db) {
- createSearchHistoryTable(db);
- }
-
- private void upgradeDatabaseFrom21to22(SQLiteDatabase db) {
- if (didCreateCurrentReadingListTable) {
- debug("No need to add CONTENT_STATUS to reading list; we just created with the current schema.");
- return;
- }
-
- debug("Adding CONTENT_STATUS column to reading list table.");
-
- try {
- db.execSQL("ALTER TABLE " + TABLE_READING_LIST +
- " ADD COLUMN " + ReadingListItems.CONTENT_STATUS +
- " TINYINT DEFAULT " + ReadingListItems.STATUS_UNFETCHED);
-
- db.execSQL("CREATE INDEX reading_list_content_status ON " + TABLE_READING_LIST + "("
- + ReadingListItems.CONTENT_STATUS + ")");
- } catch (SQLiteException e) {
- // We're betting that an error here means that the table already has the column,
- // so we're failing due to the duplicate column name.
- Log.e(LOGTAG, "Error upgrading database from 21 to 22", e);
- }
- }
-
- private void upgradeDatabaseFrom22to23(SQLiteDatabase db) {
- if (didCreateCurrentReadingListTable) {
- // If we just created this table it is already in the expected >= 23 schema. Trying
- // to run this migration will crash because columns that were in the <= 22 schema
- // no longer exist.
- debug("No need to rev reading list schema; we just created with the current schema.");
- return;
- }
-
- debug("Rewriting reading list table.");
- createReadingListTable(db, "tmp_rl");
-
- // Remove indexes. We don't need them now, and we'll be throwing away the table.
- db.execSQL("DROP INDEX IF EXISTS reading_list_url");
- db.execSQL("DROP INDEX IF EXISTS reading_list_guid");
- db.execSQL("DROP INDEX IF EXISTS reading_list_content_status");
-
- // This used to be a part of the no longer existing ReadingListProvider, since we're deleting
- // this table later in the second migration, and since sync for this table never existed,
- // we don't care about the device name here.
- final String thisDevice = "_fake_device_name_that_will_be_discarded_in_the_next_migration_";
- db.execSQL("INSERT INTO tmp_rl (" +
- // Here are the columns we can preserve.
- ReadingListItems._ID + ", " +
- ReadingListItems.URL + ", " +
- ReadingListItems.TITLE + ", " +
- ReadingListItems.RESOLVED_TITLE + ", " + // = TITLE (if CONTENT_STATUS = STATUS_FETCHED_ARTICLE)
- ReadingListItems.RESOLVED_URL + ", " + // = URL (if CONTENT_STATUS = STATUS_FETCHED_ARTICLE)
- ReadingListItems.EXCERPT + ", " +
- ReadingListItems.IS_UNREAD + ", " + // = !READ
- ReadingListItems.IS_DELETED + ", " + // = 0
- ReadingListItems.GUID + ", " + // = NULL
- ReadingListItems.CLIENT_LAST_MODIFIED + ", " + // = DATE_MODIFIED
- ReadingListItems.ADDED_ON + ", " + // = DATE_CREATED
- ReadingListItems.CONTENT_STATUS + ", " +
- ReadingListItems.MARKED_READ_BY + ", " + // if READ + ", = this device
- ReadingListItems.ADDED_BY + // = this device
- ") " +
- "SELECT " +
- "_id, url, title, " +
- "CASE content_status WHEN " + ReadingListItems.STATUS_FETCHED_ARTICLE + " THEN title ELSE NULL END, " + // RESOLVED_TITLE.
- "CASE content_status WHEN " + ReadingListItems.STATUS_FETCHED_ARTICLE + " THEN url ELSE NULL END, " + // RESOLVED_URL.
- "excerpt, " +
- "CASE read WHEN 1 THEN 0 ELSE 1 END, " + // IS_UNREAD.
- "0, " + // IS_DELETED.
- "NULL, modified, created, content_status, " +
- "CASE read WHEN 1 THEN ? ELSE NULL END, " + // MARKED_READ_BY.
- "?" + // ADDED_BY.
- " FROM " + TABLE_READING_LIST +
- " WHERE deleted = 0",
- new String[] {thisDevice, thisDevice});
-
- // Now switch these tables over and recreate the indices.
- db.execSQL("DROP TABLE " + TABLE_READING_LIST);
- db.execSQL("ALTER TABLE tmp_rl RENAME TO " + TABLE_READING_LIST);
-
- createReadingListIndices(db, TABLE_READING_LIST);
- }
-
- private void upgradeDatabaseFrom23to24(SQLiteDatabase db) {
- // Version 24 consolidates the tabs and clients table into browser.db. Before, they lived in tabs.db.
- // It's easier to copy the existing data than to arrange for Sync to re-populate it.
- try {
- final File oldTabsDBFile = new File(GeckoProfile.get(mContext).getDir(), "tabs.db");
- copyTabsDB(oldTabsDBFile, db);
- } catch (Exception e) {
- Log.e(LOGTAG, "Got exception copying tabs and clients data from tabs.db to browser.db; ignoring.", e);
- }
-
- // Delete the database, the shared memory, and the log.
- for (String filename : new String[] { "tabs.db", "tabs.db-shm", "tabs.db-wal" }) {
- final File file = new File(GeckoProfile.get(mContext).getDir(), filename);
- try {
- FileUtils.delete(file);
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception occurred while trying to delete " + file.getPath() + "; ignoring.", e);
- }
- }
- }
-
- private void upgradeDatabaseFrom24to25(SQLiteDatabase db) {
- if (didCreateTabsTable) {
- // This migration adds a foreign key constraint (the table scheme stays identical, except
- // for the new constraint) - hence it is safe to run this migration on a newly created tabs
- // table - but it's unnecessary hence we should avoid doing so.
- debug("No need to rev tabs schema; foreign key constraint exists.");
- return;
- }
-
- debug("Rewriting tabs table.");
- createTabsTable(db, "tmp_tabs");
-
- // Remove indexes. We don't need them now, and we'll be throwing away the table.
- db.execSQL("DROP INDEX IF EXISTS " + TabsProvider.INDEX_TABS_GUID);
- db.execSQL("DROP INDEX IF EXISTS " + TabsProvider.INDEX_TABS_POSITION);
-
- db.execSQL("INSERT INTO tmp_tabs (" +
- // Here are the columns we can preserve.
- BrowserContract.Tabs._ID + ", " +
- BrowserContract.Tabs.CLIENT_GUID + ", " +
- BrowserContract.Tabs.TITLE + ", " +
- BrowserContract.Tabs.URL + ", " +
- BrowserContract.Tabs.HISTORY + ", " +
- BrowserContract.Tabs.FAVICON + ", " +
- BrowserContract.Tabs.LAST_USED + ", " +
- BrowserContract.Tabs.POSITION +
- ") " +
- "SELECT " +
- "_id, client_guid, title, url, history, favicon, last_used, position" +
- " FROM " + TABLE_TABS);
-
- // Now switch these tables over and recreate the indices.
- db.execSQL("DROP TABLE " + TABLE_TABS);
- db.execSQL("ALTER TABLE tmp_tabs RENAME TO " + TABLE_TABS);
- createTabsTableIndices(db, TABLE_TABS);
- didCreateTabsTable = true;
- }
-
- private void upgradeDatabaseFrom25to26(SQLiteDatabase db) {
- debug("Dropping unnecessary indices");
- db.execSQL("DROP INDEX IF EXISTS clients_guid_index");
- db.execSQL("DROP INDEX IF EXISTS thumbnails_url_index");
- db.execSQL("DROP INDEX IF EXISTS favicons_url_index");
- }
-
- private void upgradeDatabaseFrom27to28(final SQLiteDatabase db) {
- debug("Adding url annotations table");
- createUrlAnnotationsTable(db);
- }
-
- private void upgradeDatabaseFrom28to29(SQLiteDatabase db) {
- debug("Adding numbers table");
- createNumbersTable(db);
- }
-
- private void upgradeDatabaseFrom29to30(final SQLiteDatabase db) {
- debug("creating logins table");
- createDeletedLoginsTable(db, TABLE_DELETED_LOGINS);
- createDisabledHostsTable(db, TABLE_DISABLED_HOSTS);
- createLoginsTable(db, TABLE_LOGINS);
- createLoginsTableIndices(db, TABLE_LOGINS);
- }
-
- // Get the cache path for a URL, based on the storage format in place during the 27to28 transition.
- // This is a reimplementation of _toHashedPath from ReaderMode.jsm - given that we're likely
- // to migrate the SavedReaderViewHelper implementation at some point, it seems safest to have a local
- // implementation here - moreover this is probably faster than calling into JS.
- // This is public only to allow for testing.
- @RobocopTarget
- public static String getReaderCacheFileNameForURL(String url) {
- try {
- // On KitKat and above we can use java.nio.charset.StandardCharsets.UTF_8 in place of "UTF8"
- // which avoids having to handle UnsupportedCodingException
- byte[] utf8 = url.getBytes("UTF8");
-
- final MessageDigest digester = MessageDigest.getInstance("MD5");
- byte[] hash = digester.digest(utf8);
-
- final String hashString = new Base32().encodeAsString(hash);
- return hashString.substring(0, hashString.indexOf('=')) + ".json";
- } catch (UnsupportedEncodingException e) {
- // This should never happen
- throw new IllegalStateException("UTF8 encoding not available - can't process readercache filename");
- } catch (NoSuchAlgorithmException e) {
- // This should also never happen
- throw new IllegalStateException("MD5 digester unavailable - can't process readercache filename");
- }
- }
-
- /*
- * Moves reading list items from the 'reading_list' table back into the 'bookmarks' table. This time the
- * reading list items are placed into a "Reading List" folder, which is a subfolder of the mobile-bookmarks table.
- */
- private void upgradeDatabaseFrom30to31(SQLiteDatabase db) {
- // We only need to do the migration if reading-list items already exist. We could do a query of count(*) on
- // TABLE_READING_LIST, however if we are doing the migration, we'll need to query all items in the reading-list,
- // hence we might as well just query all items, and proceed with the migration if cursor.count > 0.
-
- // We try to retain the original ordering below. Our LocalReadingListAccessor actually coalesced
- // SERVER_STORED_ON with ADDED_ON to determine positioning, however reading list syncing was never
- // implemented hence SERVER_STORED will have always been null.
- final Cursor readingListCursor = db.query(TABLE_READING_LIST,
- new String[] {
- ReadingListItems.URL,
- ReadingListItems.TITLE,
- ReadingListItems.ADDED_ON,
- ReadingListItems.CLIENT_LAST_MODIFIED
- },
- ReadingListItems.IS_DELETED + " = 0",
- null,
- null,
- null,
- ReadingListItems.ADDED_ON + " DESC");
-
- // We'll want to walk the cache directory, so that we can (A) bookkeep readercache items
- // that we want and (B) delete unneeded readercache items. (B) shouldn't actually happen, but
- // is possible if there were bugs in our reader-caching code.
- // We need to construct this here since we populate this map while walking the DB cursor,
- // and use the map later when walking the cache.
- final Map<String, String> fileToURLMap = new HashMap<>();
-
-
- try {
- if (!readingListCursor.moveToFirst()) {
- return;
- }
-
- final Integer mobileBookmarksID = getMobileFolderId(db);
-
- if (mobileBookmarksID == null) {
- // This folder is created either on DB creation or during the 3-4 or 6-7 migrations.
- throw new IllegalStateException("mobile bookmarks folder must already exist");
- }
-
- final long now = System.currentTimeMillis();
-
- // We try to retain the same order as the reading-list would show. We should hopefully be reading the
- // items in the order they are displayed on screen (final param of db.query above), by providing
- // a position we should obtain the same ordering in the bookmark folder.
- long position = 0;
-
- final int titleColumnID = readingListCursor.getColumnIndexOrThrow(ReadingListItems.TITLE);
- final int createdColumnID = readingListCursor.getColumnIndexOrThrow(ReadingListItems.ADDED_ON);
-
- // This isn't the most efficient implementation, but the migration is one-off, and this
- // also more maintainable than the SQL equivalent (generating the guids correctly is
- // difficult in SQLite).
- do {
- final ContentValues readingListItemValues = new ContentValues();
-
- final String url = readingListCursor.getString(readingListCursor.getColumnIndexOrThrow(ReadingListItems.URL));
-
- readingListItemValues.put(Bookmarks.PARENT, mobileBookmarksID);
- readingListItemValues.put(Bookmarks.GUID, Utils.generateGuid());
- readingListItemValues.put(Bookmarks.URL, url);
- // Title may be null, however we're expecting a String - we can generate an empty string if needed:
- if (!readingListCursor.isNull(titleColumnID)) {
- readingListItemValues.put(Bookmarks.TITLE, readingListCursor.getString(titleColumnID));
- } else {
- readingListItemValues.put(Bookmarks.TITLE, "");
- }
- readingListItemValues.put(Bookmarks.DATE_CREATED, readingListCursor.getLong(createdColumnID));
- readingListItemValues.put(Bookmarks.DATE_MODIFIED, now);
- readingListItemValues.put(Bookmarks.POSITION, position);
-
- db.insert(TABLE_BOOKMARKS,
- null,
- readingListItemValues);
-
- final String cacheFileName = getReaderCacheFileNameForURL(url);
- fileToURLMap.put(cacheFileName, url);
-
- position++;
- } while (readingListCursor.moveToNext());
-
- } finally {
- readingListCursor.close();
- // We need to do this work here since we might be returning (we return early if the
- // reading-list table is empty).
- db.execSQL("DROP TABLE IF EXISTS " + TABLE_READING_LIST);
- createBookmarksWithAnnotationsView(db);
- }
-
- final File profileDir = GeckoProfile.get(mContext).getDir();
- final File cacheDir = new File(profileDir, "readercache");
-
- // At the time of this migration the SavedReaderViewHelper becomes a 1:1 mirror of reader view
- // url-annotations. This may change in future implementations, however currently we only need to care
- // about standard bookmarks (untouched during this migration) and bookmarks with a reader
- // view annotation (which we're creating here, and which are guaranteed to be saved offline).
- //
- // This is why we have to migrate the cache items (instead of cleaning the cache
- // and rebuilding it). We simply don't support uncached reader view bookmarks, and we would
- // break existing reading list items (they would convert into plain bookmarks without
- // reader view). This helps ensure that offline content isn't lost during the migration.
- if (cacheDir.exists() && cacheDir.isDirectory()) {
- SavedReaderViewHelper savedReaderViewHelper = SavedReaderViewHelper.getSavedReaderViewHelper(mContext);
-
- // Usually we initialise the helper during onOpen(). However onUpgrade() is run before
- // onOpen() hence we need to manually initialise it at this stage.
- savedReaderViewHelper.loadItems();
-
- for (File cacheFile : cacheDir.listFiles()) {
- if (fileToURLMap.containsKey(cacheFile.getName())) {
- final String url = fileToURLMap.get(cacheFile.getName());
- final String path = cacheFile.getAbsolutePath();
- long size = cacheFile.length();
-
- savedReaderViewHelper.put(url, path, size);
- } else {
- // This should never happen, but we don't actually know whether or not orphaned
- // items happened in the wild.
- boolean deleted = cacheFile.delete();
-
- if (!deleted) {
- Log.w(LOGTAG, "Failed to delete orphaned saved reader view file.");
- }
- }
- }
- }
- }
-
- private void upgradeDatabaseFrom31to32(final SQLiteDatabase db) {
- debug("Adding visits table");
- createVisitsTable(db);
-
- debug("Migrating visits from history extension db into visits table");
- String historyExtensionDbName = "history_extension_database";
-
- SQLiteDatabase historyExtensionDb = null;
- final File historyExtensionsDatabase = mContext.getDatabasePath(historyExtensionDbName);
-
- // Primary goal of this migration is to improve Top Sites experience by distinguishing between
- // local and remote visits. If Sync is enabled, we rely on visit data from Sync and treat it as remote.
- // However, if Sync is disabled but we detect evidence that it was enabled at some point (HistoryExtensionsDB is present)
- // then we synthesize visits from the History table, but we mark them all as "remote". This will ensure
- // that once user starts browsing around, their Top Sites will reflect their local browsing history.
- // Otherwise, we risk overwhelming their Top Sites with remote history, just as we did before this migration.
- try {
- // If FxAccount exists (Sync is enabled) then port data over to the Visits table.
- if (FirefoxAccounts.firefoxAccountsExist(mContext)) {
- try {
- historyExtensionDb = SQLiteDatabase.openDatabase(historyExtensionsDatabase.getPath(), null,
- SQLiteDatabase.OPEN_READONLY);
-
- if (historyExtensionDb != null) {
- copyHistoryExtensionDataToVisitsTable(historyExtensionDb, db);
- }
-
- // If we fail to open HistoryExtensionDatabase, then synthesize visits marking them as remote
- } catch (SQLiteException e) {
- Log.w(LOGTAG, "Couldn't open history extension database; synthesizing visits instead", e);
- synthesizeAndInsertVisits(db, false);
-
- // It's possible that we might fail to copy over visit data from the HistoryExtensionsDB,
- // so let's synthesize visits marking them as remote. See Bug 1280409.
- } catch (IllegalStateException e) {
- Log.w(LOGTAG, "Couldn't copy over history extension data; synthesizing visits instead", e);
- synthesizeAndInsertVisits(db, false);
- }
-
- // FxAccount doesn't exist, but there's evidence Sync was enabled at some point.
- // Synthesize visits from History table marking them all as remote.
- } else if (historyExtensionsDatabase.exists()) {
- synthesizeAndInsertVisits(db, false);
-
- // FxAccount doesn't exist and there's no evidence sync was ever enabled.
- // Synthesize visits from History table marking them all as local.
- } else {
- synthesizeAndInsertVisits(db, true);
- }
- } finally {
- if (historyExtensionDb != null) {
- historyExtensionDb.close();
- }
- }
-
- // Delete history extensions database if it's present.
- if (historyExtensionsDatabase.exists()) {
- if (!mContext.deleteDatabase(historyExtensionDbName)) {
- Log.e(LOGTAG, "Couldn't remove history extension database");
- }
- }
- }
-
- private void synthesizeAndInsertVisits(final SQLiteDatabase db, boolean markAsLocal) {
- final Cursor cursor = db.query(
- History.TABLE_NAME,
- new String[] {History.GUID, History.VISITS, History.DATE_LAST_VISITED},
- null, null, null, null, null);
- if (cursor == null) {
- Log.e(LOGTAG, "Null cursor while selecting all history records");
- return;
- }
-
- try {
- if (!cursor.moveToFirst()) {
- Log.e(LOGTAG, "No history records to synthesize visits for.");
- return;
- }
-
- int guidCol = cursor.getColumnIndexOrThrow(History.GUID);
- int visitsCol = cursor.getColumnIndexOrThrow(History.VISITS);
- int dateCol = cursor.getColumnIndexOrThrow(History.DATE_LAST_VISITED);
-
- // Re-use compiled SQL statements for faster inserts.
- // Visit Type is going to be 1, which is the column's default value.
- final String insertSqlStatement = "INSERT OR IGNORE INTO " + Visits.TABLE_NAME + "(" +
- Visits.DATE_VISITED + "," +
- Visits.HISTORY_GUID + "," +
- Visits.IS_LOCAL +
- ") VALUES (?, ?, ?)";
- final SQLiteStatement compiledInsertStatement = db.compileStatement(insertSqlStatement);
-
- // For each history record, insert as many visits as there are recorded in the VISITS column.
- do {
- final int numberOfVisits = cursor.getInt(visitsCol);
- final String guid = cursor.getString(guidCol);
- final long lastVisitedDate = cursor.getLong(dateCol);
-
- // Sanity check.
- if (guid == null) {
- continue;
- }
-
- // In a strange case that lastVisitedDate is a very low number, let's not introduce
- // negative timestamps into our data.
- if (lastVisitedDate - numberOfVisits < 0) {
- continue;
- }
-
- for (int i = 0; i < numberOfVisits; i++) {
- final long offsetVisitedDate = lastVisitedDate - i;
- compiledInsertStatement.clearBindings();
- compiledInsertStatement.bindLong(1, offsetVisitedDate);
- compiledInsertStatement.bindString(2, guid);
- // Very old school, 1 is true and 0 is false :)
- if (markAsLocal) {
- compiledInsertStatement.bindLong(3, Visits.VISIT_IS_LOCAL);
- } else {
- compiledInsertStatement.bindLong(3, Visits.VISIT_IS_REMOTE);
- }
- compiledInsertStatement.executeInsert();
- }
- } while (cursor.moveToNext());
- } catch (Exception e) {
- Log.e(LOGTAG, "Error while synthesizing visits for history record", e);
- } finally {
- cursor.close();
- }
- }
-
- private void updateHistoryTableAddVisitAggregates(final SQLiteDatabase db) {
- db.execSQL("ALTER TABLE " + TABLE_HISTORY +
- " ADD COLUMN " + History.LOCAL_VISITS + " INTEGER NOT NULL DEFAULT 0");
- db.execSQL("ALTER TABLE " + TABLE_HISTORY +
- " ADD COLUMN " + History.REMOTE_VISITS + " INTEGER NOT NULL DEFAULT 0");
- db.execSQL("ALTER TABLE " + TABLE_HISTORY +
- " ADD COLUMN " + History.LOCAL_DATE_LAST_VISITED + " INTEGER NOT NULL DEFAULT 0");
- db.execSQL("ALTER TABLE " + TABLE_HISTORY +
- " ADD COLUMN " + History.REMOTE_DATE_LAST_VISITED + " INTEGER NOT NULL DEFAULT 0");
- }
-
- private void calculateHistoryTableVisitAggregates(final SQLiteDatabase db) {
- // Note that we convert from microseconds (timestamps in the visits table) to milliseconds
- // (timestamps in the history table). Sync works in microseconds, so for visits Fennec stores
- // timestamps in microseconds as well - but the rest of the timestamps are stored in milliseconds.
- db.execSQL("UPDATE " + TABLE_HISTORY + " SET " +
- History.LOCAL_VISITS + " = (" +
- "SELECT COALESCE(SUM(" + qualifyColumn(TABLE_VISITS, Visits.IS_LOCAL) + "), 0)" +
- " FROM " + TABLE_VISITS +
- " WHERE " + qualifyColumn(TABLE_VISITS, Visits.HISTORY_GUID) + " = " + qualifyColumn(TABLE_HISTORY, History.GUID) +
- "), " +
- History.REMOTE_VISITS + " = (" +
- "SELECT COALESCE(SUM(CASE " + Visits.IS_LOCAL + " WHEN 0 THEN 1 ELSE 0 END), 0)" +
- " FROM " + TABLE_VISITS +
- " WHERE " + qualifyColumn(TABLE_VISITS, Visits.HISTORY_GUID) + " = " + qualifyColumn(TABLE_HISTORY, History.GUID) +
- "), " +
- History.LOCAL_DATE_LAST_VISITED + " = (" +
- "SELECT COALESCE(MAX(CASE " + Visits.IS_LOCAL + " WHEN 1 THEN " + Visits.DATE_VISITED + " ELSE 0 END), 0) / 1000" +
- " FROM " + TABLE_VISITS +
- " WHERE " + qualifyColumn(TABLE_VISITS, Visits.HISTORY_GUID) + " = " + qualifyColumn(TABLE_HISTORY, History.GUID) +
- "), " +
- History.REMOTE_DATE_LAST_VISITED + " = (" +
- "SELECT COALESCE(MAX(CASE " + Visits.IS_LOCAL + " WHEN 0 THEN " + Visits.DATE_VISITED + " ELSE 0 END), 0) / 1000" +
- " FROM " + TABLE_VISITS +
- " WHERE " + qualifyColumn(TABLE_VISITS, Visits.HISTORY_GUID) + " = " + qualifyColumn(TABLE_HISTORY, History.GUID) +
- ") " +
- "WHERE EXISTS " +
- "(SELECT " + Visits._ID +
- " FROM " + TABLE_VISITS +
- " WHERE " + qualifyColumn(TABLE_VISITS, Visits.HISTORY_GUID) + " = " + qualifyColumn(TABLE_HISTORY, History.GUID) + ")"
- );
- }
-
- private void upgradeDatabaseFrom32to33(final SQLiteDatabase db) {
- createV33CombinedView(db);
- }
-
- private void upgradeDatabaseFrom33to34(final SQLiteDatabase db) {
- updateHistoryTableAddVisitAggregates(db);
- calculateHistoryTableVisitAggregates(db);
- createV34CombinedView(db);
- }
-
- private void upgradeDatabaseFrom34to35(final SQLiteDatabase db) {
- createActivityStreamBlocklistTable(db);
- }
-
- private void upgradeDatabaseFrom35to36(final SQLiteDatabase db) {
- createPageMetadataTable(db);
- }
-
- private void createV33CombinedView(final SQLiteDatabase db) {
- db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED);
- db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED_WITH_FAVICONS);
-
- createCombinedViewOn33(db);
- }
-
- private void createV34CombinedView(final SQLiteDatabase db) {
- db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED);
- db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED_WITH_FAVICONS);
-
- createCombinedViewOn34(db);
- }
-
- private void createV19CombinedView(SQLiteDatabase db) {
- db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED);
- db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED_WITH_FAVICONS);
-
- createCombinedViewOn19(db);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- debug("Upgrading browser.db: " + db.getPath() + " from " +
- oldVersion + " to " + newVersion);
-
- // We have to do incremental upgrades until we reach the current
- // database schema version.
- for (int v = oldVersion + 1; v <= newVersion; v++) {
- switch (v) {
- case 4:
- upgradeDatabaseFrom3to4(db);
- break;
-
- case 7:
- upgradeDatabaseFrom6to7(db);
- break;
-
- case 8:
- upgradeDatabaseFrom7to8(db);
- break;
-
- case 11:
- upgradeDatabaseFrom10to11(db);
- break;
-
- case 13:
- upgradeDatabaseFrom12to13(db);
- break;
-
- case 14:
- upgradeDatabaseFrom13to14(db);
- break;
-
- case 15:
- upgradeDatabaseFrom14to15(db);
- break;
-
- case 16:
- upgradeDatabaseFrom15to16(db);
- break;
-
- case 17:
- upgradeDatabaseFrom16to17(db);
- break;
-
- case 18:
- upgradeDatabaseFrom17to18(db);
- break;
-
- case 19:
- upgradeDatabaseFrom18to19(db);
- break;
-
- case 20:
- upgradeDatabaseFrom19to20(db);
- break;
-
- case 22:
- upgradeDatabaseFrom21to22(db);
- break;
-
- case 23:
- upgradeDatabaseFrom22to23(db);
- break;
-
- case 24:
- upgradeDatabaseFrom23to24(db);
- break;
-
- case 25:
- upgradeDatabaseFrom24to25(db);
- break;
-
- case 26:
- upgradeDatabaseFrom25to26(db);
- break;
-
- // case 27 occurs in UrlMetadataTable.onUpgrade
-
- case 28:
- upgradeDatabaseFrom27to28(db);
- break;
-
- case 29:
- upgradeDatabaseFrom28to29(db);
- break;
-
- case 30:
- upgradeDatabaseFrom29to30(db);
- break;
-
- case 31:
- upgradeDatabaseFrom30to31(db);
- break;
-
- case 32:
- upgradeDatabaseFrom31to32(db);
- break;
-
- case 33:
- upgradeDatabaseFrom32to33(db);
- break;
-
- case 34:
- upgradeDatabaseFrom33to34(db);
- break;
-
- case 35:
- upgradeDatabaseFrom34to35(db);
- break;
-
- case 36:
- upgradeDatabaseFrom35to36(db);
- break;
- }
- }
-
- for (Table table : BrowserProvider.sTables) {
- table.onUpgrade(db, oldVersion, newVersion);
- }
-
- // Delete the obsolete favicon database after all other upgrades complete.
- // This can probably equivalently be moved into upgradeDatabaseFrom12to13.
- if (oldVersion < 13 && newVersion >= 13) {
- if (mContext.getDatabasePath("favicon_urls.db").exists()) {
- mContext.deleteDatabase("favicon_urls.db");
- }
- }
- }
-
- @Override
- public void onOpen(SQLiteDatabase db) {
- debug("Opening browser.db: " + db.getPath());
-
- // Force explicit readercache loading - we won't access readercache state for bookmarks
- // until we actually know what our bookmarks are. Bookmarks are stored in the DB, hence
- // it is sufficient to ensure that the readercache is loaded before the DB can be accessed.
- // Note, this takes ~4-6ms to load on an N4 (compared to 20-50ms for most DB queries), and
- // is only done once, hence this shouldn't have noticeable impact on performance. Moreover
- // this is run on a background thread and therefore won't block UI code during startup.
- SavedReaderViewHelper.getSavedReaderViewHelper(mContext).loadItems();
-
- Cursor cursor = null;
- try {
- cursor = db.rawQuery("PRAGMA foreign_keys=ON", null);
- } finally {
- if (cursor != null)
- cursor.close();
- }
- cursor = null;
- try {
- cursor = db.rawQuery("PRAGMA synchronous=NORMAL", null);
- } finally {
- if (cursor != null)
- cursor.close();
- }
-
- // From Honeycomb on, it's possible to run several db
- // commands in parallel using multiple connections.
- if (Build.VERSION.SDK_INT >= 11) {
- // Modern Android allows WAL to be enabled through a mode flag.
- if (Build.VERSION.SDK_INT < 16) {
- db.enableWriteAheadLogging();
-
- // This does nothing on 16+.
- db.setLockingEnabled(false);
- }
- } else {
- // Pre-Honeycomb, we can do some lesser optimizations.
- cursor = null;
- try {
- cursor = db.rawQuery("PRAGMA journal_mode=PERSIST", null);
- } finally {
- if (cursor != null)
- cursor.close();
- }
- }
- }
-
- // Calculate these once, at initialization. isLoggable is too expensive to
- // have in-line in each log call.
- private static final boolean logDebug = Log.isLoggable(LOGTAG, Log.DEBUG);
- private static final boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
- protected static void trace(String message) {
- if (logVerbose) {
- Log.v(LOGTAG, message);
- }
- }
-
- protected static void debug(String message) {
- if (logDebug) {
- Log.d(LOGTAG, message);
- }
- }
-
- private Integer getMobileFolderId(SQLiteDatabase db) {
- Cursor c = null;
-
- try {
- c = db.query(TABLE_BOOKMARKS,
- mobileIdColumns,
- Bookmarks.GUID + " = ?",
- mobileIdSelectionArgs,
- null, null, null);
-
- if (c == null || !c.moveToFirst())
- return null;
-
- return c.getInt(c.getColumnIndex(Bookmarks._ID));
- } finally {
- if (c != null)
- c.close();
- }
- }
-
- private interface BookmarkMigrator {
- public void updateForNewTable(ContentValues bookmark);
- }
-
- private class BookmarkMigrator3to4 implements BookmarkMigrator {
- @Override
- public void updateForNewTable(ContentValues bookmark) {
- Integer isFolder = bookmark.getAsInteger("folder");
- if (isFolder == null || isFolder != 1) {
- bookmark.put(Bookmarks.TYPE, Bookmarks.TYPE_BOOKMARK);
- } else {
- bookmark.put(Bookmarks.TYPE, Bookmarks.TYPE_FOLDER);
- }
-
- bookmark.remove("folder");
- }
- }
-}
-
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java b/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
deleted file mode 100644
index eb75d0be9..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
+++ /dev/null
@@ -1,2340 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract.ActivityStreamBlocklist;
-import org.mozilla.gecko.db.BrowserContract.Bookmarks;
-import org.mozilla.gecko.db.BrowserContract.Combined;
-import org.mozilla.gecko.db.BrowserContract.FaviconColumns;
-import org.mozilla.gecko.db.BrowserContract.Favicons;
-import org.mozilla.gecko.db.BrowserContract.Highlights;
-import org.mozilla.gecko.db.BrowserContract.History;
-import org.mozilla.gecko.db.BrowserContract.Visits;
-import org.mozilla.gecko.db.BrowserContract.Schema;
-import org.mozilla.gecko.db.BrowserContract.Tabs;
-import org.mozilla.gecko.db.BrowserContract.Thumbnails;
-import org.mozilla.gecko.db.BrowserContract.TopSites;
-import org.mozilla.gecko.db.BrowserContract.UrlAnnotations;
-import org.mozilla.gecko.db.BrowserContract.PageMetadata;
-import org.mozilla.gecko.db.DBUtils.UpdateOperation;
-import org.mozilla.gecko.icons.IconsHelper;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.content.BroadcastReceiver;
-import android.content.ContentProviderOperation;
-import android.content.ContentProviderResult;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.OperationApplicationException;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.MatrixCursor;
-import android.database.MergeCursor;
-import android.database.SQLException;
-import android.database.sqlite.SQLiteCursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.net.Uri;
-import android.support.v4.content.LocalBroadcastManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-public class BrowserProvider extends SharedBrowserDatabaseProvider {
- public static final String ACTION_SHRINK_MEMORY = "org.mozilla.gecko.db.intent.action.SHRINK_MEMORY";
-
- private static final String LOGTAG = "GeckoBrowserProvider";
-
- // How many records to reposition in a single query.
- // This should be less than the SQLite maximum number of query variables
- // (currently 999) divided by the number of variables used per positioning
- // query (currently 3).
- static final int MAX_POSITION_UPDATES_PER_QUERY = 100;
-
- // Minimum number of records to keep when expiring history.
- static final int DEFAULT_EXPIRY_RETAIN_COUNT = 2000;
- static final int AGGRESSIVE_EXPIRY_RETAIN_COUNT = 500;
-
- // Factor used to determine the minimum number of records to keep when expiring the activity stream blocklist
- static final int ACTIVITYSTREAM_BLOCKLIST_EXPIRY_FACTOR = 5;
-
- // Minimum duration to keep when expiring.
- static final long DEFAULT_EXPIRY_PRESERVE_WINDOW = 1000L * 60L * 60L * 24L * 28L; // Four weeks.
- // Minimum number of thumbnails to keep around.
- static final int DEFAULT_EXPIRY_THUMBNAIL_COUNT = 15;
-
- static final String TABLE_BOOKMARKS = Bookmarks.TABLE_NAME;
- static final String TABLE_HISTORY = History.TABLE_NAME;
- static final String TABLE_VISITS = Visits.TABLE_NAME;
- static final String TABLE_FAVICONS = Favicons.TABLE_NAME;
- static final String TABLE_THUMBNAILS = Thumbnails.TABLE_NAME;
- static final String TABLE_TABS = Tabs.TABLE_NAME;
- static final String TABLE_URL_ANNOTATIONS = UrlAnnotations.TABLE_NAME;
- static final String TABLE_ACTIVITY_STREAM_BLOCKLIST = ActivityStreamBlocklist.TABLE_NAME;
- static final String TABLE_PAGE_METADATA = PageMetadata.TABLE_NAME;
-
- static final String VIEW_COMBINED = Combined.VIEW_NAME;
- static final String VIEW_BOOKMARKS_WITH_FAVICONS = Bookmarks.VIEW_WITH_FAVICONS;
- static final String VIEW_BOOKMARKS_WITH_ANNOTATIONS = Bookmarks.VIEW_WITH_ANNOTATIONS;
- static final String VIEW_HISTORY_WITH_FAVICONS = History.VIEW_WITH_FAVICONS;
- static final String VIEW_COMBINED_WITH_FAVICONS = Combined.VIEW_WITH_FAVICONS;
-
- // Bookmark matches
- static final int BOOKMARKS = 100;
- static final int BOOKMARKS_ID = 101;
- static final int BOOKMARKS_FOLDER_ID = 102;
- static final int BOOKMARKS_PARENT = 103;
- static final int BOOKMARKS_POSITIONS = 104;
-
- // History matches
- static final int HISTORY = 200;
- static final int HISTORY_ID = 201;
- static final int HISTORY_OLD = 202;
-
- // Favicon matches
- static final int FAVICONS = 300;
- static final int FAVICON_ID = 301;
-
- // Schema matches
- static final int SCHEMA = 400;
-
- // Combined bookmarks and history matches
- static final int COMBINED = 500;
-
- // Control matches
- static final int CONTROL = 600;
-
- // Search Suggest matches. Obsolete.
- static final int SEARCH_SUGGEST = 700;
-
- // Thumbnail matches
- static final int THUMBNAILS = 800;
- static final int THUMBNAIL_ID = 801;
-
- static final int URL_ANNOTATIONS = 900;
-
- static final int TOPSITES = 1000;
-
- static final int VISITS = 1100;
-
- static final int METADATA = 1200;
-
- static final int HIGHLIGHTS = 1300;
-
- static final int ACTIVITY_STREAM_BLOCKLIST = 1400;
-
- static final int PAGE_METADATA = 1500;
-
- static final String DEFAULT_BOOKMARKS_SORT_ORDER = Bookmarks.TYPE
- + " ASC, " + Bookmarks.POSITION + " ASC, " + Bookmarks._ID
- + " ASC";
-
- static final String DEFAULT_HISTORY_SORT_ORDER = History.DATE_LAST_VISITED + " DESC";
- static final String DEFAULT_VISITS_SORT_ORDER = Visits.DATE_VISITED + " DESC";
-
- static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
-
- static final Map<String, String> BOOKMARKS_PROJECTION_MAP;
- static final Map<String, String> HISTORY_PROJECTION_MAP;
- static final Map<String, String> COMBINED_PROJECTION_MAP;
- static final Map<String, String> SCHEMA_PROJECTION_MAP;
- static final Map<String, String> FAVICONS_PROJECTION_MAP;
- static final Map<String, String> THUMBNAILS_PROJECTION_MAP;
- static final Map<String, String> URL_ANNOTATIONS_PROJECTION_MAP;
- static final Map<String, String> VISIT_PROJECTION_MAP;
- static final Map<String, String> PAGE_METADATA_PROJECTION_MAP;
- static final Table[] sTables;
-
- static {
- sTables = new Table[] {
- // See awful shortcut assumption hack in getURLMetadataTable.
- new URLMetadataTable()
- };
- // We will reuse this.
- HashMap<String, String> map;
-
- // Bookmarks
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "bookmarks", BOOKMARKS);
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "bookmarks/#", BOOKMARKS_ID);
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "bookmarks/parents", BOOKMARKS_PARENT);
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "bookmarks/positions", BOOKMARKS_POSITIONS);
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "bookmarks/folder/#", BOOKMARKS_FOLDER_ID);
-
- map = new HashMap<String, String>();
- map.put(Bookmarks._ID, Bookmarks._ID);
- map.put(Bookmarks.TITLE, Bookmarks.TITLE);
- map.put(Bookmarks.URL, Bookmarks.URL);
- map.put(Bookmarks.FAVICON, Bookmarks.FAVICON);
- map.put(Bookmarks.FAVICON_ID, Bookmarks.FAVICON_ID);
- map.put(Bookmarks.FAVICON_URL, Bookmarks.FAVICON_URL);
- map.put(Bookmarks.TYPE, Bookmarks.TYPE);
- map.put(Bookmarks.PARENT, Bookmarks.PARENT);
- map.put(Bookmarks.POSITION, Bookmarks.POSITION);
- map.put(Bookmarks.TAGS, Bookmarks.TAGS);
- map.put(Bookmarks.DESCRIPTION, Bookmarks.DESCRIPTION);
- map.put(Bookmarks.KEYWORD, Bookmarks.KEYWORD);
- map.put(Bookmarks.DATE_CREATED, Bookmarks.DATE_CREATED);
- map.put(Bookmarks.DATE_MODIFIED, Bookmarks.DATE_MODIFIED);
- map.put(Bookmarks.GUID, Bookmarks.GUID);
- map.put(Bookmarks.IS_DELETED, Bookmarks.IS_DELETED);
- BOOKMARKS_PROJECTION_MAP = Collections.unmodifiableMap(map);
-
- // History
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "history", HISTORY);
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "history/#", HISTORY_ID);
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "history/old", HISTORY_OLD);
-
- map = new HashMap<String, String>();
- map.put(History._ID, History._ID);
- map.put(History.TITLE, History.TITLE);
- map.put(History.URL, History.URL);
- map.put(History.FAVICON, History.FAVICON);
- map.put(History.FAVICON_ID, History.FAVICON_ID);
- map.put(History.FAVICON_URL, History.FAVICON_URL);
- map.put(History.VISITS, History.VISITS);
- map.put(History.LOCAL_VISITS, History.LOCAL_VISITS);
- map.put(History.REMOTE_VISITS, History.REMOTE_VISITS);
- map.put(History.DATE_LAST_VISITED, History.DATE_LAST_VISITED);
- map.put(History.LOCAL_DATE_LAST_VISITED, History.LOCAL_DATE_LAST_VISITED);
- map.put(History.REMOTE_DATE_LAST_VISITED, History.REMOTE_DATE_LAST_VISITED);
- map.put(History.DATE_CREATED, History.DATE_CREATED);
- map.put(History.DATE_MODIFIED, History.DATE_MODIFIED);
- map.put(History.GUID, History.GUID);
- map.put(History.IS_DELETED, History.IS_DELETED);
- HISTORY_PROJECTION_MAP = Collections.unmodifiableMap(map);
-
- // Visits
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "visits", VISITS);
-
- map = new HashMap<String, String>();
- map.put(Visits._ID, Visits._ID);
- map.put(Visits.HISTORY_GUID, Visits.HISTORY_GUID);
- map.put(Visits.VISIT_TYPE, Visits.VISIT_TYPE);
- map.put(Visits.DATE_VISITED, Visits.DATE_VISITED);
- map.put(Visits.IS_LOCAL, Visits.IS_LOCAL);
- VISIT_PROJECTION_MAP = Collections.unmodifiableMap(map);
-
- // Favicons
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "favicons", FAVICONS);
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "favicons/#", FAVICON_ID);
-
- map = new HashMap<String, String>();
- map.put(Favicons._ID, Favicons._ID);
- map.put(Favicons.URL, Favicons.URL);
- map.put(Favicons.DATA, Favicons.DATA);
- map.put(Favicons.DATE_CREATED, Favicons.DATE_CREATED);
- map.put(Favicons.DATE_MODIFIED, Favicons.DATE_MODIFIED);
- FAVICONS_PROJECTION_MAP = Collections.unmodifiableMap(map);
-
- // Thumbnails
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "thumbnails", THUMBNAILS);
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "thumbnails/#", THUMBNAIL_ID);
-
- map = new HashMap<String, String>();
- map.put(Thumbnails._ID, Thumbnails._ID);
- map.put(Thumbnails.URL, Thumbnails.URL);
- map.put(Thumbnails.DATA, Thumbnails.DATA);
- THUMBNAILS_PROJECTION_MAP = Collections.unmodifiableMap(map);
-
- // Url annotations
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, TABLE_URL_ANNOTATIONS, URL_ANNOTATIONS);
-
- map = new HashMap<>();
- map.put(UrlAnnotations._ID, UrlAnnotations._ID);
- map.put(UrlAnnotations.URL, UrlAnnotations.URL);
- map.put(UrlAnnotations.KEY, UrlAnnotations.KEY);
- map.put(UrlAnnotations.VALUE, UrlAnnotations.VALUE);
- map.put(UrlAnnotations.DATE_CREATED, UrlAnnotations.DATE_CREATED);
- map.put(UrlAnnotations.DATE_MODIFIED, UrlAnnotations.DATE_MODIFIED);
- map.put(UrlAnnotations.SYNC_STATUS, UrlAnnotations.SYNC_STATUS);
- URL_ANNOTATIONS_PROJECTION_MAP = Collections.unmodifiableMap(map);
-
- // Combined bookmarks and history
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "combined", COMBINED);
-
- map = new HashMap<String, String>();
- map.put(Combined._ID, Combined._ID);
- map.put(Combined.BOOKMARK_ID, Combined.BOOKMARK_ID);
- map.put(Combined.HISTORY_ID, Combined.HISTORY_ID);
- map.put(Combined.URL, Combined.URL);
- map.put(Combined.TITLE, Combined.TITLE);
- map.put(Combined.VISITS, Combined.VISITS);
- map.put(Combined.DATE_LAST_VISITED, Combined.DATE_LAST_VISITED);
- map.put(Combined.FAVICON, Combined.FAVICON);
- map.put(Combined.FAVICON_ID, Combined.FAVICON_ID);
- map.put(Combined.FAVICON_URL, Combined.FAVICON_URL);
- map.put(Combined.LOCAL_DATE_LAST_VISITED, Combined.LOCAL_DATE_LAST_VISITED);
- map.put(Combined.REMOTE_DATE_LAST_VISITED, Combined.REMOTE_DATE_LAST_VISITED);
- map.put(Combined.LOCAL_VISITS_COUNT, Combined.LOCAL_VISITS_COUNT);
- map.put(Combined.REMOTE_VISITS_COUNT, Combined.REMOTE_VISITS_COUNT);
- COMBINED_PROJECTION_MAP = Collections.unmodifiableMap(map);
-
- map = new HashMap<>();
- map.put(PageMetadata._ID, PageMetadata._ID);
- map.put(PageMetadata.HISTORY_GUID, PageMetadata.HISTORY_GUID);
- map.put(PageMetadata.DATE_CREATED, PageMetadata.DATE_CREATED);
- map.put(PageMetadata.HAS_IMAGE, PageMetadata.HAS_IMAGE);
- map.put(PageMetadata.JSON, PageMetadata.JSON);
- PAGE_METADATA_PROJECTION_MAP = Collections.unmodifiableMap(map);
-
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "page_metadata", PAGE_METADATA);
-
- // Schema
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "schema", SCHEMA);
-
- map = new HashMap<String, String>();
- map.put(Schema.VERSION, Schema.VERSION);
- SCHEMA_PROJECTION_MAP = Collections.unmodifiableMap(map);
-
-
- // Control
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "control", CONTROL);
-
- for (Table table : sTables) {
- for (Table.ContentProviderInfo type : table.getContentProviderInfo()) {
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, type.name, type.id);
- }
- }
-
- // Combined pinned sites, top visited sites, and suggested sites
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "topsites", TOPSITES);
-
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, "highlights", HIGHLIGHTS);
-
- URI_MATCHER.addURI(BrowserContract.AUTHORITY, ActivityStreamBlocklist.TABLE_NAME, ACTIVITY_STREAM_BLOCKLIST);
- }
-
- private static class ShrinkMemoryReceiver extends BroadcastReceiver {
- private final WeakReference<BrowserProvider> mBrowserProviderWeakReference;
-
- public ShrinkMemoryReceiver(final BrowserProvider browserProvider) {
- mBrowserProviderWeakReference = new WeakReference<>(browserProvider);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final BrowserProvider browserProvider = mBrowserProviderWeakReference.get();
- if (browserProvider == null) {
- return;
- }
- final PerProfileDatabases<BrowserDatabaseHelper> databases = browserProvider.getDatabases();
- if (databases == null) {
- return;
- }
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- databases.shrinkMemory();
- }
- });
- }
- }
-
- private final ShrinkMemoryReceiver mShrinkMemoryReceiver = new ShrinkMemoryReceiver(this);
-
- @Override
- public boolean onCreate() {
- if (!super.onCreate()) {
- return false;
- }
-
- LocalBroadcastManager.getInstance(getContext()).registerReceiver(mShrinkMemoryReceiver,
- new IntentFilter(ACTION_SHRINK_MEMORY));
-
- return true;
- }
-
- @Override
- public void shutdown() {
- LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mShrinkMemoryReceiver);
-
- super.shutdown();
- }
-
- // Convenience accessor.
- // Assumes structure of sTables!
- private URLMetadataTable getURLMetadataTable() {
- return (URLMetadataTable) sTables[0];
- }
-
- private static boolean hasFaviconsInProjection(String[] projection) {
- if (projection == null) return true;
- for (int i = 0; i < projection.length; ++i) {
- if (projection[i].equals(FaviconColumns.FAVICON) ||
- projection[i].equals(FaviconColumns.FAVICON_URL))
- return true;
- }
-
- return false;
- }
-
- // Calculate these once, at initialization. isLoggable is too expensive to
- // have in-line in each log call.
- private static final boolean logDebug = Log.isLoggable(LOGTAG, Log.DEBUG);
- private static final boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
- protected static void trace(String message) {
- if (logVerbose) {
- Log.v(LOGTAG, message);
- }
- }
-
- protected static void debug(String message) {
- if (logDebug) {
- Log.d(LOGTAG, message);
- }
- }
-
- /**
- * Remove enough activity stream blocklist items to bring the database count below <code>retain</code>.
- *
- * Items will be removed according to their creation date, oldest being removed first.
- */
- private void expireActivityStreamBlocklist(final SQLiteDatabase db, final int retain) {
- Log.d(LOGTAG, "Expiring highlights blocklist.");
- final long rows = DatabaseUtils.queryNumEntries(db, TABLE_ACTIVITY_STREAM_BLOCKLIST);
-
- if (retain >= rows) {
- debug("Not expiring highlights blocklist: only have " + rows + " rows.");
- return;
- }
-
- final long toRemove = rows - retain;
-
- final String statement = "DELETE FROM " + TABLE_ACTIVITY_STREAM_BLOCKLIST + " WHERE " + ActivityStreamBlocklist._ID + " IN " +
- " ( SELECT " + ActivityStreamBlocklist._ID + " FROM " + TABLE_ACTIVITY_STREAM_BLOCKLIST + " " +
- "ORDER BY " + ActivityStreamBlocklist.CREATED + " ASC LIMIT " + toRemove + ")";
-
- beginWrite(db);
- db.execSQL(statement);
- }
-
- /**
- * Remove enough history items to bring the database count below <code>retain</code>,
- * removing no items with a modified time after <code>keepAfter</code>.
- *
- * Provide <code>keepAfter</code> less than or equal to zero to skip that check.
- *
- * Items will be removed according to last visited date.
- */
- private void expireHistory(final SQLiteDatabase db, final int retain, final long keepAfter) {
- Log.d(LOGTAG, "Expiring history.");
- final long rows = DatabaseUtils.queryNumEntries(db, TABLE_HISTORY);
-
- if (retain >= rows) {
- debug("Not expiring history: only have " + rows + " rows.");
- return;
- }
-
- final long toRemove = rows - retain;
- debug("Expiring at most " + toRemove + " rows earlier than " + keepAfter + ".");
-
- final String sql;
- if (keepAfter > 0) {
- sql = "DELETE FROM " + TABLE_HISTORY + " " +
- "WHERE MAX(" + History.DATE_LAST_VISITED + ", " + History.DATE_MODIFIED + ") < " + keepAfter + " " +
- " AND " + History._ID + " IN ( SELECT " +
- History._ID + " FROM " + TABLE_HISTORY + " " +
- "ORDER BY " + History.DATE_LAST_VISITED + " ASC LIMIT " + toRemove +
- ")";
- } else {
- sql = "DELETE FROM " + TABLE_HISTORY + " WHERE " + History._ID + " " +
- "IN ( SELECT " + History._ID + " FROM " + TABLE_HISTORY + " " +
- "ORDER BY " + History.DATE_LAST_VISITED + " ASC LIMIT " + toRemove + ")";
- }
- trace("Deleting using query: " + sql);
-
- beginWrite(db);
- db.execSQL(sql);
- }
-
- /**
- * Remove any thumbnails that for sites that aren't likely to be ever shown.
- * Items will be removed according to a frecency calculation and only if they are not pinned
- *
- * Call this method within a transaction.
- */
- private void expireThumbnails(final SQLiteDatabase db) {
- Log.d(LOGTAG, "Expiring thumbnails.");
- final String sortOrder = BrowserContract.getCombinedFrecencySortOrder(true, false);
- final String sql = "DELETE FROM " + TABLE_THUMBNAILS +
- " WHERE " + Thumbnails.URL + " NOT IN ( " +
- " SELECT " + Combined.URL +
- " FROM " + Combined.VIEW_NAME +
- " ORDER BY " + sortOrder +
- " LIMIT " + DEFAULT_EXPIRY_THUMBNAIL_COUNT +
- ") AND " + Thumbnails.URL + " NOT IN ( " +
- " SELECT " + Bookmarks.URL +
- " FROM " + TABLE_BOOKMARKS +
- " WHERE " + Bookmarks.PARENT + " = " + Bookmarks.FIXED_PINNED_LIST_ID +
- ") AND " + Thumbnails.URL + " NOT IN ( " +
- " SELECT " + Tabs.URL +
- " FROM " + TABLE_TABS +
- ")";
- trace("Clear thumbs using query: " + sql);
- db.execSQL(sql);
- }
-
- private boolean shouldIncrementVisits(Uri uri) {
- String incrementVisits = uri.getQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS);
- return Boolean.parseBoolean(incrementVisits);
- }
-
- private boolean shouldIncrementRemoteAggregates(Uri uri) {
- final String incrementRemoteAggregates = uri.getQueryParameter(BrowserContract.PARAM_INCREMENT_REMOTE_AGGREGATES);
- return Boolean.parseBoolean(incrementRemoteAggregates);
- }
-
- @Override
- public String getType(Uri uri) {
- final int match = URI_MATCHER.match(uri);
-
- trace("Getting URI type: " + uri);
-
- switch (match) {
- case BOOKMARKS:
- trace("URI is BOOKMARKS: " + uri);
- return Bookmarks.CONTENT_TYPE;
- case BOOKMARKS_ID:
- trace("URI is BOOKMARKS_ID: " + uri);
- return Bookmarks.CONTENT_ITEM_TYPE;
- case HISTORY:
- trace("URI is HISTORY: " + uri);
- return History.CONTENT_TYPE;
- case HISTORY_ID:
- trace("URI is HISTORY_ID: " + uri);
- return History.CONTENT_ITEM_TYPE;
- default:
- String type = getContentItemType(match);
- if (type != null) {
- trace("URI is " + type);
- return type;
- }
-
- debug("URI has unrecognized type: " + uri);
- return null;
- }
- }
-
- @SuppressWarnings("fallthrough")
- @Override
- public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
- trace("Calling delete in transaction on URI: " + uri);
- final SQLiteDatabase db = getWritableDatabase(uri);
-
- final int match = URI_MATCHER.match(uri);
- int deleted = 0;
-
- switch (match) {
- case BOOKMARKS_ID:
- trace("Delete on BOOKMARKS_ID: " + uri);
-
- selection = DBUtils.concatenateWhere(selection, TABLE_BOOKMARKS + "._id = ?");
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- // fall through
- case BOOKMARKS: {
- trace("Deleting bookmarks: " + uri);
- deleted = deleteBookmarks(uri, selection, selectionArgs);
- deleteUnusedImages(uri);
- break;
- }
-
- case HISTORY_ID:
- trace("Delete on HISTORY_ID: " + uri);
-
- selection = DBUtils.concatenateWhere(selection, TABLE_HISTORY + "._id = ?");
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- // fall through
- case HISTORY: {
- trace("Deleting history: " + uri);
- beginWrite(db);
- /**
- * Deletes from Sync are actual DELETE statements, which will cascade delete relevant visits.
- * Fennec's deletes mark records as deleted and wipe out all information (except for GUID).
- * Eventually, Fennec will purge history records that were marked as deleted for longer than some
- * period of time (e.g. 20 days).
- * See {@link SharedBrowserDatabaseProvider#cleanUpSomeDeletedRecords(Uri, String)}.
- */
- final ArrayList<String> historyGUIDs = getHistoryGUIDsFromSelection(db, uri, selection, selectionArgs);
-
- if (!isCallerSync(uri)) {
- deleteVisitsForHistory(db, historyGUIDs);
- }
- deletePageMetadataForHistory(db, historyGUIDs);
- deleted = deleteHistory(db, uri, selection, selectionArgs);
- deleteUnusedImages(uri);
- break;
- }
-
- case VISITS:
- trace("Deleting visits: " + uri);
- beginWrite(db);
- deleted = deleteVisits(uri, selection, selectionArgs);
- break;
-
- case HISTORY_OLD: {
- String priority = uri.getQueryParameter(BrowserContract.PARAM_EXPIRE_PRIORITY);
- long keepAfter = System.currentTimeMillis() - DEFAULT_EXPIRY_PRESERVE_WINDOW;
- int retainCount = DEFAULT_EXPIRY_RETAIN_COUNT;
-
- if (BrowserContract.ExpirePriority.AGGRESSIVE.toString().equals(priority)) {
- keepAfter = 0;
- retainCount = AGGRESSIVE_EXPIRY_RETAIN_COUNT;
- }
- expireHistory(db, retainCount, keepAfter);
- expireActivityStreamBlocklist(db, retainCount / ACTIVITYSTREAM_BLOCKLIST_EXPIRY_FACTOR);
- expireThumbnails(db);
- deleteUnusedImages(uri);
- break;
- }
-
- case FAVICON_ID:
- debug("Delete on FAVICON_ID: " + uri);
-
- selection = DBUtils.concatenateWhere(selection, TABLE_FAVICONS + "._id = ?");
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- // fall through
- case FAVICONS: {
- trace("Deleting favicons: " + uri);
- beginWrite(db);
- deleted = deleteFavicons(uri, selection, selectionArgs);
- break;
- }
-
- case THUMBNAIL_ID:
- debug("Delete on THUMBNAIL_ID: " + uri);
-
- selection = DBUtils.concatenateWhere(selection, TABLE_THUMBNAILS + "._id = ?");
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- // fall through
- case THUMBNAILS: {
- trace("Deleting thumbnails: " + uri);
- beginWrite(db);
- deleted = deleteThumbnails(uri, selection, selectionArgs);
- break;
- }
-
- case URL_ANNOTATIONS:
- trace("Delete on URL_ANNOTATIONS: " + uri);
- deleteUrlAnnotation(uri, selection, selectionArgs);
- break;
-
- case PAGE_METADATA:
- trace("Delete on PAGE_METADATA: " + uri);
- deleted = deletePageMetadata(uri, selection, selectionArgs);
- break;
-
- default: {
- Table table = findTableFor(match);
- if (table == null) {
- throw new UnsupportedOperationException("Unknown delete URI " + uri);
- }
- trace("Deleting TABLE: " + uri);
- beginWrite(db);
- deleted = table.delete(db, uri, match, selection, selectionArgs);
- }
- }
-
- debug("Deleted " + deleted + " rows for URI: " + uri);
-
- return deleted;
- }
-
- @Override
- public Uri insertInTransaction(Uri uri, ContentValues values) {
- trace("Calling insert in transaction on URI: " + uri);
-
- int match = URI_MATCHER.match(uri);
- long id = -1;
-
- switch (match) {
- case BOOKMARKS: {
- trace("Insert on BOOKMARKS: " + uri);
- id = insertBookmark(uri, values);
- break;
- }
-
- case HISTORY: {
- trace("Insert on HISTORY: " + uri);
- id = insertHistory(uri, values);
- break;
- }
-
- case VISITS: {
- trace("Insert on VISITS: " + uri);
- id = insertVisit(uri, values);
- break;
- }
-
- case FAVICONS: {
- trace("Insert on FAVICONS: " + uri);
- id = insertFavicon(uri, values);
- break;
- }
-
- case THUMBNAILS: {
- trace("Insert on THUMBNAILS: " + uri);
- id = insertThumbnail(uri, values);
- break;
- }
-
- case URL_ANNOTATIONS: {
- trace("Insert on URL_ANNOTATIONS: " + uri);
- id = insertUrlAnnotation(uri, values);
- break;
- }
-
- case ACTIVITY_STREAM_BLOCKLIST: {
- trace("Insert on ACTIVITY_STREAM_BLOCKLIST: " + uri);
- id = insertActivityStreamBlocklistSite(uri, values);
- break;
- }
-
- case PAGE_METADATA: {
- trace("Insert on PAGE_METADATA: " + uri);
- id = insertPageMetadata(uri, values);
- break;
- }
-
- default: {
- Table table = findTableFor(match);
- if (table == null) {
- throw new UnsupportedOperationException("Unknown insert URI " + uri);
- }
-
- trace("Insert on TABLE: " + uri);
- final SQLiteDatabase db = getWritableDatabase(uri);
- beginWrite(db);
- id = table.insert(db, uri, match, values);
- }
- }
-
- debug("Inserted ID in database: " + id);
-
- if (id >= 0)
- return ContentUris.withAppendedId(uri, id);
-
- return null;
- }
-
- @SuppressWarnings("fallthrough")
- @Override
- public int updateInTransaction(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
- trace("Calling update in transaction on URI: " + uri);
-
- int match = URI_MATCHER.match(uri);
- int updated = 0;
-
- final SQLiteDatabase db = getWritableDatabase(uri);
- switch (match) {
- // We provide a dedicated (hacky) API for callers to bulk-update the positions of
- // folder children by passing an array of GUID strings as `selectionArgs`.
- // Each child will have its position column set to its index in the provided array.
- //
- // This avoids callers having to issue a large number of UPDATE queries through
- // the usual channels. See Bug 728783.
- //
- // Note that this is decidedly not a general-purpose API; use at your own risk.
- // `values` and `selection` are ignored.
- case BOOKMARKS_POSITIONS: {
- debug("Update on BOOKMARKS_POSITIONS: " + uri);
-
- // This already starts and finishes its own transaction.
- updated = updateBookmarkPositions(uri, selectionArgs);
- break;
- }
-
- case BOOKMARKS_PARENT: {
- debug("Update on BOOKMARKS_PARENT: " + uri);
- beginWrite(db);
- updated = updateBookmarkParents(db, values, selection, selectionArgs);
- break;
- }
-
- case BOOKMARKS_ID:
- debug("Update on BOOKMARKS_ID: " + uri);
-
- selection = DBUtils.concatenateWhere(selection, TABLE_BOOKMARKS + "._id = ?");
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- // fall through
- case BOOKMARKS: {
- debug("Updating bookmark: " + uri);
- if (shouldUpdateOrInsert(uri)) {
- updated = updateOrInsertBookmark(uri, values, selection, selectionArgs);
- } else {
- updated = updateBookmarks(uri, values, selection, selectionArgs);
- }
- break;
- }
-
- case HISTORY_ID:
- debug("Update on HISTORY_ID: " + uri);
-
- selection = DBUtils.concatenateWhere(selection, TABLE_HISTORY + "._id = ?");
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- // fall through
- case HISTORY: {
- debug("Updating history: " + uri);
- if (shouldUpdateOrInsert(uri)) {
- updated = updateOrInsertHistory(uri, values, selection, selectionArgs);
- } else {
- updated = updateHistory(uri, values, selection, selectionArgs);
- }
- if (shouldIncrementVisits(uri)) {
- insertVisitForHistory(uri, values, selection, selectionArgs);
- }
- break;
- }
-
- case FAVICONS: {
- debug("Update on FAVICONS: " + uri);
-
- String url = values.getAsString(Favicons.URL);
- String faviconSelection = null;
- String[] faviconSelectionArgs = null;
-
- if (!TextUtils.isEmpty(url)) {
- faviconSelection = Favicons.URL + " = ?";
- faviconSelectionArgs = new String[] { url };
- }
-
- if (shouldUpdateOrInsert(uri)) {
- updated = updateOrInsertFavicon(uri, values, faviconSelection, faviconSelectionArgs);
- } else {
- updated = updateExistingFavicon(uri, values, faviconSelection, faviconSelectionArgs);
- }
- break;
- }
-
- case THUMBNAILS: {
- debug("Update on THUMBNAILS: " + uri);
-
- String url = values.getAsString(Thumbnails.URL);
-
- // if no URL is provided, update all of the entries
- if (TextUtils.isEmpty(values.getAsString(Thumbnails.URL))) {
- updated = updateExistingThumbnail(uri, values, null, null);
- } else if (shouldUpdateOrInsert(uri)) {
- updated = updateOrInsertThumbnail(uri, values, Thumbnails.URL + " = ?",
- new String[] { url });
- } else {
- updated = updateExistingThumbnail(uri, values, Thumbnails.URL + " = ?",
- new String[] { url });
- }
- break;
- }
-
- case URL_ANNOTATIONS:
- updateUrlAnnotation(uri, values, selection, selectionArgs);
- break;
-
- default: {
- Table table = findTableFor(match);
- if (table == null) {
- throw new UnsupportedOperationException("Unknown update URI " + uri);
- }
- trace("Update TABLE: " + uri);
-
- beginWrite(db);
- updated = table.update(db, uri, match, values, selection, selectionArgs);
- if (shouldUpdateOrInsert(uri) && updated == 0) {
- trace("No update, inserting for URL: " + uri);
- table.insert(db, uri, match, values);
- updated = 1;
- }
- }
- }
-
- debug("Updated " + updated + " rows for URI: " + uri);
- return updated;
- }
-
- /**
- * Get topsites by themselves, without the inclusion of pinned sites. Suggested sites
- * will be appended (if necessary) to the end of the list in order to provide up to PARAM_LIMIT items.
- */
- private Cursor getPlainTopSites(final Uri uri) {
- final SQLiteDatabase db = getReadableDatabase(uri);
-
- final String limitParam = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
- final int limit;
- if (limitParam != null) {
- limit = Integer.parseInt(limitParam);
- } else {
- limit = 12;
- }
-
- // Filter out: unvisited pages (history_id == -1) pinned (and other special) sites, deleted sites,
- // and about: pages.
- final String ignoreForTopSitesWhereClause =
- "(" + Combined.HISTORY_ID + " IS NOT -1)" +
- " AND " +
- Combined.URL + " NOT IN (SELECT " +
- Bookmarks.URL + " FROM " + TABLE_BOOKMARKS + " WHERE " +
- DBUtils.qualifyColumn(TABLE_BOOKMARKS, Bookmarks.PARENT) + " < " + Bookmarks.FIXED_ROOT_ID + " AND " +
- DBUtils.qualifyColumn(TABLE_BOOKMARKS, Bookmarks.IS_DELETED) + " == 0)" +
- " AND " +
- "(" + Combined.URL + " NOT LIKE ?)";
-
- final String[] ignoreForTopSitesArgs = new String[] {
- AboutPages.URL_FILTER
- };
-
- final Cursor c = db.rawQuery("SELECT " +
- Bookmarks._ID + ", " +
- Combined.BOOKMARK_ID + ", " +
- Combined.HISTORY_ID + ", " +
- Bookmarks.URL + ", " +
- Bookmarks.TITLE + ", " +
- Combined.HISTORY_ID + ", " +
- TopSites.TYPE_TOP + " AS " + TopSites.TYPE +
- " FROM " + Combined.VIEW_NAME +
- " WHERE " + ignoreForTopSitesWhereClause +
- " ORDER BY " + BrowserContract.getCombinedFrecencySortOrder(true, false) +
- " LIMIT " + limit,
- ignoreForTopSitesArgs);
-
- c.setNotificationUri(getContext().getContentResolver(),
- BrowserContract.AUTHORITY_URI);
-
- if (c.getCount() == limit) {
- return c;
- }
-
- // If we don't have enough data: get suggested sites too
- final SuggestedSites suggestedSites = BrowserDB.from(GeckoProfile.get(
- getContext(), uri.getQueryParameter(BrowserContract.PARAM_PROFILE))).getSuggestedSites();
-
- final Cursor suggestedSitesCursor = suggestedSites.get(limit - c.getCount());
-
- return new MergeCursor(new Cursor[]{
- c,
- suggestedSitesCursor
- });
- }
-
- private Cursor getTopSites(final Uri uri) {
- // In order to correctly merge the top and pinned sites we:
- //
- // 1. Generate a list of free ids for topsites - this is the positions that are NOT used by pinned sites.
- // We do this using a subquery with a self-join in order to generate rowids, that allow us to join with
- // the list of topsites.
- // 2. Generate the list of topsites in order of frecency.
- // 3. Join these, so that each topsite is given its resulting position
- // 4. UNION all with the pinned sites, and order by position
- //
- // Suggested sites are placed after the topsites, but might still be interspersed with the suggested sites,
- // hence we append these to the topsite list, and treat these identically to topsites from this point on.
- //
- // We require rowids to join the two lists, however subqueries aren't given rowids - hence we use two different
- // tricks to generate these:
- // 1. The list of free ids is small, hence we can do a self-join to generate rowids.
- // 2. The topsites list is larger, hence we use a temporary table, which automatically provides rowids.
-
- final SQLiteDatabase db = getWritableDatabase(uri);
-
- final String TABLE_TOPSITES = "topsites";
-
- final String limitParam = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
- final String gridLimitParam = uri.getQueryParameter(BrowserContract.PARAM_SUGGESTEDSITES_LIMIT);
-
- final int totalLimit;
- final int suggestedGridLimit;
-
- if (limitParam == null) {
- totalLimit = 50;
- } else {
- totalLimit = Integer.parseInt(limitParam, 10);
- }
-
- if (gridLimitParam == null) {
- suggestedGridLimit = getContext().getResources().getInteger(R.integer.number_of_top_sites);
- } else {
- suggestedGridLimit = Integer.parseInt(gridLimitParam, 10);
- }
-
- final String pinnedSitesFromClause = "FROM " + TABLE_BOOKMARKS + " WHERE " +
- Bookmarks.PARENT + " == " + Bookmarks.FIXED_PINNED_LIST_ID +
- " AND " + Bookmarks.IS_DELETED + " IS NOT 1";
-
- // Ideally we'd use a recursive CTE to generate our sequence, e.g. something like this worked at one point:
- // " WITH RECURSIVE" +
- // " cnt(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM cnt WHERE x < 6)" +
- // However that requires SQLite >= 3.8.3 (available on Android >= 5.0), so in the meantime
- // we use a temporary numbers table.
- // Note: SQLite rowids are 1-indexed, whereas we're expecting 0-indexed values for the position. Our numbers
- // table starts at position = 0, which ensures the correct results here.
- final String freeIDSubquery =
- " SELECT count(free_ids.position) + 1 AS rowid, numbers.position AS " + Bookmarks.POSITION +
- " FROM (SELECT position FROM numbers WHERE position NOT IN (SELECT " + Bookmarks.POSITION + " " + pinnedSitesFromClause + ")) AS numbers" +
- " LEFT OUTER JOIN " +
- " (SELECT position FROM numbers WHERE position NOT IN (SELECT " + Bookmarks.POSITION + " " + pinnedSitesFromClause + ")) AS free_ids" +
- " ON numbers.position > free_ids.position" +
- " GROUP BY numbers.position" +
- " ORDER BY numbers.position ASC" +
- " LIMIT " + suggestedGridLimit;
-
- // Filter out: unvisited pages (history_id == -1) pinned (and other special) sites, deleted sites,
- // and about: pages.
- final String ignoreForTopSitesWhereClause =
- "(" + Combined.HISTORY_ID + " IS NOT -1)" +
- " AND " +
- Combined.URL + " NOT IN (SELECT " +
- Bookmarks.URL + " FROM bookmarks WHERE " +
- DBUtils.qualifyColumn("bookmarks", Bookmarks.PARENT) + " < " + Bookmarks.FIXED_ROOT_ID + " AND " +
- DBUtils.qualifyColumn("bookmarks", Bookmarks.IS_DELETED) + " == 0)" +
- " AND " +
- "(" + Combined.URL + " NOT LIKE ?)";
-
- final String[] ignoreForTopSitesArgs = new String[] {
- AboutPages.URL_FILTER
- };
-
- // Stuff the suggested sites into SQL: this allows us to filter pinned and topsites out of the suggested
- // sites list as part of the final query (as opposed to walking cursors in java)
- final SuggestedSites suggestedSites = BrowserDB.from(GeckoProfile.get(
- getContext(), uri.getQueryParameter(BrowserContract.PARAM_PROFILE))).getSuggestedSites();
-
- StringBuilder suggestedSitesBuilder = new StringBuilder();
- // We could access the underlying data here, however SuggestedSites also performs filtering on the suggested
- // sites list, which means we'd need to process the lists within SuggestedSites in any case. If we're doing
- // that processing, there is little real between us using a MatrixCursor, or a Map (or List) instead of the
- // MatrixCursor.
- final Cursor suggestedSitesCursor = suggestedSites.get(suggestedGridLimit);
-
- String[] suggestedSiteArgs = new String[0];
-
- boolean hasProcessedAnySuggestedSites = false;
-
- final int idColumnIndex = suggestedSitesCursor.getColumnIndexOrThrow(Bookmarks._ID);
- final int urlColumnIndex = suggestedSitesCursor.getColumnIndexOrThrow(Bookmarks.URL);
- final int titleColumnIndex = suggestedSitesCursor.getColumnIndexOrThrow(Bookmarks.TITLE);
-
- while (suggestedSitesCursor.moveToNext()) {
- // We'll be using this as a subquery, hence we need to avoid the preceding UNION ALL
- if (hasProcessedAnySuggestedSites) {
- suggestedSitesBuilder.append(" UNION ALL");
- } else {
- hasProcessedAnySuggestedSites = true;
- }
- suggestedSitesBuilder.append(" SELECT" +
- " ? AS " + Bookmarks._ID + "," +
- " ? AS " + Bookmarks.URL + "," +
- " ? AS " + Bookmarks.TITLE);
-
- suggestedSiteArgs = DBUtils.appendSelectionArgs(suggestedSiteArgs,
- new String[] {
- suggestedSitesCursor.getString(idColumnIndex),
- suggestedSitesCursor.getString(urlColumnIndex),
- suggestedSitesCursor.getString(titleColumnIndex)
- });
- }
- suggestedSitesCursor.close();
-
- boolean hasPreparedBlankTiles = false;
-
- // We can somewhat reduce the number of blanks we produce by eliminating suggested sites.
- // We do the actual limit calculation in SQL (since we need to take into account the number
- // of pinned sites too), but this might avoid producing 5 or so additional blank tiles
- // that would then need to be filtered out.
- final int maxBlanksNeeded = suggestedGridLimit - suggestedSitesCursor.getCount();
-
- final StringBuilder blanksBuilder = new StringBuilder();
- for (int i = 0; i < maxBlanksNeeded; i++) {
- if (hasPreparedBlankTiles) {
- blanksBuilder.append(" UNION ALL");
- } else {
- hasPreparedBlankTiles = true;
- }
-
- blanksBuilder.append(" SELECT" +
- " -1 AS " + Bookmarks._ID + "," +
- " '' AS " + Bookmarks.URL + "," +
- " '' AS " + Bookmarks.TITLE);
- }
-
-
-
- // To restrict suggested sites to the grid, we simply subtract the number of topsites (which have already had
- // the pinned sites filtered out), and the number of pinned sites.
- // SQLite completely ignores negative limits, hence we need to manually limit to 0 in this case.
- final String suggestedLimitClause = " LIMIT MAX(0, (" + suggestedGridLimit + " - (SELECT COUNT(*) FROM " + TABLE_TOPSITES + ") - (SELECT COUNT(*) " + pinnedSitesFromClause + "))) ";
-
- // Pinned site positions are zero indexed, but we need to get the maximum 1-indexed position.
- // Hence to correctly calculate the largest pinned position (which should be 0 if there are
- // no sites, or 1-6 if we have at least one pinned site), we coalesce the DB position (0-5)
- // with -1 to represent no-sites, which allows us to directly add 1 to obtain the expected value
- // regardless of whether a position was actually retrieved.
- final String blanksLimitClause = " LIMIT MAX(0, " +
- "COALESCE((SELECT " + Bookmarks.POSITION + " " + pinnedSitesFromClause + "), -1) + 1" +
- " - (SELECT COUNT(*) " + pinnedSitesFromClause + ")" +
- " - (SELECT COUNT(*) FROM " + TABLE_TOPSITES + ")" +
- ")";
-
- db.beginTransaction();
- try {
- db.execSQL("DROP TABLE IF EXISTS " + TABLE_TOPSITES);
-
- db.execSQL("CREATE TEMP TABLE " + TABLE_TOPSITES + " AS" +
- " SELECT " +
- Bookmarks._ID + ", " +
- Combined.BOOKMARK_ID + ", " +
- Combined.HISTORY_ID + ", " +
- Bookmarks.URL + ", " +
- Bookmarks.TITLE + ", " +
- Combined.HISTORY_ID + ", " +
- TopSites.TYPE_TOP + " AS " + TopSites.TYPE +
- " FROM " + Combined.VIEW_NAME +
- " WHERE " + ignoreForTopSitesWhereClause +
- " ORDER BY " + BrowserContract.getCombinedFrecencySortOrder(true, false) +
- " LIMIT " + totalLimit,
-
- ignoreForTopSitesArgs);
-
- if (hasProcessedAnySuggestedSites) {
- db.execSQL("INSERT INTO " + TABLE_TOPSITES +
- // We need to LIMIT _after_ selecting the relevant suggested sites, which requires us to
- // use an additional internal subquery, since we cannot LIMIT a subquery that is part of UNION ALL.
- // Hence the weird SELECT * FROM (SELECT ...relevant suggested sites... LIMIT ?)
- " SELECT * FROM (SELECT " +
- Bookmarks._ID + ", " +
- Bookmarks._ID + " AS " + Combined.BOOKMARK_ID + ", " +
- " -1 AS " + Combined.HISTORY_ID + ", " +
- Bookmarks.URL + ", " +
- Bookmarks.TITLE + ", " +
- "NULL AS " + Combined.HISTORY_ID + ", " +
- TopSites.TYPE_SUGGESTED + " as " + TopSites.TYPE +
- " FROM ( " + suggestedSitesBuilder.toString() + " )" +
- " WHERE " +
- Bookmarks.URL + " NOT IN (SELECT url FROM " + TABLE_TOPSITES + ")" +
- " AND " +
- Bookmarks.URL + " NOT IN (SELECT url " + pinnedSitesFromClause + ")" +
- suggestedLimitClause + " )",
-
- suggestedSiteArgs);
- }
-
- if (hasPreparedBlankTiles) {
- db.execSQL("INSERT INTO " + TABLE_TOPSITES +
- // We need to LIMIT _after_ selecting the relevant suggested sites, which requires us to
- // use an additional internal subquery, since we cannot LIMIT a subquery that is part of UNION ALL.
- // Hence the weird SELECT * FROM (SELECT ...relevant suggested sites... LIMIT ?)
- " SELECT * FROM (SELECT " +
- Bookmarks._ID + ", " +
- Bookmarks._ID + " AS " + Combined.BOOKMARK_ID + ", " +
- " -1 AS " + Combined.HISTORY_ID + ", " +
- Bookmarks.URL + ", " +
- Bookmarks.TITLE + ", " +
- "NULL AS " + Combined.HISTORY_ID + ", " +
- TopSites.TYPE_BLANK + " as " + TopSites.TYPE +
- " FROM ( " + blanksBuilder.toString() + " )" +
- blanksLimitClause + " )");
- }
-
- // If we retrieve more topsites than we have free positions for in the freeIdSubquery,
- // we will have topsites that don't receive a position when joining TABLE_TOPSITES
- // with freeIdSubquery. Hence we need to coalesce the position with a generated position.
- // We know that the difference in positions will be at most suggestedGridLimit, hence we
- // can add that to the rowid to generate a safe position.
- // I.e. if we have 6 pinned sites then positions 0..5 are filled, the JOIN results in
- // the first N rows having positions 6..(N+6), so row N+1 should receive a position that is at
- // least N+1+6, which is equal to rowid + 6.
- final SQLiteCursor c = (SQLiteCursor) db.rawQuery(
- "SELECT " +
- Bookmarks._ID + ", " +
- TopSites.BOOKMARK_ID + ", " +
- TopSites.HISTORY_ID + ", " +
- Bookmarks.URL + ", " +
- Bookmarks.TITLE + ", " +
- "COALESCE(" + Bookmarks.POSITION + ", " +
- DBUtils.qualifyColumn(TABLE_TOPSITES, "rowid") + " + " + suggestedGridLimit +
- ")" + " AS " + Bookmarks.POSITION + ", " +
- Combined.HISTORY_ID + ", " +
- TopSites.TYPE +
- " FROM " + TABLE_TOPSITES +
- " LEFT OUTER JOIN " + // TABLE_IDS +
- "(" + freeIDSubquery + ") AS id_results" +
- " ON " + DBUtils.qualifyColumn(TABLE_TOPSITES, "rowid") +
- " = " + DBUtils.qualifyColumn("id_results", "rowid") +
-
- " UNION ALL " +
-
- "SELECT " +
- Bookmarks._ID + ", " +
- Bookmarks._ID + " AS " + TopSites.BOOKMARK_ID + ", " +
- " -1 AS " + TopSites.HISTORY_ID + ", " +
- Bookmarks.URL + ", " +
- Bookmarks.TITLE + ", " +
- Bookmarks.POSITION + ", " +
- "NULL AS " + Combined.HISTORY_ID + ", " +
- TopSites.TYPE_PINNED + " as " + TopSites.TYPE +
- " " + pinnedSitesFromClause +
-
- " ORDER BY " + Bookmarks.POSITION,
-
- null);
-
- c.setNotificationUri(getContext().getContentResolver(),
- BrowserContract.AUTHORITY_URI);
-
- // Force the cursor to be compiled and the cursor-window filled now:
- // (A) without compiling the cursor now we won't have access to the TEMP table which
- // is removed as soon as we close our connection.
- // (B) this might also mitigate the situation causing this crash where we're accessing
- // a cursor and crashing in fillWindow.
- c.moveToFirst();
-
- db.setTransactionSuccessful();
- return c;
- } finally {
- db.endTransaction();
- }
- }
-
- /**
- * Obtain a set of links for highlights (from bookmarks and history).
- *
- * Based on the query for Activity^ Stream (desktop):
- * https://github.com/mozilla/activity-stream/blob/9eb9f451b553bb62ae9b8d6b41a8ef94a2e020ea/addon/PlacesProvider.js#L578
- */
- public Cursor getHighlights(final SQLiteDatabase db, String limit) {
- final int totalLimit = limit == null ? 20 : Integer.parseInt(limit);
-
- final long threeDaysAgo = System.currentTimeMillis() - (1000 * 60 * 60 * 24 * 3);
- final long bookmarkLimit = 1;
-
- // Select recent bookmarks that have not been visited much
- final String bookmarksQuery = "SELECT * FROM (SELECT " +
- "-1 AS " + Combined.HISTORY_ID + ", " +
- DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks._ID) + " AS " + Combined.BOOKMARK_ID + ", " +
- DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.URL) + ", " +
- DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.TITLE) + ", " +
- DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.DATE_CREATED) + " AS " + Highlights.DATE + " " +
- "FROM " + Bookmarks.TABLE_NAME + " " +
- "LEFT JOIN " + History.TABLE_NAME + " ON " +
- DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.URL) + " = " +
- DBUtils.qualifyColumn(History.TABLE_NAME, History.URL) + " " +
- "WHERE " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.DATE_CREATED) + " > " + threeDaysAgo + " " +
- "AND (" + DBUtils.qualifyColumn(History.TABLE_NAME, History.VISITS) + " <= 3 " +
- "OR " + DBUtils.qualifyColumn(History.TABLE_NAME, History.VISITS) + " IS NULL) " +
- "AND " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.IS_DELETED) + " = 0 " +
- "AND " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.TYPE) + " = " + Bookmarks.TYPE_BOOKMARK + " " +
- "AND " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.URL) + " NOT IN (SELECT " + ActivityStreamBlocklist.URL + " FROM " + ActivityStreamBlocklist.TABLE_NAME + " )" +
- "ORDER BY " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.DATE_CREATED) + " DESC " +
- "LIMIT " + bookmarkLimit + ")";
-
- final long last30Minutes = System.currentTimeMillis() - (1000 * 60 * 30);
- final long historyLimit = totalLimit - bookmarkLimit;
-
- // Select recent history that has not been visited much.
- final String historyQuery = "SELECT * FROM (SELECT " +
- History._ID + " AS " + Combined.HISTORY_ID + ", " +
- "-1 AS " + Combined.BOOKMARK_ID + ", " +
- History.URL + ", " +
- History.TITLE + ", " +
- History.DATE_LAST_VISITED + " AS " + Highlights.DATE + " " +
- "FROM " + History.TABLE_NAME + " " +
- "WHERE " + History.DATE_LAST_VISITED + " < " + last30Minutes + " " +
- "AND " + History.VISITS + " <= 3 " +
- "AND " + History.TITLE + " NOT NULL AND " + History.TITLE + " != '' " +
- "AND " + History.IS_DELETED + " = 0 " +
- "AND " + History.URL + " NOT IN (SELECT " + ActivityStreamBlocklist.URL + " FROM " + ActivityStreamBlocklist.TABLE_NAME + " )" +
- // TODO: Implement domain black list (bug 1298786)
- // TODO: Group by host (bug 1298785)
- "ORDER BY " + History.DATE_LAST_VISITED + " DESC " +
- "LIMIT " + historyLimit + ")";
-
- final String query = "SELECT DISTINCT * " +
- "FROM (" + bookmarksQuery + " " +
- "UNION ALL " + historyQuery + ") " +
- "GROUP BY " + Combined.URL + ";";
-
- final Cursor cursor = db.rawQuery(query, null);
-
- cursor.setNotificationUri(getContext().getContentResolver(),
- BrowserContract.AUTHORITY_URI);
-
- return cursor;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder) {
- final int match = URI_MATCHER.match(uri);
-
- // Handle only queries requiring a writable DB connection here: most queries need only a readable
- // connection, hence we can get a readable DB once, and then handle most queries within a switch.
- // TopSites requires a writable connection (because of the temporary tables it uses), hence
- // we handle that separately, i.e. before retrieving a readable connection.
- if (match == TOPSITES) {
- if (uri.getBooleanQueryParameter(BrowserContract.PARAM_TOPSITES_DISABLE_PINNED, false)) {
- return getPlainTopSites(uri);
- } else {
- return getTopSites(uri);
- }
- }
-
- SQLiteDatabase db = getReadableDatabase(uri);
-
- SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
- String groupBy = null;
-
- switch (match) {
- case BOOKMARKS_FOLDER_ID:
- case BOOKMARKS_ID:
- case BOOKMARKS: {
- debug("Query is on bookmarks: " + uri);
-
- if (match == BOOKMARKS_ID) {
- selection = DBUtils.concatenateWhere(selection, Bookmarks._ID + " = ?");
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- } else if (match == BOOKMARKS_FOLDER_ID) {
- selection = DBUtils.concatenateWhere(selection, Bookmarks.PARENT + " = ?");
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- }
-
- if (!shouldShowDeleted(uri))
- selection = DBUtils.concatenateWhere(Bookmarks.IS_DELETED + " = 0", selection);
-
- if (TextUtils.isEmpty(sortOrder)) {
- sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER;
- } else {
- debug("Using sort order " + sortOrder + ".");
- }
-
- qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
-
- if (hasFaviconsInProjection(projection)) {
- qb.setTables(VIEW_BOOKMARKS_WITH_FAVICONS);
- } else if (selection != null && selection.contains(Bookmarks.ANNOTATION_KEY)) {
- qb.setTables(VIEW_BOOKMARKS_WITH_ANNOTATIONS);
-
- groupBy = uri.getQueryParameter(BrowserContract.PARAM_GROUP_BY);
- } else {
- qb.setTables(TABLE_BOOKMARKS);
- }
-
- break;
- }
-
- case HISTORY_ID:
- selection = DBUtils.concatenateWhere(selection, History._ID + " = ?");
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- // fall through
- case HISTORY: {
- debug("Query is on history: " + uri);
-
- if (!shouldShowDeleted(uri))
- selection = DBUtils.concatenateWhere(History.IS_DELETED + " = 0", selection);
-
- if (TextUtils.isEmpty(sortOrder))
- sortOrder = DEFAULT_HISTORY_SORT_ORDER;
-
- qb.setProjectionMap(HISTORY_PROJECTION_MAP);
-
- if (hasFaviconsInProjection(projection))
- qb.setTables(VIEW_HISTORY_WITH_FAVICONS);
- else
- qb.setTables(TABLE_HISTORY);
-
- break;
- }
-
- case VISITS:
- debug("Query is on visits: " + uri);
- qb.setProjectionMap(VISIT_PROJECTION_MAP);
- qb.setTables(TABLE_VISITS);
-
- if (TextUtils.isEmpty(sortOrder)) {
- sortOrder = DEFAULT_VISITS_SORT_ORDER;
- }
- break;
-
- case FAVICON_ID:
- selection = DBUtils.concatenateWhere(selection, Favicons._ID + " = ?");
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- // fall through
- case FAVICONS: {
- debug("Query is on favicons: " + uri);
-
- qb.setProjectionMap(FAVICONS_PROJECTION_MAP);
- qb.setTables(TABLE_FAVICONS);
-
- break;
- }
-
- case THUMBNAIL_ID:
- selection = DBUtils.concatenateWhere(selection, Thumbnails._ID + " = ?");
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- // fall through
- case THUMBNAILS: {
- debug("Query is on thumbnails: " + uri);
-
- qb.setProjectionMap(THUMBNAILS_PROJECTION_MAP);
- qb.setTables(TABLE_THUMBNAILS);
-
- break;
- }
-
- case URL_ANNOTATIONS:
- debug("Query is on url annotations: " + uri);
-
- qb.setProjectionMap(URL_ANNOTATIONS_PROJECTION_MAP);
- qb.setTables(TABLE_URL_ANNOTATIONS);
- break;
-
- case SCHEMA: {
- debug("Query is on schema.");
- MatrixCursor schemaCursor = new MatrixCursor(new String[] { Schema.VERSION });
- schemaCursor.newRow().add(BrowserDatabaseHelper.DATABASE_VERSION);
-
- return schemaCursor;
- }
-
- case COMBINED: {
- debug("Query is on combined: " + uri);
-
- if (TextUtils.isEmpty(sortOrder))
- sortOrder = DEFAULT_HISTORY_SORT_ORDER;
-
- // This will avoid duplicate entries in the awesomebar
- // results when a history entry has multiple bookmarks.
- groupBy = Combined.URL;
-
- qb.setProjectionMap(COMBINED_PROJECTION_MAP);
-
- if (hasFaviconsInProjection(projection))
- qb.setTables(VIEW_COMBINED_WITH_FAVICONS);
- else
- qb.setTables(Combined.VIEW_NAME);
-
- break;
- }
-
- case HIGHLIGHTS: {
- debug("Highlights query: " + uri);
-
- return getHighlights(db, limit);
- }
-
- case PAGE_METADATA: {
- debug("PageMetadata query: " + uri);
-
- qb.setProjectionMap(PAGE_METADATA_PROJECTION_MAP);
- qb.setTables(TABLE_PAGE_METADATA);
- break;
- }
-
- default: {
- Table table = findTableFor(match);
- if (table == null) {
- throw new UnsupportedOperationException("Unknown query URI " + uri);
- }
- trace("Update TABLE: " + uri);
- return table.query(db, uri, match, projection, selection, selectionArgs, sortOrder, groupBy, limit);
- }
- }
-
- trace("Running built query.");
- Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy,
- null, sortOrder, limit);
- cursor.setNotificationUri(getContext().getContentResolver(),
- BrowserContract.AUTHORITY_URI);
-
- return cursor;
- }
-
- /**
- * Update the positions of bookmarks in batches.
- *
- * Begins and ends its own transactions.
- *
- * @see #updateBookmarkPositionsInTransaction(SQLiteDatabase, String[], int, int)
- */
- private int updateBookmarkPositions(Uri uri, String[] guids) {
- if (guids == null) {
- return 0;
- }
-
- int guidsCount = guids.length;
- if (guidsCount == 0) {
- return 0;
- }
-
- int offset = 0;
- int updated = 0;
-
- final SQLiteDatabase db = getWritableDatabase(uri);
- db.beginTransaction();
-
- while (offset < guidsCount) {
- try {
- updated += updateBookmarkPositionsInTransaction(db, guids, offset,
- MAX_POSITION_UPDATES_PER_QUERY);
- } catch (SQLException e) {
- Log.e(LOGTAG, "Got SQLite exception updating bookmark positions at offset " + offset, e);
-
- // Need to restart the transaction.
- // The only way a caller knows that anything failed is that the
- // returned update count will be smaller than the requested
- // number of records.
- db.setTransactionSuccessful();
- db.endTransaction();
-
- db.beginTransaction();
- }
-
- offset += MAX_POSITION_UPDATES_PER_QUERY;
- }
-
- db.setTransactionSuccessful();
- db.endTransaction();
-
- return updated;
- }
-
- /**
- * Construct and execute an update expression that will modify the positions
- * of records in-place.
- */
- private static int updateBookmarkPositionsInTransaction(final SQLiteDatabase db, final String[] guids,
- final int offset, final int max) {
- int guidsCount = guids.length;
- int processCount = Math.min(max, guidsCount - offset);
-
- // Each must appear twice: once in a CASE, and once in the IN clause.
- String[] args = new String[processCount * 2];
- System.arraycopy(guids, offset, args, 0, processCount);
- System.arraycopy(guids, offset, args, processCount, processCount);
-
- StringBuilder b = new StringBuilder("UPDATE " + TABLE_BOOKMARKS +
- " SET " + Bookmarks.POSITION +
- " = CASE guid");
-
- // Build the CASE statement body for GUID/index pairs from offset up to
- // the computed limit.
- final int end = offset + processCount;
- int i = offset;
- for (; i < end; ++i) {
- if (guids[i] == null) {
- // We don't want to issue the query if not every GUID is specified.
- debug("updateBookmarkPositions called with null GUID at index " + i);
- return 0;
- }
- b.append(" WHEN ? THEN " + i);
- }
-
- b.append(" END WHERE " + DBUtils.computeSQLInClause(processCount, Bookmarks.GUID));
- db.execSQL(b.toString(), args);
-
- // We can't easily get a modified count without calling something like changes().
- return processCount;
- }
-
- /**
- * Construct an update expression that will modify the parents of any records
- * that match.
- */
- private int updateBookmarkParents(SQLiteDatabase db, ContentValues values, String selection, String[] selectionArgs) {
- trace("Updating bookmark parents of " + selection + " (" + selectionArgs[0] + ")");
- String where = Bookmarks._ID + " IN (" +
- " SELECT DISTINCT " + Bookmarks.PARENT +
- " FROM " + TABLE_BOOKMARKS +
- " WHERE " + selection + " )";
- return db.update(TABLE_BOOKMARKS, values, where, selectionArgs);
- }
-
- private long insertBookmark(Uri uri, ContentValues values) {
- // Generate values if not specified. Don't overwrite
- // if specified by caller.
- long now = System.currentTimeMillis();
- if (!values.containsKey(Bookmarks.DATE_CREATED)) {
- values.put(Bookmarks.DATE_CREATED, now);
- }
-
- if (!values.containsKey(Bookmarks.DATE_MODIFIED)) {
- values.put(Bookmarks.DATE_MODIFIED, now);
- }
-
- if (!values.containsKey(Bookmarks.GUID)) {
- values.put(Bookmarks.GUID, Utils.generateGuid());
- }
-
- if (!values.containsKey(Bookmarks.POSITION)) {
- debug("Inserting bookmark with no position for URI");
- values.put(Bookmarks.POSITION,
- Long.toString(BrowserContract.Bookmarks.DEFAULT_POSITION));
- }
-
- if (!values.containsKey(Bookmarks.TITLE)) {
- // Desktop Places barfs on insertion of a bookmark with no title,
- // so we don't store them that way.
- values.put(Bookmarks.TITLE, "");
- }
-
- String url = values.getAsString(Bookmarks.URL);
-
- debug("Inserting bookmark in database with URL: " + url);
- final SQLiteDatabase db = getWritableDatabase(uri);
- beginWrite(db);
- return db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.TITLE, values);
- }
-
-
- private int updateOrInsertBookmark(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
- int updated = updateBookmarks(uri, values, selection, selectionArgs);
- if (updated > 0) {
- return updated;
- }
-
- // Transaction already begun by updateBookmarks.
- if (0 <= insertBookmark(uri, values)) {
- // We 'updated' one row.
- return 1;
- }
-
- // If something went wrong, then we updated zero rows.
- return 0;
- }
-
- private int updateBookmarks(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
- trace("Updating bookmarks on URI: " + uri);
-
- final String[] bookmarksProjection = new String[] {
- Bookmarks._ID, // 0
- };
-
- if (!values.containsKey(Bookmarks.DATE_MODIFIED)) {
- values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
- }
-
- trace("Querying bookmarks to update on URI: " + uri);
- final SQLiteDatabase db = getWritableDatabase(uri);
-
- // Compute matching IDs.
- final Cursor cursor = db.query(TABLE_BOOKMARKS, bookmarksProjection,
- selection, selectionArgs, null, null, null);
-
- // Now that we're done reading, open a transaction.
- final String inClause;
- try {
- inClause = DBUtils.computeSQLInClauseFromLongs(cursor, Bookmarks._ID);
- } finally {
- cursor.close();
- }
-
- beginWrite(db);
- return db.update(TABLE_BOOKMARKS, values, inClause, null);
- }
-
- private long insertHistory(Uri uri, ContentValues values) {
- final long now = System.currentTimeMillis();
- values.put(History.DATE_CREATED, now);
- values.put(History.DATE_MODIFIED, now);
-
- // Generate GUID for new history entry. Don't override specified GUIDs.
- if (!values.containsKey(History.GUID)) {
- values.put(History.GUID, Utils.generateGuid());
- }
-
- String url = values.getAsString(History.URL);
-
- debug("Inserting history in database with URL: " + url);
- final SQLiteDatabase db = getWritableDatabase(uri);
- beginWrite(db);
- return db.insertOrThrow(TABLE_HISTORY, History.VISITS, values);
- }
-
- private int updateOrInsertHistory(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
- final int updated = updateHistory(uri, values, selection, selectionArgs);
- if (updated > 0) {
- return updated;
- }
-
- // Insert a new entry if necessary, setting visit and date aggregate values.
- if (!values.containsKey(History.VISITS)) {
- values.put(History.VISITS, 1);
- values.put(History.LOCAL_VISITS, 1);
- } else {
- values.put(History.LOCAL_VISITS, values.getAsInteger(History.VISITS));
- }
- if (values.containsKey(History.DATE_LAST_VISITED)) {
- values.put(History.LOCAL_DATE_LAST_VISITED, values.getAsLong(History.DATE_LAST_VISITED));
- }
- if (!values.containsKey(History.TITLE)) {
- values.put(History.TITLE, values.getAsString(History.URL));
- }
-
- if (0 <= insertHistory(uri, values)) {
- return 1;
- }
-
- return 0;
- }
-
- private int updateHistory(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
- trace("Updating history on URI: " + uri);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
-
- if (!values.containsKey(History.DATE_MODIFIED)) {
- values.put(History.DATE_MODIFIED, System.currentTimeMillis());
- }
-
- // Use the simple code path for easy updates.
- if (!shouldIncrementVisits(uri) && !shouldIncrementRemoteAggregates(uri)) {
- trace("Updating history meta data only");
- return db.update(TABLE_HISTORY, values, selection, selectionArgs);
- }
-
- trace("Updating history meta data and incrementing visits");
-
- if (values.containsKey(History.DATE_LAST_VISITED)) {
- values.put(History.LOCAL_DATE_LAST_VISITED, values.getAsLong(History.DATE_LAST_VISITED));
- }
-
- // Create a separate set of values that will be updated as an expression.
- final ContentValues visits = new ContentValues();
- if (shouldIncrementVisits(uri)) {
- // Update data and increment visits by 1.
- final long incVisits = 1;
-
- visits.put(History.VISITS, History.VISITS + " + " + incVisits);
- visits.put(History.LOCAL_VISITS, History.LOCAL_VISITS + " + " + incVisits);
- }
-
- if (shouldIncrementRemoteAggregates(uri)) {
- // Let's fail loudly instead of trying to assume what users of this API meant to do.
- if (!values.containsKey(History.REMOTE_VISITS)) {
- throw new IllegalArgumentException(
- "Tried incrementing History.REMOTE_VISITS by unknown value");
- }
- visits.put(
- History.REMOTE_VISITS,
- History.REMOTE_VISITS + " + " + values.getAsInteger(History.REMOTE_VISITS)
- );
- // Need to remove passed in value, so that we increment REMOTE_VISITS, and not just set it.
- values.remove(History.REMOTE_VISITS);
- }
-
- final ContentValues[] valuesAndVisits = { values, visits };
- final UpdateOperation[] ops = { UpdateOperation.ASSIGN, UpdateOperation.EXPRESSION };
-
- return DBUtils.updateArrays(db, TABLE_HISTORY, valuesAndVisits, ops, selection, selectionArgs);
- }
-
- private long insertVisitForHistory(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- trace("Inserting visit for history on URI: " + uri);
-
- final SQLiteDatabase db = getReadableDatabase(uri);
-
- final Cursor cursor = db.query(
- History.TABLE_NAME, new String[] {History.GUID}, selection, selectionArgs,
- null, null, null);
- if (cursor == null) {
- Log.e(LOGTAG, "Null cursor while trying to insert visit for history URI: " + uri);
- return 0;
- }
- final ContentValues[] visitValues;
- try {
- visitValues = new ContentValues[cursor.getCount()];
-
- if (!cursor.moveToFirst()) {
- Log.e(LOGTAG, "No history records found while inserting visit(s) for history URI: " + uri);
- return 0;
- }
-
- // Sync works in microseconds, so we store visit timestamps in microseconds as well.
- // History timestamps are in milliseconds.
- // This is the conversion point for locally generated visits.
- final long visitDate;
- if (values.containsKey(History.DATE_LAST_VISITED)) {
- visitDate = values.getAsLong(History.DATE_LAST_VISITED) * 1000;
- } else {
- visitDate = System.currentTimeMillis() * 1000;
- }
-
- final int guidColumn = cursor.getColumnIndexOrThrow(History.GUID);
- while (!cursor.isAfterLast()) {
- final ContentValues visit = new ContentValues();
- visit.put(Visits.HISTORY_GUID, cursor.getString(guidColumn));
- visit.put(Visits.DATE_VISITED, visitDate);
- visitValues[cursor.getPosition()] = visit;
- cursor.moveToNext();
- }
- } finally {
- cursor.close();
- }
-
- if (visitValues.length == 1) {
- return insertVisit(Visits.CONTENT_URI, visitValues[0]);
- } else {
- return bulkInsert(Visits.CONTENT_URI, visitValues);
- }
- }
-
- private long insertVisit(Uri uri, ContentValues values) {
- final SQLiteDatabase db = getWritableDatabase(uri);
-
- debug("Inserting history in database with URL: " + uri);
- beginWrite(db);
-
- // We ignore insert conflicts here to simplify inserting visits records coming in from Sync.
- // Visits table has a unique index on (history_guid,date), so a conflict might arise when we're
- // trying to insert history record visits coming in from sync which are already present locally
- // as a result of previous sync operations.
- // An alternative to doing this is to filter out already present records when we're doing history inserts
- // from Sync, which is a costly operation to do en masse.
- return db.insertWithOnConflict(
- TABLE_VISITS, null, values, SQLiteDatabase.CONFLICT_IGNORE);
- }
-
- private void updateFaviconIdsForUrl(SQLiteDatabase db, String pageUrl, Long faviconId) {
- ContentValues updateValues = new ContentValues(1);
- updateValues.put(FaviconColumns.FAVICON_ID, faviconId);
- db.update(TABLE_HISTORY,
- updateValues,
- History.URL + " = ?",
- new String[] { pageUrl });
- db.update(TABLE_BOOKMARKS,
- updateValues,
- Bookmarks.URL + " = ?",
- new String[] { pageUrl });
- }
-
- private long insertFavicon(Uri uri, ContentValues values) {
- return insertFavicon(getWritableDatabase(uri), values);
- }
-
- private long insertFavicon(SQLiteDatabase db, ContentValues values) {
- String faviconUrl = values.getAsString(Favicons.URL);
- String pageUrl = null;
-
- trace("Inserting favicon for URL: " + faviconUrl);
-
- DBUtils.stripEmptyByteArray(values, Favicons.DATA);
-
- // Extract the page URL from the ContentValues
- if (values.containsKey(Favicons.PAGE_URL)) {
- pageUrl = values.getAsString(Favicons.PAGE_URL);
- values.remove(Favicons.PAGE_URL);
- }
-
- // If no URL is provided, insert using the default one.
- if (TextUtils.isEmpty(faviconUrl) && !TextUtils.isEmpty(pageUrl)) {
- values.put(Favicons.URL, IconsHelper.guessDefaultFaviconURL(pageUrl));
- }
-
- final long now = System.currentTimeMillis();
- values.put(Favicons.DATE_CREATED, now);
- values.put(Favicons.DATE_MODIFIED, now);
-
- beginWrite(db);
- final long faviconId = db.insertOrThrow(TABLE_FAVICONS, null, values);
-
- if (pageUrl != null) {
- updateFaviconIdsForUrl(db, pageUrl, faviconId);
- }
- return faviconId;
- }
-
- private int updateOrInsertFavicon(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
- return updateFavicon(uri, values, selection, selectionArgs,
- true /* insert if needed */);
- }
-
- private int updateExistingFavicon(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
- return updateFavicon(uri, values, selection, selectionArgs,
- false /* only update, no insert */);
- }
-
- private int updateFavicon(Uri uri, ContentValues values, String selection,
- String[] selectionArgs, boolean insertIfNeeded) {
- String faviconUrl = values.getAsString(Favicons.URL);
- String pageUrl = null;
- int updated = 0;
- Long faviconId = null;
- long now = System.currentTimeMillis();
-
- trace("Updating favicon for URL: " + faviconUrl);
-
- DBUtils.stripEmptyByteArray(values, Favicons.DATA);
-
- // Extract the page URL from the ContentValues
- if (values.containsKey(Favicons.PAGE_URL)) {
- pageUrl = values.getAsString(Favicons.PAGE_URL);
- values.remove(Favicons.PAGE_URL);
- }
-
- values.put(Favicons.DATE_MODIFIED, now);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
-
- // If there's no favicon URL given and we're inserting if needed, skip
- // the update and only do an insert (otherwise all rows would be
- // updated).
- if (!(insertIfNeeded && (faviconUrl == null))) {
- updated = db.update(TABLE_FAVICONS, values, selection, selectionArgs);
- }
-
- if (updated > 0) {
- if ((faviconUrl != null) && (pageUrl != null)) {
- final Cursor cursor = db.query(TABLE_FAVICONS,
- new String[] { Favicons._ID },
- Favicons.URL + " = ?",
- new String[] { faviconUrl },
- null, null, null);
- try {
- if (cursor.moveToFirst()) {
- faviconId = cursor.getLong(cursor.getColumnIndexOrThrow(Favicons._ID));
- }
- } finally {
- cursor.close();
- }
- }
- if (pageUrl != null) {
- beginWrite(db);
- }
- } else if (insertIfNeeded) {
- values.put(Favicons.DATE_CREATED, now);
-
- trace("No update, inserting favicon for URL: " + faviconUrl);
- beginWrite(db);
- faviconId = db.insert(TABLE_FAVICONS, null, values);
- updated = 1;
- }
-
- if (pageUrl != null) {
- updateFaviconIdsForUrl(db, pageUrl, faviconId);
- }
-
- return updated;
- }
-
- private long insertThumbnail(Uri uri, ContentValues values) {
- final String url = values.getAsString(Thumbnails.URL);
-
- trace("Inserting thumbnail for URL: " + url);
-
- DBUtils.stripEmptyByteArray(values, Thumbnails.DATA);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
- beginWrite(db);
- return db.insertOrThrow(TABLE_THUMBNAILS, null, values);
- }
-
- private long insertActivityStreamBlocklistSite(final Uri uri, final ContentValues values) {
- final String url = values.getAsString(ActivityStreamBlocklist.URL);
- trace("Inserting url into highlights blocklist, URL: " + url);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
- values.put(ActivityStreamBlocklist.CREATED, System.currentTimeMillis());
-
- beginWrite(db);
- return db.insertOrThrow(TABLE_ACTIVITY_STREAM_BLOCKLIST, null, values);
- }
-
- private long insertPageMetadata(final Uri uri, final ContentValues values) {
- final SQLiteDatabase db = getWritableDatabase(uri);
-
- if (!values.containsKey(PageMetadata.DATE_CREATED)) {
- values.put(PageMetadata.DATE_CREATED, System.currentTimeMillis());
- }
-
- beginWrite(db);
-
- // Perform INSERT OR REPLACE, there might be page metadata present and we want to replace it.
- // Depends on a conflict arising from unique foreign key (history_guid) constraint violation.
- return db.insertWithOnConflict(
- TABLE_PAGE_METADATA, null, values, SQLiteDatabase.CONFLICT_REPLACE);
- }
-
- private long insertUrlAnnotation(final Uri uri, final ContentValues values) {
- final String url = values.getAsString(UrlAnnotations.URL);
- trace("Inserting url annotations for URL: " + url);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
- beginWrite(db);
- return db.insertOrThrow(TABLE_URL_ANNOTATIONS, null, values);
- }
-
- private void deleteUrlAnnotation(final Uri uri, final String selection, final String[] selectionArgs) {
- trace("Deleting url annotation for URI: " + uri);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
- db.delete(TABLE_URL_ANNOTATIONS, selection, selectionArgs);
- }
-
- private int deletePageMetadata(final Uri uri, final String selection, final String[] selectionArgs) {
- trace("Deleting page metadata for URI: " + uri);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
- return db.delete(TABLE_PAGE_METADATA, selection, selectionArgs);
- }
-
- private void updateUrlAnnotation(final Uri uri, final ContentValues values, final String selection, final String[] selectionArgs) {
- trace("Updating url annotation for URI: " + uri);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
- db.update(TABLE_URL_ANNOTATIONS, values, selection, selectionArgs);
- }
-
- private int updateOrInsertThumbnail(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
- return updateThumbnail(uri, values, selection, selectionArgs,
- true /* insert if needed */);
- }
-
- private int updateExistingThumbnail(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
- return updateThumbnail(uri, values, selection, selectionArgs,
- false /* only update, no insert */);
- }
-
- private int updateThumbnail(Uri uri, ContentValues values, String selection,
- String[] selectionArgs, boolean insertIfNeeded) {
- final String url = values.getAsString(Thumbnails.URL);
- DBUtils.stripEmptyByteArray(values, Thumbnails.DATA);
-
- trace("Updating thumbnail for URL: " + url);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
- beginWrite(db);
- int updated = db.update(TABLE_THUMBNAILS, values, selection, selectionArgs);
-
- if (updated == 0 && insertIfNeeded) {
- trace("No update, inserting thumbnail for URL: " + url);
- db.insert(TABLE_THUMBNAILS, null, values);
- updated = 1;
- }
-
- return updated;
- }
-
- /**
- * This method does not create a new transaction. Its first operation is
- * guaranteed to be a write, which in the case of a new enclosing
- * transaction will guarantee that a read does not need to be upgraded to
- * a write.
- */
- private int deleteHistory(SQLiteDatabase db, Uri uri, String selection, String[] selectionArgs) {
- debug("Deleting history entry for URI: " + uri);
-
- if (isCallerSync(uri)) {
- return db.delete(TABLE_HISTORY, selection, selectionArgs);
- }
-
- debug("Marking history entry as deleted for URI: " + uri);
-
- ContentValues values = new ContentValues();
- values.put(History.IS_DELETED, 1);
-
- // Wipe sensitive data.
- values.putNull(History.TITLE);
- values.put(History.URL, ""); // Column is NOT NULL.
- values.put(History.DATE_CREATED, 0);
- values.put(History.DATE_LAST_VISITED, 0);
- values.put(History.VISITS, 0);
- values.put(History.DATE_MODIFIED, System.currentTimeMillis());
-
- // Doing this UPDATE (or the DELETE above) first ensures that the
- // first operation within a new enclosing transaction is a write.
- // The cleanup call below will do a SELECT first, and thus would
- // require the transaction to be upgraded from a reader to a writer.
- // In some cases that upgrade can fail (SQLITE_BUSY), so we avoid
- // it if we can.
- final int updated = db.update(TABLE_HISTORY, values, selection, selectionArgs);
- try {
- cleanUpSomeDeletedRecords(uri, TABLE_HISTORY);
- } catch (Exception e) {
- // We don't care.
- Log.e(LOGTAG, "Unable to clean up deleted history records: ", e);
- }
- return updated;
- }
-
- private ArrayList<String> getHistoryGUIDsFromSelection(SQLiteDatabase db, Uri uri, String selection, String[] selectionArgs) {
- final ArrayList<String> historyGUIDs = new ArrayList<>();
-
- final Cursor cursor = db.query(
- History.TABLE_NAME, new String[] {History.GUID}, selection, selectionArgs,
- null, null, null);
- if (cursor == null) {
- Log.e(LOGTAG, "Null cursor while trying to delete visits for history URI: " + uri);
- return historyGUIDs;
- }
-
- try {
- if (!cursor.moveToFirst()) {
- trace("No history items for which to remove visits matched for URI: " + uri);
- return historyGUIDs;
- }
- final int historyColumn = cursor.getColumnIndexOrThrow(History.GUID);
- while (!cursor.isAfterLast()) {
- historyGUIDs.add(cursor.getString(historyColumn));
- cursor.moveToNext();
- }
- } finally {
- cursor.close();
- }
-
- return historyGUIDs;
- }
-
- private int deletePageMetadataForHistory(SQLiteDatabase db, ArrayList<String> historyGUIDs) {
- return bulkDeleteByHistoryGUID(db, historyGUIDs, PageMetadata.TABLE_NAME, PageMetadata.HISTORY_GUID);
- }
-
- private int deleteVisitsForHistory(SQLiteDatabase db, ArrayList<String> historyGUIDs) {
- return bulkDeleteByHistoryGUID(db, historyGUIDs, Visits.TABLE_NAME, Visits.HISTORY_GUID);
- }
-
- private int bulkDeleteByHistoryGUID(SQLiteDatabase db, ArrayList<String> historyGUIDs, String table, String historyGUIDColumn) {
- // Due to SQLite's maximum variable limitation, we need to chunk our delete statements.
- // For example, if there were 1200 GUIDs, this will perform 2 delete statements.
- int deleted = 0;
- for (int chunk = 0; chunk <= historyGUIDs.size() / DBUtils.SQLITE_MAX_VARIABLE_NUMBER; chunk++) {
- final int chunkStart = chunk * DBUtils.SQLITE_MAX_VARIABLE_NUMBER;
- int chunkEnd = (chunk + 1) * DBUtils.SQLITE_MAX_VARIABLE_NUMBER;
- if (chunkEnd > historyGUIDs.size()) {
- chunkEnd = historyGUIDs.size();
- }
- final List<String> chunkGUIDs = historyGUIDs.subList(chunkStart, chunkEnd);
- deleted += db.delete(
- table,
- DBUtils.computeSQLInClause(chunkGUIDs.size(), historyGUIDColumn),
- chunkGUIDs.toArray(new String[chunkGUIDs.size()])
- );
- }
-
- return deleted;
- }
-
- private int deleteVisits(Uri uri, String selection, String[] selectionArgs) {
- debug("Deleting visits for URI: " + uri);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
-
- beginWrite(db);
- return db.delete(TABLE_VISITS, selection, selectionArgs);
- }
-
- private int deleteBookmarks(Uri uri, String selection, String[] selectionArgs) {
- debug("Deleting bookmarks for URI: " + uri);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
-
- if (isCallerSync(uri)) {
- beginWrite(db);
- return db.delete(TABLE_BOOKMARKS, selection, selectionArgs);
- }
-
- debug("Marking bookmarks as deleted for URI: " + uri);
-
- ContentValues values = new ContentValues();
- values.put(Bookmarks.IS_DELETED, 1);
- values.put(Bookmarks.POSITION, 0);
- values.putNull(Bookmarks.PARENT);
- values.putNull(Bookmarks.URL);
- values.putNull(Bookmarks.TITLE);
- values.putNull(Bookmarks.DESCRIPTION);
- values.putNull(Bookmarks.KEYWORD);
- values.putNull(Bookmarks.TAGS);
- values.putNull(Bookmarks.FAVICON_ID);
-
- // Doing this UPDATE (or the DELETE above) first ensures that the
- // first operation within this transaction is a write.
- // The cleanup call below will do a SELECT first, and thus would
- // require the transaction to be upgraded from a reader to a writer.
- final int updated = updateBookmarks(uri, values, selection, selectionArgs);
- try {
- cleanUpSomeDeletedRecords(uri, TABLE_BOOKMARKS);
- } catch (Exception e) {
- // We don't care.
- Log.e(LOGTAG, "Unable to clean up deleted bookmark records: ", e);
- }
- return updated;
- }
-
- private int deleteFavicons(Uri uri, String selection, String[] selectionArgs) {
- debug("Deleting favicons for URI: " + uri);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
-
- return db.delete(TABLE_FAVICONS, selection, selectionArgs);
- }
-
- private int deleteThumbnails(Uri uri, String selection, String[] selectionArgs) {
- debug("Deleting thumbnails for URI: " + uri);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
-
- return db.delete(TABLE_THUMBNAILS, selection, selectionArgs);
- }
-
- private int deleteUnusedImages(Uri uri) {
- debug("Deleting all unused favicons and thumbnails for URI: " + uri);
-
- String faviconSelection = Favicons._ID + " NOT IN "
- + "(SELECT " + History.FAVICON_ID
- + " FROM " + TABLE_HISTORY
- + " WHERE " + History.IS_DELETED + " = 0"
- + " AND " + History.FAVICON_ID + " IS NOT NULL"
- + " UNION ALL SELECT " + Bookmarks.FAVICON_ID
- + " FROM " + TABLE_BOOKMARKS
- + " WHERE " + Bookmarks.IS_DELETED + " = 0"
- + " AND " + Bookmarks.FAVICON_ID + " IS NOT NULL)";
-
- String thumbnailSelection = Thumbnails.URL + " NOT IN "
- + "(SELECT " + History.URL
- + " FROM " + TABLE_HISTORY
- + " WHERE " + History.IS_DELETED + " = 0"
- + " AND " + History.URL + " IS NOT NULL"
- + " UNION ALL SELECT " + Bookmarks.URL
- + " FROM " + TABLE_BOOKMARKS
- + " WHERE " + Bookmarks.IS_DELETED + " = 0"
- + " AND " + Bookmarks.URL + " IS NOT NULL)";
-
- return deleteFavicons(uri, faviconSelection, null) +
- deleteThumbnails(uri, thumbnailSelection, null) +
- getURLMetadataTable().deleteUnused(getWritableDatabase(uri));
- }
-
- @Override
- public ContentProviderResult[] applyBatch (ArrayList<ContentProviderOperation> operations)
- throws OperationApplicationException {
- final int numOperations = operations.size();
- final ContentProviderResult[] results = new ContentProviderResult[numOperations];
-
- if (numOperations < 1) {
- debug("applyBatch: no operations; returning immediately.");
- // The original Android implementation returns a zero-length
- // array in this case. We do the same.
- return results;
- }
-
- boolean failures = false;
-
- // We only have 1 database for all Uris that we can get.
- SQLiteDatabase db = getWritableDatabase(operations.get(0).getUri());
-
- // Note that the apply() call may cause us to generate
- // additional transactions for the individual operations.
- // But Android's wrapper for SQLite supports nested transactions,
- // so this will do the right thing.
- //
- // Note further that in some circumstances this can result in
- // exceptions: if this transaction is first involved in reading,
- // and then (naturally) tries to perform writes, SQLITE_BUSY can
- // be raised. See Bug 947939 and friends.
- beginBatch(db);
-
- for (int i = 0; i < numOperations; i++) {
- try {
- final ContentProviderOperation operation = operations.get(i);
- results[i] = operation.apply(this, results, i);
- } catch (SQLException e) {
- Log.w(LOGTAG, "SQLite Exception during applyBatch.", e);
- // The Android API makes it implementation-defined whether
- // the failure of a single operation makes all others abort
- // or not. For our use cases, best-effort operation makes
- // more sense. Rolling back and forcing the caller to retry
- // after it figures out what went wrong isn't very convenient
- // anyway.
- // Signal failed operation back, so the caller knows what
- // went through and what didn't.
- results[i] = new ContentProviderResult(0);
- failures = true;
- // http://www.sqlite.org/lang_conflict.html
- // Note that we need a new transaction, subsequent operations
- // on this one will fail (we're in ABORT by default, which
- // isn't IGNORE). We still need to set it as successful to let
- // everything before the failed op go through.
- // We can't set conflict resolution on API level < 8, and even
- // above 8 it requires splitting the call per operation
- // (insert/update/delete).
- db.setTransactionSuccessful();
- db.endTransaction();
- db.beginTransaction();
- } catch (OperationApplicationException e) {
- // Repeat of above.
- results[i] = new ContentProviderResult(0);
- failures = true;
- db.setTransactionSuccessful();
- db.endTransaction();
- db.beginTransaction();
- }
- }
-
- trace("Flushing DB applyBatch...");
- markBatchSuccessful(db);
- endBatch(db);
-
- if (failures) {
- throw new OperationApplicationException();
- }
-
- return results;
- }
-
- private static Table findTableFor(int id) {
- for (Table table : sTables) {
- for (Table.ContentProviderInfo type : table.getContentProviderInfo()) {
- if (type.id == id) {
- return table;
- }
- }
- }
- return null;
- }
-
- private static void addTablesToMatcher(Table[] tables, final UriMatcher matcher) {
- }
-
- private static String getContentItemType(final int match) {
- for (Table table : sTables) {
- for (Table.ContentProviderInfo type : table.getContentProviderInfo()) {
- if (type.id == match) {
- return "vnd.android.cursor.item/" + type.name;
- }
- }
- }
-
- return null;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/DBUtils.java b/mobile/android/base/java/org/mozilla/gecko/db/DBUtils.java
deleted file mode 100644
index cfa2f870f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/DBUtils.java
+++ /dev/null
@@ -1,450 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import android.annotation.TargetApi;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteStatement;
-import android.os.Build;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoProfile;
-
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.util.Log;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.Telemetry;
-
-import java.util.Map;
-
-public class DBUtils {
- private static final String LOGTAG = "GeckoDBUtils";
-
- public static final int SQLITE_MAX_VARIABLE_NUMBER = 999;
-
- public static final String qualifyColumn(String table, String column) {
- return table + "." + column;
- }
-
- // This is available in Android >= 11. Implemented locally to be
- // compatible with older versions.
- public static String concatenateWhere(String a, String b) {
- if (TextUtils.isEmpty(a)) {
- return b;
- }
-
- if (TextUtils.isEmpty(b)) {
- return a;
- }
-
- return "(" + a + ") AND (" + b + ")";
- }
-
- // This is available in Android >= 11. Implemented locally to be
- // compatible with older versions.
- public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) {
- if (originalValues == null || originalValues.length == 0) {
- return newValues;
- }
-
- if (newValues == null || newValues.length == 0) {
- return originalValues;
- }
-
- String[] result = new String[originalValues.length + newValues.length];
- System.arraycopy(originalValues, 0, result, 0, originalValues.length);
- System.arraycopy(newValues, 0, result, originalValues.length, newValues.length);
-
- return result;
- }
-
- /**
- * Concatenate multiple lists of selection arguments. <code>values</code> may be <code>null</code>.
- */
- public static String[] concatenateSelectionArgs(String[]... values) {
- // Since we're most likely to be concatenating a few arrays of many values, it is most
- // efficient to iterate over the arrays once to obtain their lengths, allowing us to create one target array
- // (as opposed to copying arrays on every iteration, which would result in many more copies).
- int totalLength = 0;
- for (String[] v : values) {
- if (v != null) {
- totalLength += v.length;
- }
- }
-
- String[] result = new String[totalLength];
-
- int position = 0;
- for (String[] v: values) {
- if (v != null) {
- int currentLength = v.length;
- System.arraycopy(v, 0, result, position, currentLength);
- position += currentLength;
- }
- }
-
- return result;
- }
-
- public static void replaceKey(ContentValues aValues, String aOriginalKey,
- String aNewKey, String aDefault) {
- String value = aDefault;
- if (aOriginalKey != null && aValues.containsKey(aOriginalKey)) {
- value = aValues.get(aOriginalKey).toString();
- aValues.remove(aOriginalKey);
- }
-
- if (!aValues.containsKey(aNewKey)) {
- aValues.put(aNewKey, value);
- }
- }
-
- private static String HISTOGRAM_DATABASE_LOCKED = "DATABASE_LOCKED_EXCEPTION";
- private static String HISTOGRAM_DATABASE_UNLOCKED = "DATABASE_SUCCESSFUL_UNLOCK";
- public static void ensureDatabaseIsNotLocked(SQLiteOpenHelper dbHelper, String databasePath) {
- final int maxAttempts = 5;
- int attempt = 0;
- SQLiteDatabase db = null;
- for (; attempt < maxAttempts; attempt++) {
- try {
- // Try a simple test and exit the loop.
- db = dbHelper.getWritableDatabase();
- break;
- } catch (Exception e) {
- // We assume that this is a android.database.sqlite.SQLiteDatabaseLockedException.
- // That class is only available on API 11+.
- Telemetry.addToHistogram(HISTOGRAM_DATABASE_LOCKED, attempt);
-
- // Things could get very bad if we don't find a way to unlock the DB.
- Log.d(LOGTAG, "Database is locked, trying to kill any zombie processes: " + databasePath);
- GeckoAppShell.killAnyZombies();
- try {
- Thread.sleep(attempt * 100);
- } catch (InterruptedException ie) {
- }
- }
- }
-
- if (db == null) {
- Log.w(LOGTAG, "Failed to unlock database.");
- GeckoAppShell.listOfOpenFiles();
- return;
- }
-
- // If we needed to retry, but we succeeded, report that in telemetry.
- // Failures are indicated by a lower frequency of UNLOCKED than LOCKED.
- if (attempt > 1) {
- Telemetry.addToHistogram(HISTOGRAM_DATABASE_UNLOCKED, attempt - 1);
- }
- }
-
- /**
- * Copies a table <b>between</b> database files.
- *
- * This method assumes that the source table and destination table already exist in the
- * source and destination databases, respectively.
- *
- * The table is copied row-by-row in a single transaction.
- *
- * @param source The source database that the table will be copied from.
- * @param sourceTableName The name of the source table.
- * @param destination The destination database that the table will be copied to.
- * @param destinationTableName The name of the destination table.
- * @return true if all rows were copied; false otherwise.
- */
- public static boolean copyTable(SQLiteDatabase source, String sourceTableName,
- SQLiteDatabase destination, String destinationTableName) {
- Cursor cursor = null;
- try {
- destination.beginTransaction();
-
- cursor = source.query(sourceTableName, null, null, null, null, null, null);
- Log.d(LOGTAG, "Trying to copy " + cursor.getCount() + " rows from " + sourceTableName + " to " + destinationTableName);
-
- final ContentValues contentValues = new ContentValues();
- while (cursor.moveToNext()) {
- contentValues.clear();
- DatabaseUtils.cursorRowToContentValues(cursor, contentValues);
- destination.insert(destinationTableName, null, contentValues);
- }
-
- destination.setTransactionSuccessful();
- Log.d(LOGTAG, "Successfully copied " + cursor.getCount() + " rows from " + sourceTableName + " to " + destinationTableName);
- return true;
- } catch (Exception e) {
- Log.w(LOGTAG, "Got exception copying rows from " + sourceTableName + " to " + destinationTableName + "; ignoring.", e);
- return false;
- } finally {
- destination.endTransaction();
- if (cursor != null) {
- cursor.close();
- }
- }
- }
-
- /**
- * Verifies that 0-byte arrays aren't added as favicon or thumbnail data.
- * @param values ContentValues of query
- * @param columnName Name of data column to verify
- */
- public static void stripEmptyByteArray(ContentValues values, String columnName) {
- if (values.containsKey(columnName)) {
- byte[] data = values.getAsByteArray(columnName);
- if (data == null || data.length == 0) {
- Log.w(LOGTAG, "Tried to insert an empty or non-byte-array image. Ignoring.");
- values.putNull(columnName);
- }
- }
- }
-
- /**
- * Builds a selection string that searches for a list of arguments in a particular column.
- * For example URL in (?,?,?). Callers should pass the actual arguments into their query
- * as selection args.
- * @para columnName The column to search in
- * @para size The number of arguments to search for
- */
- public static String computeSQLInClause(int items, String field) {
- final StringBuilder builder = new StringBuilder(field);
- builder.append(" IN (");
- int i = 0;
- for (; i < items - 1; ++i) {
- builder.append("?, ");
- }
- if (i < items) {
- builder.append("?");
- }
- builder.append(")");
- return builder.toString();
- }
-
- /**
- * Turn a single-column cursor of longs into a single SQL "IN" clause.
- * We can do this without using selection arguments because Long isn't
- * vulnerable to injection.
- */
- public static String computeSQLInClauseFromLongs(final Cursor cursor, String field) {
- final StringBuilder builder = new StringBuilder(field);
- builder.append(" IN (");
- final int commaLimit = cursor.getCount() - 1;
- int i = 0;
- while (cursor.moveToNext()) {
- builder.append(cursor.getLong(0));
- if (i++ < commaLimit) {
- builder.append(", ");
- }
- }
- builder.append(")");
- return builder.toString();
- }
-
- public static Uri appendProfile(final String profile, final Uri uri) {
- return uri.buildUpon().appendQueryParameter(BrowserContract.PARAM_PROFILE, profile).build();
- }
-
- public static Uri appendProfileWithDefault(final String profile, final Uri uri) {
- if (profile == null) {
- return appendProfile(GeckoProfile.DEFAULT_PROFILE, uri);
- }
- return appendProfile(profile, uri);
- }
-
- /**
- * Use the following when no conflict action is specified.
- */
- private static final int CONFLICT_NONE = 0;
- private static final String[] CONFLICT_VALUES = new String[] {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "};
-
- /**
- * Convenience method for updating rows in the database.
- *
- * @param table the table to update in
- * @param values a map from column names to new column values. null is a
- * valid value that will be translated to NULL.
- * @param whereClause the optional WHERE clause to apply when updating.
- * Passing null will update all rows.
- * @param whereArgs You may include ?s in the where clause, which
- * will be replaced by the values from whereArgs. The values
- * will be bound as Strings.
- * @return the number of rows affected
- */
- @RobocopTarget
- public static int updateArrays(SQLiteDatabase db, String table, ContentValues[] values, UpdateOperation[] ops, String whereClause, String[] whereArgs) {
- return updateArraysWithOnConflict(db, table, values, ops, whereClause, whereArgs, CONFLICT_NONE, true);
- }
-
- public static void updateArraysBlindly(SQLiteDatabase db, String table, ContentValues[] values, UpdateOperation[] ops, String whereClause, String[] whereArgs) {
- updateArraysWithOnConflict(db, table, values, ops, whereClause, whereArgs, CONFLICT_NONE, false);
- }
-
- @RobocopTarget
- public enum UpdateOperation {
- /**
- * ASSIGN is the usual update: replaces the value in the named column with the provided value.
- *
- * foo = ?
- */
- ASSIGN,
-
- /**
- * BITWISE_OR applies the provided value to the existing value with a bitwise OR. This is useful for adding to flags.
- *
- * foo |= ?
- */
- BITWISE_OR,
-
- /**
- * EXPRESSION is an end-run around the API: it allows callers to specify a fragment of SQL to splice into the
- * SET part of the query.
- *
- * foo = $value
- *
- * Be very careful not to use user input in this.
- */
- EXPRESSION,
- }
-
- /**
- * This is an evil reimplementation of SQLiteDatabase's methods to allow for
- * smarter updating.
- *
- * Each ContentValues has an associated enum that describes how to unify input values with the existing column values.
- */
- private static int updateArraysWithOnConflict(SQLiteDatabase db, String table,
- ContentValues[] values,
- UpdateOperation[] ops,
- String whereClause,
- String[] whereArgs,
- int conflictAlgorithm,
- boolean returnChangedRows) {
- if (values == null || values.length == 0) {
- throw new IllegalArgumentException("Empty values");
- }
-
- if (ops == null || ops.length != values.length) {
- throw new IllegalArgumentException("ops and values don't match");
- }
-
- StringBuilder sql = new StringBuilder(120);
- sql.append("UPDATE ");
- sql.append(CONFLICT_VALUES[conflictAlgorithm]);
- sql.append(table);
- sql.append(" SET ");
-
- // move all bind args to one array
- int setValuesSize = 0;
- for (int i = 0; i < values.length; i++) {
- // EXPRESSION types don't contribute any placeholders.
- if (ops[i] != UpdateOperation.EXPRESSION) {
- setValuesSize += values[i].size();
- }
- }
-
- int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length);
- Object[] bindArgs = new Object[bindArgsSize];
-
- int arg = 0;
- for (int i = 0; i < values.length; i++) {
- final ContentValues v = values[i];
- final UpdateOperation op = ops[i];
-
- // Alas, code duplication.
- switch (op) {
- case ASSIGN:
- for (Map.Entry<String, Object> entry : v.valueSet()) {
- final String colName = entry.getKey();
- sql.append((arg > 0) ? "," : "");
- sql.append(colName);
- bindArgs[arg++] = entry.getValue();
- sql.append("= ?");
- }
- break;
- case BITWISE_OR:
- for (Map.Entry<String, Object> entry : v.valueSet()) {
- final String colName = entry.getKey();
- sql.append((arg > 0) ? "," : "");
- sql.append(colName);
- bindArgs[arg++] = entry.getValue();
- sql.append("= ? | ");
- sql.append(colName);
- }
- break;
- case EXPRESSION:
- // Treat each value as a literal SQL string.
- for (Map.Entry<String, Object> entry : v.valueSet()) {
- final String colName = entry.getKey();
- sql.append((arg > 0) ? "," : "");
- sql.append(colName);
- sql.append(" = ");
- sql.append(entry.getValue());
- }
- break;
- }
- }
-
- if (whereArgs != null) {
- for (arg = setValuesSize; arg < bindArgsSize; arg++) {
- bindArgs[arg] = whereArgs[arg - setValuesSize];
- }
- }
- if (!TextUtils.isEmpty(whereClause)) {
- sql.append(" WHERE ");
- sql.append(whereClause);
- }
-
- // What a huge pain in the ass, all because SQLiteDatabase doesn't expose .executeSql,
- // and we can't get a DB handle. Nor can we easily construct a statement with arguments
- // already bound.
- final SQLiteStatement statement = db.compileStatement(sql.toString());
- try {
- bindAllArgs(statement, bindArgs);
- if (!returnChangedRows) {
- statement.execute();
- return 0;
- }
- // This is a separate method so we can annotate it with @TargetApi.
- return executeStatementReturningChangedRows(statement);
- } finally {
- statement.close();
- }
- }
-
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- private static int executeStatementReturningChangedRows(SQLiteStatement statement) {
- return statement.executeUpdateDelete();
- }
-
- // All because {@link SQLiteProgram#bind(integer, Object)} is private.
- private static void bindAllArgs(SQLiteStatement statement, Object[] bindArgs) {
- if (bindArgs == null) {
- return;
- }
- for (int i = bindArgs.length; i != 0; i--) {
- Object v = bindArgs[i - 1];
- if (v == null) {
- statement.bindNull(i);
- } else if (v instanceof String) {
- statement.bindString(i, (String) v);
- } else if (v instanceof Double) {
- statement.bindDouble(i, (Double) v);
- } else if (v instanceof Float) {
- statement.bindDouble(i, (Float) v);
- } else if (v instanceof Long) {
- statement.bindLong(i, (Long) v);
- } else if (v instanceof Integer) {
- statement.bindLong(i, (Integer) v);
- } else if (v instanceof Byte) {
- statement.bindLong(i, (Byte) v);
- } else if (v instanceof byte[]) {
- statement.bindBlob(i, (byte[]) v);
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/FormHistoryProvider.java b/mobile/android/base/java/org/mozilla/gecko/db/FormHistoryProvider.java
deleted file mode 100644
index ff2f5238e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/FormHistoryProvider.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import java.lang.IllegalArgumentException;
-import java.util.HashMap;
-
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.db.BrowserContract.FormHistory;
-import org.mozilla.gecko.db.BrowserContract.DeletedFormHistory;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.sqlite.SQLiteBridge;
-import org.mozilla.gecko.sync.Utils;
-
-import android.content.ContentValues;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.net.Uri;
-import android.text.TextUtils;
-
-public class FormHistoryProvider extends SQLiteBridgeContentProvider {
- static final String TABLE_FORM_HISTORY = "moz_formhistory";
- static final String TABLE_DELETED_FORM_HISTORY = "moz_deleted_formhistory";
-
- private static final int FORM_HISTORY = 100;
- private static final int DELETED_FORM_HISTORY = 101;
-
- private static final UriMatcher URI_MATCHER;
-
-
- // This should be kept in sync with the db version in toolkit/components/satchel/nsFormHistory.js
- private static final int DB_VERSION = 4;
- private static final String DB_FILENAME = "formhistory.sqlite";
- private static final String TELEMETRY_TAG = "SQLITEBRIDGE_PROVIDER_FORMS";
-
- private static final String WHERE_GUID_IS_NULL = BrowserContract.DeletedFormHistory.GUID + " IS NULL";
- private static final String WHERE_GUID_IS_VALUE = BrowserContract.DeletedFormHistory.GUID + " = ?";
-
- private static final String LOG_TAG = "FormHistoryProvider";
-
- static {
- URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
- URI_MATCHER.addURI(BrowserContract.FORM_HISTORY_AUTHORITY, "formhistory", FORM_HISTORY);
- URI_MATCHER.addURI(BrowserContract.FORM_HISTORY_AUTHORITY, "deleted-formhistory", DELETED_FORM_HISTORY);
- }
-
- public FormHistoryProvider() {
- super(LOG_TAG);
- }
-
-
- @Override
- public String getType(Uri uri) {
- final int match = URI_MATCHER.match(uri);
-
- switch (match) {
- case FORM_HISTORY:
- return FormHistory.CONTENT_TYPE;
-
- case DELETED_FORM_HISTORY:
- return DeletedFormHistory.CONTENT_TYPE;
-
- default:
- throw new UnsupportedOperationException("Unknown type " + uri);
- }
- }
-
- @Override
- public String getTable(Uri uri) {
- String table = null;
- final int match = URI_MATCHER.match(uri);
- switch (match) {
- case DELETED_FORM_HISTORY:
- table = TABLE_DELETED_FORM_HISTORY;
- break;
-
- case FORM_HISTORY:
- table = TABLE_FORM_HISTORY;
- break;
-
- default:
- throw new UnsupportedOperationException("Unknown table " + uri);
- }
- return table;
- }
-
- @Override
- public String getSortOrder(Uri uri, String aRequested) {
- if (!TextUtils.isEmpty(aRequested)) {
- return aRequested;
- }
-
- return null;
- }
-
- @Override
- public void setupDefaults(Uri uri, ContentValues values) {
- int match = URI_MATCHER.match(uri);
- long now = System.currentTimeMillis();
-
- switch (match) {
- case DELETED_FORM_HISTORY:
- values.put(DeletedFormHistory.TIME_DELETED, now);
-
- // Deleted entries must contain a guid
- if (!values.containsKey(FormHistory.GUID)) {
- throw new IllegalArgumentException("Must provide a GUID for a deleted form history");
- }
- break;
-
- case FORM_HISTORY:
- // Generate GUID for new entry. Don't override specified GUIDs.
- if (!values.containsKey(FormHistory.GUID)) {
- String guid = Utils.generateGuid();
- values.put(FormHistory.GUID, guid);
- }
- break;
-
- default:
- throw new UnsupportedOperationException("Unknown insert URI " + uri);
- }
- }
-
- @Override
- public void initGecko() {
- GeckoAppShell.notifyObservers("FormHistory:Init", null);
- }
-
- @Override
- public void onPreInsert(ContentValues values, Uri uri, SQLiteBridge db) {
- if (!values.containsKey(FormHistory.GUID)) {
- return;
- }
-
- String guid = values.getAsString(FormHistory.GUID);
- if (guid == null) {
- db.delete(TABLE_DELETED_FORM_HISTORY, WHERE_GUID_IS_NULL, null);
- return;
- }
- String[] args = new String[] { guid };
- db.delete(TABLE_DELETED_FORM_HISTORY, WHERE_GUID_IS_VALUE, args);
- }
-
- @Override
- public void onPreUpdate(ContentValues values, Uri uri, SQLiteBridge db) { }
-
- @Override
- public void onPostQuery(Cursor cursor, Uri uri, SQLiteBridge db) { }
-
- @Override
- protected String getDBName() {
- return DB_FILENAME;
- }
-
- @Override
- protected String getTelemetryPrefix() {
- return TELEMETRY_TAG;
- }
-
- @Override
- protected int getDBVersion() {
- return DB_VERSION;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/HomeProvider.java b/mobile/android/base/java/org/mozilla/gecko/db/HomeProvider.java
deleted file mode 100644
index 1a241f9da..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/HomeProvider.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import java.io.IOException;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract.HomeItems;
-import org.mozilla.gecko.db.DBUtils;
-import org.mozilla.gecko.sqlite.SQLiteBridge;
-import org.mozilla.gecko.util.RawResource;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.net.Uri;
-import android.util.Log;
-
-public class HomeProvider extends SQLiteBridgeContentProvider {
- private static final String LOGTAG = "GeckoHomeProvider";
-
- // This should be kept in sync with the db version in mobile/android/modules/HomeProvider.jsm
- private static final int DB_VERSION = 3;
- private static final String DB_FILENAME = "home.sqlite";
- private static final String TELEMETRY_TAG = "SQLITEBRIDGE_PROVIDER_HOME";
-
- private static final String TABLE_ITEMS = "items";
-
- // Endpoint to return static fake data.
- static final int ITEMS_FAKE = 100;
- static final int ITEMS = 101;
- static final int ITEMS_ID = 102;
-
- static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
-
- static {
- URI_MATCHER.addURI(BrowserContract.HOME_AUTHORITY, "items/fake", ITEMS_FAKE);
- URI_MATCHER.addURI(BrowserContract.HOME_AUTHORITY, "items", ITEMS);
- URI_MATCHER.addURI(BrowserContract.HOME_AUTHORITY, "items/#", ITEMS_ID);
- }
-
- public HomeProvider() {
- super(LOGTAG);
- }
-
- @Override
- public String getType(Uri uri) {
- final int match = URI_MATCHER.match(uri);
-
- switch (match) {
- case ITEMS_FAKE: {
- return HomeItems.CONTENT_TYPE;
- }
- case ITEMS: {
- return HomeItems.CONTENT_TYPE;
- }
- default: {
- throw new UnsupportedOperationException("Unknown type " + uri);
- }
- }
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
- final int match = URI_MATCHER.match(uri);
-
- // If we're querying the fake items, don't try to get the database.
- if (match == ITEMS_FAKE) {
- return queryFakeItems(uri, projection, selection, selectionArgs, sortOrder);
- }
-
- final String datasetId = uri.getQueryParameter(BrowserContract.PARAM_DATASET_ID);
- if (datasetId == null) {
- throw new IllegalArgumentException("All queries should contain a dataset ID parameter");
- }
-
- selection = DBUtils.concatenateWhere(selection, HomeItems.DATASET_ID + " = ?");
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { datasetId });
-
- // Otherwise, let the SQLiteContentProvider implementation take care of this query for us!
- Cursor c = super.query(uri, projection, selection, selectionArgs, sortOrder);
-
- // SQLiteBridgeContentProvider may return a null Cursor if the database hasn't been created yet.
- // However, we need a non-null cursor in order to listen for notifications.
- if (c == null) {
- c = new MatrixCursor(projection != null ? projection : HomeItems.DEFAULT_PROJECTION);
- }
-
- final ContentResolver cr = getContext().getContentResolver();
- c.setNotificationUri(cr, getDatasetNotificationUri(datasetId));
-
- return c;
- }
-
- /**
- * Returns a cursor populated with static fake data.
- */
- private Cursor queryFakeItems(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
- JSONArray items = null;
- try {
- final String jsonString = RawResource.getAsString(getContext(), R.raw.fake_home_items);
- items = new JSONArray(jsonString);
- } catch (IOException e) {
- Log.e(LOGTAG, "Error getting fake home items", e);
- return null;
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error parsing fake_home_items.json", e);
- return null;
- }
-
- final MatrixCursor c = new MatrixCursor(HomeItems.DEFAULT_PROJECTION);
- for (int i = 0; i < items.length(); i++) {
- try {
- final JSONObject item = items.getJSONObject(i);
- c.addRow(new Object[] {
- item.getInt("id"),
- item.getString("dataset_id"),
- item.getString("url"),
- item.getString("title"),
- item.getString("description"),
- item.getString("image_url"),
- item.getString("filter")
- });
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error creating cursor row for fake home item", e);
- }
- }
- return c;
- }
-
- /**
- * SQLiteBridgeContentProvider implementation
- */
-
- @Override
- protected String getDBName() {
- return DB_FILENAME;
- }
-
- @Override
- protected String getTelemetryPrefix() {
- return TELEMETRY_TAG;
- }
-
- @Override
- protected int getDBVersion() {
- return DB_VERSION;
- }
-
- @Override
- public String getTable(Uri uri) {
- final int match = URI_MATCHER.match(uri);
- switch (match) {
- case ITEMS: {
- return TABLE_ITEMS;
- }
- default: {
- throw new UnsupportedOperationException("Unknown table " + uri);
- }
- }
- }
-
- @Override
- public String getSortOrder(Uri uri, String aRequested) {
- return null;
- }
-
- @Override
- public void setupDefaults(Uri uri, ContentValues values) { }
-
- @Override
- public void initGecko() { }
-
- @Override
- public void onPreInsert(ContentValues values, Uri uri, SQLiteBridge db) { }
-
- @Override
- public void onPreUpdate(ContentValues values, Uri uri, SQLiteBridge db) { }
-
- @Override
- public void onPostQuery(Cursor cursor, Uri uri, SQLiteBridge db) { }
-
- public static Uri getDatasetNotificationUri(String datasetId) {
- return Uri.withAppendedPath(HomeItems.CONTENT_URI, datasetId);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java b/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
deleted file mode 100644
index 8c219282f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
+++ /dev/null
@@ -1,1938 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.lang.IllegalAccessException;
-import java.lang.NoSuchFieldException;
-import java.lang.reflect.Array;
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract.ActivityStreamBlocklist;
-import org.mozilla.gecko.db.BrowserContract.Bookmarks;
-import org.mozilla.gecko.db.BrowserContract.Combined;
-import org.mozilla.gecko.db.BrowserContract.ExpirePriority;
-import org.mozilla.gecko.db.BrowserContract.Favicons;
-import org.mozilla.gecko.db.BrowserContract.History;
-import org.mozilla.gecko.db.BrowserContract.SyncColumns;
-import org.mozilla.gecko.db.BrowserContract.Thumbnails;
-import org.mozilla.gecko.db.BrowserContract.TopSites;
-import org.mozilla.gecko.db.BrowserContract.Highlights;
-import org.mozilla.gecko.db.BrowserContract.PageMetadata;
-import org.mozilla.gecko.distribution.Distribution;
-import org.mozilla.gecko.icons.decoders.FaviconDecoder;
-import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
-import org.mozilla.gecko.gfx.BitmapUtils;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.util.GeckoJarReader;
-import org.mozilla.gecko.util.StringUtils;
-
-import android.content.ContentProviderClient;
-import android.content.ContentProviderOperation;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.database.MergeCursor;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.drawable.BitmapDrawable;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.support.annotation.CheckResult;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v4.content.CursorLoader;
-import android.text.TextUtils;
-import android.util.Log;
-import org.mozilla.gecko.util.IOUtils;
-
-import static org.mozilla.gecko.util.IOUtils.ConsumedInputStream;
-
-public class LocalBrowserDB extends BrowserDB {
- // The default size of the buffer to use for downloading Favicons in the event no size is given
- // by the server.
- public static final int DEFAULT_FAVICON_BUFFER_SIZE_BYTES = 25000;
-
- private static final String LOGTAG = "GeckoLocalBrowserDB";
-
- // Calculate this once, at initialization. isLoggable is too expensive to
- // have in-line in each log call.
- private static final boolean logDebug = Log.isLoggable(LOGTAG, Log.DEBUG);
- protected static void debug(String message) {
- if (logDebug) {
- Log.d(LOGTAG, message);
- }
- }
-
- // Sentinel value used to indicate a failure to locate an ID for a default favicon.
- private static final int FAVICON_ID_NOT_FOUND = Integer.MIN_VALUE;
-
- // Constant used to indicate that no folder was found for particular GUID.
- private static final long FOLDER_NOT_FOUND = -1L;
-
- private final String mProfile;
-
- // Map of folder GUIDs to IDs. Used for caching.
- private final HashMap<String, Long> mFolderIdMap;
-
- // Use wrapped Boolean so that we can have a null state
- private volatile Boolean mDesktopBookmarksExist;
-
- private volatile SuggestedSites mSuggestedSites;
-
- // Constants used when importing history data from legacy browser.
- public static String HISTORY_VISITS_DATE = "date";
- public static String HISTORY_VISITS_COUNT = "visits";
- public static String HISTORY_VISITS_URL = "url";
-
- private static final String TELEMETRY_HISTOGRAM_ACITIVITY_STREAM_TOPSITES = "FENNEC_ACTIVITY_STREAM_TOPSITES_LOADER_TIME_MS";
-
- private final Uri mBookmarksUriWithProfile;
- private final Uri mParentsUriWithProfile;
- private final Uri mHistoryUriWithProfile;
- private final Uri mHistoryExpireUriWithProfile;
- private final Uri mCombinedUriWithProfile;
- private final Uri mUpdateHistoryUriWithProfile;
- private final Uri mFaviconsUriWithProfile;
- private final Uri mThumbnailsUriWithProfile;
- private final Uri mTopSitesUriWithProfile;
- private final Uri mHighlightsUriWithProfile;
- private final Uri mSearchHistoryUri;
- private final Uri mActivityStreamBlockedUriWithProfile;
- private final Uri mPageMetadataWithProfile;
-
- private LocalSearches searches;
- private LocalTabsAccessor tabsAccessor;
- private LocalURLMetadata urlMetadata;
- private LocalUrlAnnotations urlAnnotations;
-
- private static final String[] DEFAULT_BOOKMARK_COLUMNS =
- new String[] { Bookmarks._ID,
- Bookmarks.GUID,
- Bookmarks.URL,
- Bookmarks.TITLE,
- Bookmarks.TYPE,
- Bookmarks.PARENT };
-
- public LocalBrowserDB(String profile) {
- mProfile = profile;
- mFolderIdMap = new HashMap<String, Long>();
-
- mBookmarksUriWithProfile = DBUtils.appendProfile(profile, Bookmarks.CONTENT_URI);
- mParentsUriWithProfile = DBUtils.appendProfile(profile, Bookmarks.PARENTS_CONTENT_URI);
- mHistoryUriWithProfile = DBUtils.appendProfile(profile, History.CONTENT_URI);
- mHistoryExpireUriWithProfile = DBUtils.appendProfile(profile, History.CONTENT_OLD_URI);
- mCombinedUriWithProfile = DBUtils.appendProfile(profile, Combined.CONTENT_URI);
- mFaviconsUriWithProfile = DBUtils.appendProfile(profile, Favicons.CONTENT_URI);
- mTopSitesUriWithProfile = DBUtils.appendProfile(profile, TopSites.CONTENT_URI);
- mHighlightsUriWithProfile = DBUtils.appendProfile(profile, Highlights.CONTENT_URI);
- mThumbnailsUriWithProfile = DBUtils.appendProfile(profile, Thumbnails.CONTENT_URI);
- mActivityStreamBlockedUriWithProfile = DBUtils.appendProfile(profile, ActivityStreamBlocklist.CONTENT_URI);
-
- mPageMetadataWithProfile = DBUtils.appendProfile(profile, PageMetadata.CONTENT_URI);
-
- mSearchHistoryUri = BrowserContract.SearchHistory.CONTENT_URI;
-
- mUpdateHistoryUriWithProfile =
- mHistoryUriWithProfile.buildUpon()
- .appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true")
- .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true")
- .build();
-
- searches = new LocalSearches(mProfile);
- tabsAccessor = new LocalTabsAccessor(mProfile);
- urlMetadata = new LocalURLMetadata(mProfile);
- urlAnnotations = new LocalUrlAnnotations(mProfile);
- }
-
- @Override
- public Searches getSearches() {
- return searches;
- }
-
- @Override
- public TabsAccessor getTabsAccessor() {
- return tabsAccessor;
- }
-
- @Override
- public URLMetadata getURLMetadata() {
- return urlMetadata;
- }
-
- @RobocopTarget
- @Override
- public UrlAnnotations getUrlAnnotations() {
- return urlAnnotations;
- }
-
- /**
- * Not thread safe. A helper to allocate new IDs for arbitrary strings.
- */
- private static class NameCounter {
- private final HashMap<String, Integer> names = new HashMap<String, Integer>();
- private int counter;
- private final int increment;
-
- public NameCounter(int start, int increment) {
- this.counter = start;
- this.increment = increment;
- }
-
- public int get(final String name) {
- Integer mapping = names.get(name);
- if (mapping == null) {
- int ours = counter;
- counter += increment;
- names.put(name, ours);
- return ours;
- }
-
- return mapping;
- }
-
- public boolean has(final String name) {
- return names.containsKey(name);
- }
- }
-
- /**
- * Add default bookmarks to the database.
- * Takes an offset; returns a new offset.
- */
- @Override
- public int addDefaultBookmarks(Context context, ContentResolver cr, final int offset) {
- final long folderID = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
- if (folderID == FOLDER_NOT_FOUND) {
- Log.e(LOGTAG, "No mobile folder: cannot add default bookmarks.");
- return offset;
- }
-
- // Use reflection to walk the set of bookmark defaults.
- // This is horrible.
- final Class<?> stringsClass = R.string.class;
- final Field[] fields = stringsClass.getFields();
- final Pattern p = Pattern.compile("^bookmarkdefaults_title_");
-
- int pos = offset;
- final long now = System.currentTimeMillis();
-
- final ArrayList<ContentValues> bookmarkValues = new ArrayList<ContentValues>();
- final ArrayList<ContentValues> faviconValues = new ArrayList<ContentValues>();
-
- // Count down from -offset into negative values to get new favicon IDs.
- final NameCounter faviconIDs = new NameCounter((-1 - offset), -1);
-
- for (int i = 0; i < fields.length; i++) {
- final String name = fields[i].getName();
- final Matcher m = p.matcher(name);
- if (!m.find()) {
- continue;
- }
-
- try {
- if (Restrictions.isRestrictedProfile(context)) {
- // matching on variable name from strings.xml.in
- final String addons = "bookmarkdefaults_title_addons";
- final String regularSumo = "bookmarkdefaults_title_support";
- if (name.equals(addons) || name.equals(regularSumo)) {
- continue;
- }
- }
- if (!Restrictions.isRestrictedProfile(context)) {
- // if we're not in kidfox, skip the kidfox specific bookmark(s)
- if (name.startsWith("bookmarkdefaults_title_restricted")) {
- continue;
- }
- }
- final int titleID = fields[i].getInt(null);
- final String title = context.getString(titleID);
-
- final Field urlField = stringsClass.getField(name.replace("_title_", "_url_"));
- final int urlID = urlField.getInt(null);
- final String url = context.getString(urlID);
-
- final ContentValues bookmarkValue = createBookmark(now, title, url, pos++, folderID);
- bookmarkValues.add(bookmarkValue);
-
- ConsumedInputStream faviconStream = getDefaultFaviconFromDrawable(context, name);
- if (faviconStream == null) {
- faviconStream = getDefaultFaviconFromPath(context, name);
- }
-
- if (faviconStream == null) {
- continue;
- }
-
- // In the event that truncating the buffer fails, give up and move on.
- byte[] icon;
- try {
- icon = faviconStream.getTruncatedData();
- } catch (OutOfMemoryError e) {
- continue;
- }
-
- final ContentValues iconValue = createFavicon(url, icon);
-
- // Assign a reserved negative _id to each new favicon.
- // For now, each name is expected to be unique, and duplicate
- // icons will be duplicated in the DB. See Bug 1040806 Comment 8.
- if (iconValue != null) {
- final int faviconID = faviconIDs.get(name);
- iconValue.put("_id", faviconID);
- bookmarkValue.put(Bookmarks.FAVICON_ID, faviconID);
- faviconValues.add(iconValue);
- }
- } catch (IllegalAccessException | IllegalArgumentException | NoSuchFieldException e) {
- Log.wtf(LOGTAG, "Reflection failure.", e);
- }
- }
-
- if (!faviconValues.isEmpty()) {
- try {
- cr.bulkInsert(mFaviconsUriWithProfile, faviconValues.toArray(new ContentValues[faviconValues.size()]));
- } catch (Exception e) {
- Log.e(LOGTAG, "Error bulk-inserting default favicons.", e);
- }
- }
-
- if (!bookmarkValues.isEmpty()) {
- try {
- final int inserted = cr.bulkInsert(mBookmarksUriWithProfile, bookmarkValues.toArray(new ContentValues[bookmarkValues.size()]));
- return offset + inserted;
- } catch (Exception e) {
- Log.e(LOGTAG, "Error bulk-inserting default bookmarks.", e);
- }
- }
-
- return offset;
- }
-
- /**
- * Add bookmarks from the provided distribution.
- * Takes an offset; returns a new offset.
- */
- @Override
- public int addDistributionBookmarks(ContentResolver cr, Distribution distribution, int offset) {
- if (!distribution.exists()) {
- Log.d(LOGTAG, "No distribution from which to add bookmarks.");
- return offset;
- }
-
- final JSONArray bookmarks = distribution.getBookmarks();
- if (bookmarks == null) {
- Log.d(LOGTAG, "No distribution bookmarks.");
- return offset;
- }
-
- final long folderID = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
- if (folderID == FOLDER_NOT_FOUND) {
- Log.e(LOGTAG, "No mobile folder: cannot add distribution bookmarks.");
- return offset;
- }
-
- final Locale locale = Locale.getDefault();
- final long now = System.currentTimeMillis();
- int mobilePos = offset;
- int pinnedPos = 0; // Assume nobody has pinned anything yet.
-
- final ArrayList<ContentValues> bookmarkValues = new ArrayList<ContentValues>();
- final ArrayList<ContentValues> faviconValues = new ArrayList<ContentValues>();
-
- // Count down from -offset into negative values to get new favicon IDs.
- final NameCounter faviconIDs = new NameCounter((-1 - offset), -1);
-
- for (int i = 0; i < bookmarks.length(); i++) {
- try {
- final JSONObject bookmark = bookmarks.getJSONObject(i);
-
- final String title = getLocalizedProperty(bookmark, "title", locale);
- final String url = getLocalizedProperty(bookmark, "url", locale);
- final long parent;
- final int pos;
- if (bookmark.has("pinned")) {
- parent = Bookmarks.FIXED_PINNED_LIST_ID;
- pos = pinnedPos++;
- } else {
- parent = folderID;
- pos = mobilePos++;
- }
-
- final ContentValues bookmarkValue = createBookmark(now, title, url, pos, parent);
- bookmarkValues.add(bookmarkValue);
-
- // Return early if there is no icon for this bookmark.
- if (!bookmark.has("icon")) {
- continue;
- }
-
- try {
- final String iconData = bookmark.getString("icon");
-
- byte[] icon = BitmapUtils.getBytesFromDataURI(iconData);
- if (icon == null) {
- continue;
- }
-
- final ContentValues iconValue = createFavicon(url, icon);
- if (iconValue == null) {
- continue;
- }
-
- /*
- * Find out if this icon is a duplicate. If it is, don't try
- * to insert it again, but reuse the shared ID.
- * Otherwise, assign a new reserved negative _id.
- * Duplicates won't be detected in default bookmarks, or
- * those already in the database.
- */
- final boolean seen = faviconIDs.has(iconData);
- final int faviconID = faviconIDs.get(iconData);
-
- iconValue.put("_id", faviconID);
- bookmarkValue.put(Bookmarks.FAVICON_ID, faviconID);
-
- if (!seen) {
- faviconValues.add(iconValue);
- }
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error creating distribution bookmark icon.", e);
- }
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error creating distribution bookmark.", e);
- }
- }
-
- if (!faviconValues.isEmpty()) {
- try {
- cr.bulkInsert(mFaviconsUriWithProfile, faviconValues.toArray(new ContentValues[faviconValues.size()]));
- } catch (Exception e) {
- Log.e(LOGTAG, "Error bulk-inserting distribution favicons.", e);
- }
- }
-
- if (!bookmarkValues.isEmpty()) {
- try {
- final int inserted = cr.bulkInsert(mBookmarksUriWithProfile, bookmarkValues.toArray(new ContentValues[bookmarkValues.size()]));
- return offset + inserted;
- } catch (Exception e) {
- Log.e(LOGTAG, "Error bulk-inserting distribution bookmarks.", e);
- }
- }
-
- return offset;
- }
-
- private static ContentValues createBookmark(final long timestamp, final String title, final String url, final int pos, final long parent) {
- final ContentValues v = new ContentValues();
-
- v.put(Bookmarks.DATE_CREATED, timestamp);
- v.put(Bookmarks.DATE_MODIFIED, timestamp);
- v.put(Bookmarks.GUID, Utils.generateGuid());
-
- v.put(Bookmarks.PARENT, parent);
- v.put(Bookmarks.POSITION, pos);
- v.put(Bookmarks.TITLE, title);
- v.put(Bookmarks.URL, url);
- return v;
- }
-
- private static ContentValues createFavicon(final String url, final byte[] icon) {
- ContentValues iconValues = new ContentValues();
- iconValues.put(Favicons.PAGE_URL, url);
- iconValues.put(Favicons.DATA, icon);
-
- return iconValues;
- }
-
- private static String getLocalizedProperty(final JSONObject bookmark, final String property, final Locale locale) throws JSONException {
- // Try the full locale.
- final String fullLocale = property + "." + locale.toString();
- if (bookmark.has(fullLocale)) {
- return bookmark.getString(fullLocale);
- }
-
- // Try without a variant.
- if (!TextUtils.isEmpty(locale.getVariant())) {
- String noVariant = fullLocale.substring(0, fullLocale.lastIndexOf("_"));
- if (bookmark.has(noVariant)) {
- return bookmark.getString(noVariant);
- }
- }
-
- // Try just the language.
- String lang = property + "." + locale.getLanguage();
- if (bookmark.has(lang)) {
- return bookmark.getString(lang);
- }
-
- // Default to the non-localized property name.
- return bookmark.getString(property);
- }
-
- private static int getFaviconId(String name) {
- try {
- Class<?> drawablesClass = R.raw.class;
-
- // Look for a favicon with the id R.raw.bookmarkdefaults_favicon_*.
- Field faviconField = drawablesClass.getField(name.replace("_title_", "_favicon_"));
- faviconField.setAccessible(true);
-
- return faviconField.getInt(null);
- } catch (IllegalAccessException | NoSuchFieldException e) {
- // We'll end up here for any default bookmark that doesn't have a favicon in
- // resources/raw/ (i.e., about:firefox). When this happens, the Favicons service will
- // fall back to the default branding icon for about pages. Non-about pages should always
- // specify an icon; otherwise, the placeholder globe favicon will be used.
- Log.d(LOGTAG, "No raw favicon resource found for " + name);
- }
-
- Log.e(LOGTAG, "Failed to find favicon resource ID for " + name);
- return FAVICON_ID_NOT_FOUND;
- }
-
- @Override
- public boolean insertPageMetadata(ContentProviderClient contentProviderClient, String pageUrl, boolean hasImage, String metadataJSON) {
- final String historyGUID = lookupHistoryGUIDByPageUri(contentProviderClient, pageUrl);
-
- if (historyGUID == null) {
- return false;
- }
-
- // We have the GUID, insert the metadata.
- final ContentValues cv = new ContentValues();
- cv.put(PageMetadata.HISTORY_GUID, historyGUID);
- cv.put(PageMetadata.HAS_IMAGE, hasImage);
- cv.put(PageMetadata.JSON, metadataJSON);
-
- try {
- contentProviderClient.insert(mPageMetadataWithProfile, cv);
- } catch (RemoteException e) {
- throw new IllegalStateException("Unexpected RemoteException", e);
- }
-
- return true;
- }
-
- @Override
- public int deletePageMetadata(ContentProviderClient contentProviderClient, String pageUrl) {
- final String historyGUID = lookupHistoryGUIDByPageUri(contentProviderClient, pageUrl);
-
- if (historyGUID == null) {
- return 0;
- }
-
- try {
- return contentProviderClient.delete(mPageMetadataWithProfile, PageMetadata.HISTORY_GUID + " = ?", new String[]{historyGUID});
- } catch (RemoteException e) {
- throw new IllegalStateException("Unexpected RemoteException", e);
- }
- }
-
- @Nullable
- private String lookupHistoryGUIDByPageUri(ContentProviderClient contentProviderClient, String uri) {
- // Unfortunately we might have duplicate history records for the same URL.
- final Cursor cursor;
- try {
- cursor = contentProviderClient.query(
- mHistoryUriWithProfile
- .buildUpon()
- .appendQueryParameter(BrowserContract.PARAM_LIMIT, "1")
- .build(),
- new String[]{
- History.GUID,
- },
- History.URL + "= ?",
- new String[]{uri}, History.DATE_LAST_VISITED + " DESC"
- );
- } catch (RemoteException e) {
- // Won't happen, we control the implementation.
- throw new IllegalStateException("Unexpected RemoteException", e);
- }
-
- if (cursor == null) {
- return null;
- }
-
- try {
- if (!cursor.moveToFirst()) {
- return null;
- }
-
- final int historyGUIDCol = cursor.getColumnIndexOrThrow(History.GUID);
- return cursor.getString(historyGUIDCol);
- } finally {
- cursor.close();
- }
- }
-
- /**
- * Load a favicon from the omnijar.
- * @return A ConsumedInputStream containing the bytes loaded from omnijar. This must be a format
- * compatible with the favicon decoder (most probably a PNG or ICO file).
- */
- private static ConsumedInputStream getDefaultFaviconFromPath(Context context, String name) {
- final int faviconId = getFaviconId(name);
- if (faviconId == FAVICON_ID_NOT_FOUND) {
- return null;
- }
-
- final String bitmapPath = GeckoJarReader.getJarURL(context, context.getString(faviconId));
- final InputStream iStream = GeckoJarReader.getStream(context, bitmapPath);
-
- return IOUtils.readFully(iStream, DEFAULT_FAVICON_BUFFER_SIZE_BYTES);
- }
-
- private static ConsumedInputStream getDefaultFaviconFromDrawable(Context context, String name) {
- int faviconId = getFaviconId(name);
- if (faviconId == FAVICON_ID_NOT_FOUND) {
- return null;
- }
-
- InputStream iStream = context.getResources().openRawResource(faviconId);
- return IOUtils.readFully(iStream, DEFAULT_FAVICON_BUFFER_SIZE_BYTES);
- }
-
- // Invalidate cached data
- @Override
- public void invalidate() {
- mDesktopBookmarksExist = null;
- }
-
- private Uri bookmarksUriWithLimit(int limit) {
- return mBookmarksUriWithProfile.buildUpon()
- .appendQueryParameter(BrowserContract.PARAM_LIMIT,
- String.valueOf(limit))
- .build();
- }
-
- private Uri combinedUriWithLimit(int limit) {
- return mCombinedUriWithProfile.buildUpon()
- .appendQueryParameter(BrowserContract.PARAM_LIMIT,
- String.valueOf(limit))
- .build();
- }
-
- private static Uri withDeleted(final Uri uri) {
- return uri.buildUpon()
- .appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "1")
- .build();
- }
-
- private Cursor filterAllSites(ContentResolver cr, String[] projection, CharSequence constraint,
- int limit, CharSequence urlFilter, String selection, String[] selectionArgs) {
- // The combined history/bookmarks selection queries for sites with a URL or title containing
- // the constraint string(s), treating space-separated words as separate constraints
- if (!TextUtils.isEmpty(constraint)) {
- final String[] constraintWords = constraint.toString().split(" ");
-
- // Only create a filter query with a maximum of 10 constraint words.
- final int constraintCount = Math.min(constraintWords.length, 10);
- for (int i = 0; i < constraintCount; i++) {
- selection = DBUtils.concatenateWhere(selection, "(" + Combined.URL + " LIKE ? OR " +
- Combined.TITLE + " LIKE ?)");
- String constraintWord = "%" + constraintWords[i] + "%";
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { constraintWord, constraintWord });
- }
- }
-
- if (urlFilter != null) {
- selection = DBUtils.concatenateWhere(selection, "(" + Combined.URL + " NOT LIKE ?)");
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs, new String[] { urlFilter.toString() });
- }
-
- // Order by combined remote+local frecency score.
- // Local visits are preferred, so they will by far outweigh remote visits.
- // Bookmarked history items get extra frecency points.
- final String sortOrder = BrowserContract.getCombinedFrecencySortOrder(true, false);
-
- return cr.query(combinedUriWithLimit(limit),
- projection,
- selection,
- selectionArgs,
- sortOrder);
- }
-
- @Override
- public int getCount(ContentResolver cr, String database) {
- int count = 0;
- String[] columns = null;
- String constraint = null;
- Uri uri = null;
-
- if ("history".equals(database)) {
- uri = mHistoryUriWithProfile;
- columns = new String[] { History._ID };
- constraint = Combined.VISITS + " > 0";
- } else if ("bookmarks".equals(database)) {
- uri = mBookmarksUriWithProfile;
- columns = new String[] { Bookmarks._ID };
- // ignore folders, tags, keywords, separators, etc.
- constraint = Bookmarks.TYPE + " = " + Bookmarks.TYPE_BOOKMARK;
- } else if ("thumbnails".equals(database)) {
- uri = mThumbnailsUriWithProfile;
- columns = new String[] { Thumbnails._ID };
- } else if ("favicons".equals(database)) {
- uri = mFaviconsUriWithProfile;
- columns = new String[] { Favicons._ID };
- }
-
- if (uri != null) {
- final Cursor cursor = cr.query(uri, columns, constraint, null, null);
-
- try {
- count = cursor.getCount();
- } finally {
- cursor.close();
- }
- }
-
- debug("Got count " + count + " for " + database);
- return count;
- }
-
- @Override
- @RobocopTarget
- public Cursor filter(ContentResolver cr, CharSequence constraint, int limit,
- EnumSet<FilterFlags> flags) {
- String selection = "";
- String[] selectionArgs = null;
-
- if (flags.contains(FilterFlags.EXCLUDE_PINNED_SITES)) {
- selection = Combined.URL + " NOT IN (SELECT " +
- Bookmarks.URL + " FROM bookmarks WHERE " +
- DBUtils.qualifyColumn("bookmarks", Bookmarks.PARENT) + " = ? AND " +
- DBUtils.qualifyColumn("bookmarks", Bookmarks.IS_DELETED) + " == 0)";
- selectionArgs = new String[] { String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) };
- }
-
- return filterAllSites(cr,
- new String[] { Combined._ID,
- Combined.URL,
- Combined.TITLE,
- Combined.BOOKMARK_ID,
- Combined.HISTORY_ID },
- constraint,
- limit,
- null,
- selection, selectionArgs);
- }
-
- @Override
- public void updateVisitedHistory(ContentResolver cr, String uri) {
- ContentValues values = new ContentValues();
-
- values.put(History.URL, uri);
- values.put(History.DATE_LAST_VISITED, System.currentTimeMillis());
- values.put(History.IS_DELETED, 0);
-
- // This will insert a new history entry if one for this URL
- // doesn't already exist
- cr.update(mUpdateHistoryUriWithProfile,
- values,
- History.URL + " = ?",
- new String[] { uri });
- }
-
- @Override
- public void updateHistoryTitle(ContentResolver cr, String uri, String title) {
- ContentValues values = new ContentValues();
- values.put(History.TITLE, title);
-
- cr.update(mHistoryUriWithProfile,
- values,
- History.URL + " = ?",
- new String[] { uri });
- }
-
- @Override
- @RobocopTarget
- public Cursor getAllVisitedHistory(ContentResolver cr) {
- return cr.query(mHistoryUriWithProfile,
- new String[] { History.URL },
- History.VISITS + " > 0",
- null,
- null);
- }
-
- @Override
- public Cursor getRecentHistory(ContentResolver cr, int limit) {
- return cr.query(combinedUriWithLimit(limit),
- new String[] { Combined._ID,
- Combined.BOOKMARK_ID,
- Combined.HISTORY_ID,
- Combined.URL,
- Combined.TITLE,
- Combined.DATE_LAST_VISITED,
- Combined.VISITS },
- History.DATE_LAST_VISITED + " > 0",
- null,
- History.DATE_LAST_VISITED + " DESC");
- }
-
- @Override
- public Cursor getRecentHistoryBetweenTime(ContentResolver cr, int limit, long start, long end) {
- return cr.query(combinedUriWithLimit(limit),
- new String[] { Combined._ID,
- Combined.BOOKMARK_ID,
- Combined.HISTORY_ID,
- Combined.URL,
- Combined.TITLE,
- Combined.DATE_LAST_VISITED,
- Combined.VISITS },
- History.DATE_LAST_VISITED + " >= " + start + " AND " + History.DATE_LAST_VISITED + " < " + end,
- null,
- History.DATE_LAST_VISITED + " DESC");
- }
-
- public Cursor getHistoryForURL(ContentResolver cr, String uri) {
- return cr.query(mHistoryUriWithProfile,
- new String[] {
- History.VISITS,
- History.DATE_LAST_VISITED
- },
- History.URL + "= ?",
- new String[] { uri },
- History.DATE_LAST_VISITED + " DESC"
- );
- }
-
- @Override
- public long getPrePathLastVisitedTimeMilliseconds(ContentResolver cr, String prePath) {
- if (prePath == null) {
- return 0;
- }
- // If we don't end with a trailing slash, then both https://foo.com and https://foo.company.biz will match.
- if (!prePath.endsWith("/")) {
- prePath = prePath + "/";
- }
- final Cursor cursor = cr.query(BrowserContract.History.CONTENT_URI,
- new String[] { "MAX(" + BrowserContract.HistoryColumns.DATE_LAST_VISITED + ") AS date" },
- BrowserContract.URLColumns.URL + " BETWEEN ? AND ?", new String[] { prePath, prePath + "\u007f" }, null);
- try {
- cursor.moveToFirst();
- if (cursor.isAfterLast()) {
- return 0;
- }
- return cursor.getLong(0);
- } finally {
- cursor.close();
- }
- }
-
- @Override
- public void expireHistory(ContentResolver cr, ExpirePriority priority) {
- Uri url = mHistoryExpireUriWithProfile;
- url = url.buildUpon().appendQueryParameter(BrowserContract.PARAM_EXPIRE_PRIORITY, priority.toString()).build();
- cr.delete(url, null, null);
- }
-
- @Override
- @RobocopTarget
- public void removeHistoryEntry(ContentResolver cr, String url) {
- cr.delete(mHistoryUriWithProfile,
- History.URL + " = ?",
- new String[] { url });
- }
-
- @Override
- public void clearHistory(ContentResolver cr, boolean clearSearchHistory) {
- if (clearSearchHistory) {
- cr.delete(mSearchHistoryUri, null, null);
- } else {
- cr.delete(mHistoryUriWithProfile, null, null);
- }
- }
-
- private void assertDefaultBookmarkColumnOrdering() {
- // We need to insert MatrixCursor values in a specific order - in order to protect against changes
- // in DEFAULT_BOOKMARK_COLUMNS we can just assert that we're using the correct ordering.
- // Alternatively we could use RowBuilder.add(columnName, value) but that needs api >= 19,
- // or we could iterate over DEFAULT_BOOKMARK_COLUMNS, but that gets messy once we need
- // to add more than one artificial folder.
- if (!((DEFAULT_BOOKMARK_COLUMNS[0].equals(Bookmarks._ID)) &&
- (DEFAULT_BOOKMARK_COLUMNS[1].equals(Bookmarks.GUID)) &&
- (DEFAULT_BOOKMARK_COLUMNS[2].equals(Bookmarks.URL)) &&
- (DEFAULT_BOOKMARK_COLUMNS[3].equals(Bookmarks.TITLE)) &&
- (DEFAULT_BOOKMARK_COLUMNS[4].equals(Bookmarks.TYPE)) &&
- (DEFAULT_BOOKMARK_COLUMNS[5].equals(Bookmarks.PARENT)) &&
- (DEFAULT_BOOKMARK_COLUMNS.length == 6))) {
- // If DEFAULT_BOOKMARK_COLUMNS changes we need to update all the MatrixCursor rows
- // to contain appropriate data.
- throw new IllegalStateException("Fake folder MatrixCursor creation code must be updated to match DEFAULT_BOOKMARK_COLUMNS");
- }
- }
-
- /**
- * Retrieve the list of reader-view bookmarks, i.e. the equivalent of the former reading-list.
- * This is the result of a join of bookmarks with reader-view annotations (as stored in
- * UrlAnnotations).
- */
- private Cursor getReadingListBookmarks(ContentResolver cr) {
- // group by URL to avoid having duplicate bookmarks listed. It's possible to have multiple
- // bookmarks pointing to the same URL (this would most commonly happen by manually
- // copying bookmarks on desktop, followed by syncing with mobile), and we don't want
- // to show the same URL multiple times in the reading list folder.
- final Uri bookmarksGroupedByUri = mBookmarksUriWithProfile.buildUpon()
- .appendQueryParameter(BrowserContract.PARAM_GROUP_BY, Bookmarks.URL)
- .build();
-
- return cr.query(bookmarksGroupedByUri,
- DEFAULT_BOOKMARK_COLUMNS,
- Bookmarks.ANNOTATION_KEY + " == ? AND " +
- Bookmarks.ANNOTATION_VALUE + " == ? AND " +
- "(" + Bookmarks.TYPE + " = ? AND " + Bookmarks.URL + " IS NOT NULL)",
- new String[] {
- BrowserContract.UrlAnnotations.Key.READER_VIEW.getDbValue(),
- BrowserContract.UrlAnnotations.READER_VIEW_SAVED_VALUE,
- String.valueOf(Bookmarks.TYPE_BOOKMARK) },
- null);
- }
-
- @Override
- @RobocopTarget
- public Cursor getBookmarksInFolder(ContentResolver cr, long folderId) {
- final boolean addDesktopFolder;
- final boolean addScreenshotsFolder;
- final boolean addReadingListFolder;
-
- // We always want to show mobile bookmarks in the root view.
- if (folderId == Bookmarks.FIXED_ROOT_ID) {
- folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
-
- // We'll add a fake "Desktop Bookmarks" folder to the root view if desktop
- // bookmarks exist, so that the user can still access non-mobile bookmarks.
- addDesktopFolder = desktopBookmarksExist(cr);
- addScreenshotsFolder = AppConstants.SCREENSHOTS_IN_BOOKMARKS_ENABLED;
-
- final int readingListItemCount = getBookmarkCountForFolder(cr, Bookmarks.FAKE_READINGLIST_SMARTFOLDER_ID);
- addReadingListFolder = (readingListItemCount > 0);
- } else {
- addDesktopFolder = false;
- addScreenshotsFolder = false;
- addReadingListFolder = false;
- }
-
- final Cursor c;
-
- // (You can't switch on a long in Java, hence the if statements)
- if (folderId == Bookmarks.FAKE_DESKTOP_FOLDER_ID) {
- // Since the "Desktop Bookmarks" folder doesn't actually exist, we
- // just fake it by querying specifically certain known desktop folders.
- c = cr.query(mBookmarksUriWithProfile,
- DEFAULT_BOOKMARK_COLUMNS,
- Bookmarks.GUID + " = ? OR " +
- Bookmarks.GUID + " = ? OR " +
- Bookmarks.GUID + " = ?",
- new String[] { Bookmarks.TOOLBAR_FOLDER_GUID,
- Bookmarks.MENU_FOLDER_GUID,
- Bookmarks.UNFILED_FOLDER_GUID },
- null);
- } else if (folderId == Bookmarks.FIXED_SCREENSHOT_FOLDER_ID) {
- c = getUrlAnnotations().getScreenshots(cr);
- } else if (folderId == Bookmarks.FAKE_READINGLIST_SMARTFOLDER_ID) {
- c = getReadingListBookmarks(cr);
- } else {
- // Right now, we only support showing folder and bookmark type of
- // entries. We should add support for other types though (bug 737024)
- c = cr.query(mBookmarksUriWithProfile,
- DEFAULT_BOOKMARK_COLUMNS,
- Bookmarks.PARENT + " = ? AND " +
- "(" + Bookmarks.TYPE + " = ? OR " +
- "(" + Bookmarks.TYPE + " = ? AND " + Bookmarks.URL + " IS NOT NULL))",
- new String[] { String.valueOf(folderId),
- String.valueOf(Bookmarks.TYPE_FOLDER),
- String.valueOf(Bookmarks.TYPE_BOOKMARK) },
- null);
- }
-
- final List<Cursor> cursorsToMerge = getSpecialFoldersCursorList(addDesktopFolder, addScreenshotsFolder, addReadingListFolder);
- if (cursorsToMerge.size() >= 1) {
- cursorsToMerge.add(c);
- final Cursor[] arr = (Cursor[]) Array.newInstance(Cursor.class, cursorsToMerge.size());
- return new MergeCursor(cursorsToMerge.toArray(arr));
- } else {
- return c;
- }
- }
-
- @Override
- public int getBookmarkCountForFolder(ContentResolver cr, long folderID) {
- if (folderID == Bookmarks.FAKE_READINGLIST_SMARTFOLDER_ID) {
- return getUrlAnnotations().getAnnotationCount(cr, BrowserContract.UrlAnnotations.Key.READER_VIEW);
- } else {
- throw new IllegalArgumentException("Retrieving bookmark count for folder with ID=" + folderID + " not supported yet");
- }
- }
-
- @CheckResult
- private ArrayList<Cursor> getSpecialFoldersCursorList(final boolean addDesktopFolder,
- final boolean addScreenshotsFolder, final boolean addReadingListFolder) {
- if (addDesktopFolder || addScreenshotsFolder || addReadingListFolder) {
- // Avoid calling this twice.
- assertDefaultBookmarkColumnOrdering();
- }
-
- // Capacity is number of cursors added below plus one for non-special data.
- final ArrayList<Cursor> out = new ArrayList<>(4);
- if (addDesktopFolder) {
- out.add(getSpecialFolderCursor(Bookmarks.FAKE_DESKTOP_FOLDER_ID, Bookmarks.FAKE_DESKTOP_FOLDER_GUID));
- }
-
- if (addScreenshotsFolder) {
- out.add(getSpecialFolderCursor(Bookmarks.FIXED_SCREENSHOT_FOLDER_ID, Bookmarks.SCREENSHOT_FOLDER_GUID));
- }
-
- if (addReadingListFolder) {
- out.add(getSpecialFolderCursor(Bookmarks.FAKE_READINGLIST_SMARTFOLDER_ID, Bookmarks.FAKE_READINGLIST_SMARTFOLDER_GUID));
- }
-
- return out;
- }
-
- @CheckResult
- private MatrixCursor getSpecialFolderCursor(final int folderId, final String folderGuid) {
- final MatrixCursor out = new MatrixCursor(DEFAULT_BOOKMARK_COLUMNS);
- out.addRow(new Object[] {
- folderId,
- folderGuid,
- "",
- "", // Title localisation is done later, in the UI layer (BookmarksListAdapter)
- Bookmarks.TYPE_FOLDER,
- Bookmarks.FIXED_ROOT_ID
- });
- return out;
- }
-
- // Returns true if any desktop bookmarks exist, which will be true if the user
- // has set up sync at one point, or done a profile migration from XUL fennec.
- private boolean desktopBookmarksExist(ContentResolver cr) {
- if (mDesktopBookmarksExist != null) {
- return mDesktopBookmarksExist;
- }
-
- // Check to see if there are any bookmarks in one of our three
- // fixed "Desktop Bookmarks" folders.
- final Cursor c = cr.query(bookmarksUriWithLimit(1),
- new String[] { Bookmarks._ID },
- Bookmarks.PARENT + " = ? OR " +
- Bookmarks.PARENT + " = ? OR " +
- Bookmarks.PARENT + " = ?",
- new String[] { String.valueOf(getFolderIdFromGuid(cr, Bookmarks.TOOLBAR_FOLDER_GUID)),
- String.valueOf(getFolderIdFromGuid(cr, Bookmarks.MENU_FOLDER_GUID)),
- String.valueOf(getFolderIdFromGuid(cr, Bookmarks.UNFILED_FOLDER_GUID)) },
- null);
-
- try {
- // Don't read back out of the cache to avoid races with invalidation.
- final boolean e = c.getCount() > 0;
- mDesktopBookmarksExist = e;
- return e;
- } finally {
- c.close();
- }
- }
-
- @Override
- @RobocopTarget
- public boolean isBookmark(ContentResolver cr, String uri) {
- final Cursor c = cr.query(bookmarksUriWithLimit(1),
- new String[] { Bookmarks._ID },
- Bookmarks.URL + " = ? AND " + Bookmarks.PARENT + " != ?",
- new String[] { uri, String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) },
- Bookmarks.URL);
-
- if (c == null) {
- Log.e(LOGTAG, "Null cursor in isBookmark");
- return false;
- }
-
- try {
- return c.getCount() > 0;
- } finally {
- c.close();
- }
- }
-
- @Override
- public String getUrlForKeyword(ContentResolver cr, String keyword) {
- final Cursor c = cr.query(mBookmarksUriWithProfile,
- new String[] { Bookmarks.URL },
- Bookmarks.KEYWORD + " = ?",
- new String[] { keyword },
- null);
- try {
- if (!c.moveToFirst()) {
- return null;
- }
-
- return c.getString(c.getColumnIndexOrThrow(Bookmarks.URL));
- } finally {
- c.close();
- }
- }
-
- private synchronized long getFolderIdFromGuid(final ContentResolver cr, final String guid) {
- if (mFolderIdMap.containsKey(guid)) {
- return mFolderIdMap.get(guid);
- }
-
- final Cursor c = cr.query(mBookmarksUriWithProfile,
- new String[] { Bookmarks._ID },
- Bookmarks.GUID + " = ?",
- new String[] { guid },
- null);
- try {
- final int col = c.getColumnIndexOrThrow(Bookmarks._ID);
- if (!c.moveToFirst() || c.isNull(col)) {
- return FOLDER_NOT_FOUND;
- }
-
- final long id = c.getLong(col);
- mFolderIdMap.put(guid, id);
- return id;
- } finally {
- c.close();
- }
- }
-
- /**
- * Find parents of records that match the provided criteria, and bump their
- * modified timestamp.
- */
- protected void bumpParents(ContentResolver cr, String param, String value) {
- ContentValues values = new ContentValues();
- values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
-
- String where = param + " = ?";
- String[] args = new String[] { value };
- int updated = cr.update(mParentsUriWithProfile, values, where, args);
- debug("Updated " + updated + " rows to new modified time.");
- }
-
- private void addBookmarkItem(ContentResolver cr, String title, String uri, long folderId) {
- final long now = System.currentTimeMillis();
- ContentValues values = new ContentValues();
- if (title != null) {
- values.put(Bookmarks.TITLE, title);
- }
-
- values.put(Bookmarks.URL, uri);
- values.put(Bookmarks.PARENT, folderId);
- values.put(Bookmarks.DATE_MODIFIED, now);
-
- // Get the page's favicon ID from the history table
- final Cursor c = cr.query(mHistoryUriWithProfile,
- new String[] { History.FAVICON_ID },
- History.URL + " = ?",
- new String[] { uri },
- null);
- try {
- if (c.moveToFirst()) {
- int columnIndex = c.getColumnIndexOrThrow(History.FAVICON_ID);
- if (!c.isNull(columnIndex)) {
- values.put(Bookmarks.FAVICON_ID, c.getLong(columnIndex));
- }
- }
- } finally {
- c.close();
- }
-
- // Restore deleted record if possible
- values.put(Bookmarks.IS_DELETED, 0);
-
- final Uri bookmarksWithInsert = mBookmarksUriWithProfile.buildUpon()
- .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true")
- .build();
- cr.update(bookmarksWithInsert,
- values,
- Bookmarks.URL + " = ? AND " +
- Bookmarks.PARENT + " = " + folderId,
- new String[] { uri });
-
- // Bump parent modified time using its ID.
- debug("Bumping parent modified time for addition to: " + folderId);
- final String where = Bookmarks._ID + " = ?";
- final String[] args = new String[] { String.valueOf(folderId) };
-
- ContentValues bumped = new ContentValues();
- bumped.put(Bookmarks.DATE_MODIFIED, now);
-
- final int updated = cr.update(mBookmarksUriWithProfile, bumped, where, args);
- debug("Updated " + updated + " rows to new modified time.");
- }
-
- @Override
- @RobocopTarget
- public boolean addBookmark(ContentResolver cr, String title, String uri) {
- long folderId = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
- if (isBookmarkForUrlInFolder(cr, uri, folderId)) {
- // Bookmark added already.
- return false;
- }
-
- // Add a new bookmark.
- addBookmarkItem(cr, title, uri, folderId);
- return true;
- }
-
- private boolean isBookmarkForUrlInFolder(ContentResolver cr, String uri, long folderId) {
- final Cursor c = cr.query(bookmarksUriWithLimit(1),
- new String[] { Bookmarks._ID },
- Bookmarks.URL + " = ? AND " + Bookmarks.PARENT + " = ? AND " + Bookmarks.IS_DELETED + " == 0",
- new String[] { uri, String.valueOf(folderId) },
- Bookmarks.URL);
-
- if (c == null) {
- return false;
- }
-
- try {
- return c.getCount() > 0;
- } finally {
- c.close();
- }
- }
-
- @Override
- @RobocopTarget
- public void removeBookmarksWithURL(ContentResolver cr, String uri) {
- Uri contentUri = mBookmarksUriWithProfile;
-
- // Do this now so that the items still exist!
- bumpParents(cr, Bookmarks.URL, uri);
-
- final String[] urlArgs = new String[] { uri, String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) };
- final String urlEquals = Bookmarks.URL + " = ? AND " + Bookmarks.PARENT + " != ? ";
-
- cr.delete(contentUri, urlEquals, urlArgs);
- }
-
- @Override
- public void registerBookmarkObserver(ContentResolver cr, ContentObserver observer) {
- cr.registerContentObserver(mBookmarksUriWithProfile, false, observer);
- }
-
- @Override
- @RobocopTarget
- public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword) {
- ContentValues values = new ContentValues();
- values.put(Bookmarks.TITLE, title);
- values.put(Bookmarks.URL, uri);
- values.put(Bookmarks.KEYWORD, keyword);
- values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
-
- cr.update(mBookmarksUriWithProfile,
- values,
- Bookmarks._ID + " = ?",
- new String[] { String.valueOf(id) });
- }
-
- @Override
- public boolean hasBookmarkWithGuid(ContentResolver cr, String guid) {
- Cursor c = cr.query(bookmarksUriWithLimit(1),
- new String[] { Bookmarks.GUID },
- Bookmarks.GUID + " = ?",
- new String[] { guid },
- null);
-
- try {
- return c != null && c.getCount() > 0;
- } finally {
- if (c != null) {
- c.close();
- }
- }
- }
-
- /**
- * Get the favicon from the database, if any, associated with the given favicon URL. (That is,
- * the URL of the actual favicon image, not the URL of the page with which the favicon is associated.)
- * @param cr The ContentResolver to use.
- * @param faviconURL The URL of the favicon to fetch from the database.
- * @return The decoded Bitmap from the database, if any. null if none is stored.
- */
- @Override
- public LoadFaviconResult getFaviconForUrl(Context context, ContentResolver cr, String faviconURL) {
- final Cursor c = cr.query(mFaviconsUriWithProfile,
- new String[] { Favicons.DATA },
- Favicons.URL + " = ? AND " + Favicons.DATA + " IS NOT NULL",
- new String[] { faviconURL },
- null);
-
- boolean shouldDelete = false;
- byte[] b = null;
- try {
- if (!c.moveToFirst()) {
- return null;
- }
-
- final int faviconIndex = c.getColumnIndexOrThrow(Favicons.DATA);
- try {
- b = c.getBlob(faviconIndex);
- } catch (IllegalStateException e) {
- // This happens when the blob is more than 1MB: Bug 1106347.
- // Delete that row.
- shouldDelete = true;
- }
- } finally {
- c.close();
- }
-
- if (shouldDelete) {
- try {
- Log.d(LOGTAG, "Deleting invalid favicon.");
- cr.delete(mFaviconsUriWithProfile,
- Favicons.URL + " = ?",
- new String[] { faviconURL });
- } catch (Exception e) {
- // Do nothing.
- }
- }
-
- if (b == null) {
- return null;
- }
-
- return FaviconDecoder.decodeFavicon(context, b);
- }
-
- /**
- * Try to find a usable favicon URL in the history or bookmarks table.
- */
- @Override
- public String getFaviconURLFromPageURL(ContentResolver cr, String uri) {
- // Check first in the history table.
- Cursor c = cr.query(mHistoryUriWithProfile,
- new String[] { History.FAVICON_URL },
- Combined.URL + " = ?",
- new String[] { uri },
- null);
-
- try {
- if (c.moveToFirst()) {
- // Interrupted page loads can leave History items without a valid favicon_id.
- final int columnIndex = c.getColumnIndexOrThrow(History.FAVICON_URL);
- if (!c.isNull(columnIndex)) {
- final String faviconURL = c.getString(columnIndex);
- if (faviconURL != null) {
- return faviconURL;
- }
- }
- }
- } finally {
- c.close();
- }
-
- // If that fails, check in the bookmarks table.
- c = cr.query(mBookmarksUriWithProfile,
- new String[] { Bookmarks.FAVICON_URL },
- Bookmarks.URL + " = ?",
- new String[] { uri },
- null);
-
- try {
- if (c.moveToFirst()) {
- return c.getString(c.getColumnIndexOrThrow(Bookmarks.FAVICON_URL));
- }
-
- return null;
- } finally {
- c.close();
- }
- }
-
- @Override
- public boolean hideSuggestedSite(String url) {
- if (mSuggestedSites == null) {
- return false;
- }
-
- return mSuggestedSites.hideSite(url);
- }
-
- @Override
- public void updateThumbnailForUrl(ContentResolver cr, String uri,
- BitmapDrawable thumbnail) {
- // If a null thumbnail was passed in, delete the stored thumbnail for this url.
- if (thumbnail == null) {
- cr.delete(mThumbnailsUriWithProfile, Thumbnails.URL + " == ?", new String[] { uri });
- return;
- }
-
- Bitmap bitmap = thumbnail.getBitmap();
-
- byte[] data = null;
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- if (bitmap.compress(Bitmap.CompressFormat.PNG, 0, stream)) {
- data = stream.toByteArray();
- } else {
- Log.w(LOGTAG, "Favicon compression failed.");
- }
-
- ContentValues values = new ContentValues();
- values.put(Thumbnails.URL, uri);
- values.put(Thumbnails.DATA, data);
-
- Uri thumbnailsUri = mThumbnailsUriWithProfile.buildUpon().
- appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
- cr.update(thumbnailsUri,
- values,
- Thumbnails.URL + " = ?",
- new String[] { uri });
- }
-
- @Override
- @RobocopTarget
- public byte[] getThumbnailForUrl(ContentResolver cr, String uri) {
- final Cursor c = cr.query(mThumbnailsUriWithProfile,
- new String[]{ Thumbnails.DATA },
- Thumbnails.URL + " = ? AND " + Thumbnails.DATA + " IS NOT NULL",
- new String[]{ uri },
- null);
- try {
- if (!c.moveToFirst()) {
- return null;
- }
-
- int thumbnailIndex = c.getColumnIndexOrThrow(Thumbnails.DATA);
-
- return c.getBlob(thumbnailIndex);
- } finally {
- c.close();
- }
-
- }
-
- /**
- * Query for non-null thumbnails matching the provided <code>urls</code>.
- * The returned cursor will have no more than, but possibly fewer than,
- * the requested number of thumbnails.
- *
- * Returns null if the provided list of URLs is empty or null.
- */
- @Override
- public Cursor getThumbnailsForUrls(ContentResolver cr, List<String> urls) {
- final int urlCount = urls.size();
- if (urlCount == 0) {
- return null;
- }
-
- // Don't match against null thumbnails.
- final String selection = Thumbnails.DATA + " IS NOT NULL AND " +
- DBUtils.computeSQLInClause(urlCount, Thumbnails.URL);
- final String[] selectionArgs = urls.toArray(new String[urlCount]);
-
- return cr.query(mThumbnailsUriWithProfile,
- new String[] { Thumbnails.URL, Thumbnails.DATA },
- selection,
- selectionArgs,
- null);
- }
-
- @Override
- @RobocopTarget
- public void removeThumbnails(ContentResolver cr) {
- cr.delete(mThumbnailsUriWithProfile, null, null);
- }
-
- /**
- * Utility method used by AndroidImport for updating existing history record using batch operations.
- *
- * @param cr <code>ContentResolver</code> used for querying information about existing history records.
- * @param operations Collection of operations for queueing record updates.
- * @param url URL used for querying history records to update.
- * @param title Optional new title.
- * @param date New last visited date. Will be used if newer than current last visited date.
- * @param visits Will increment existing visit counts by this number.
- */
- @Override
- public void updateHistoryInBatch(@NonNull ContentResolver cr,
- @NonNull Collection<ContentProviderOperation> operations,
- @NonNull String url, @Nullable String title,
- long date, int visits) {
- final String[] projection = {
- History._ID,
- History.VISITS,
- History.LOCAL_VISITS,
- History.DATE_LAST_VISITED,
- History.LOCAL_DATE_LAST_VISITED
- };
-
- // We need to get the old visit and date aggregates.
- final Cursor cursor = cr.query(withDeleted(mHistoryUriWithProfile),
- projection,
- History.URL + " = ?",
- new String[] { url },
- null);
- if (cursor == null) {
- Log.w(LOGTAG, "Null cursor while querying for old visit and date aggregates");
- return;
- }
-
- try {
- final ContentValues values = new ContentValues();
-
- // Restore deleted record if possible
- values.put(History.IS_DELETED, 0);
-
- if (cursor.moveToFirst()) {
- final int visitsCol = cursor.getColumnIndexOrThrow(History.VISITS);
- final int localVisitsCol = cursor.getColumnIndexOrThrow(History.LOCAL_VISITS);
- final int dateCol = cursor.getColumnIndexOrThrow(History.DATE_LAST_VISITED);
- final int localDateCol = cursor.getColumnIndexOrThrow(History.LOCAL_DATE_LAST_VISITED);
-
- final int oldVisits = cursor.getInt(visitsCol);
- final int oldLocalVisits = cursor.getInt(localVisitsCol);
- final long oldDate = cursor.getLong(dateCol);
- final long oldLocalDate = cursor.getLong(localDateCol);
-
- // NB: This will increment visit counts even if subsequent "insert visits" operations
- // insert no new visits (see insertVisitsFromImportHistoryInBatch).
- // So, we're doing a wrong thing here if user imports history more than once.
- // See Bug 1277330.
- values.put(History.VISITS, oldVisits + visits);
- values.put(History.LOCAL_VISITS, oldLocalVisits + visits);
- // Only update last visited if newer.
- if (date > oldDate) {
- values.put(History.DATE_LAST_VISITED, date);
- }
- if (date > oldLocalDate) {
- values.put(History.LOCAL_DATE_LAST_VISITED, date);
- }
- } else {
- values.put(History.VISITS, visits);
- values.put(History.LOCAL_VISITS, visits);
- values.put(History.DATE_LAST_VISITED, date);
- values.put(History.LOCAL_DATE_LAST_VISITED, date);
- }
- if (title != null) {
- values.put(History.TITLE, title);
- }
- values.put(History.URL, url);
-
- final Uri historyUri = withDeleted(mHistoryUriWithProfile).buildUpon().
- appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
-
- // Update or insert
- final ContentProviderOperation.Builder builder =
- ContentProviderOperation.newUpdate(historyUri);
- builder.withSelection(History.URL + " = ?", new String[] { url });
- builder.withValues(values);
-
- // Queue the operation
- operations.add(builder.build());
- } finally {
- cursor.close();
- }
- }
-
- /**
- * Utility method used by AndroidImport to insert visit data for history records that were just imported.
- * Uses batch operations.
- *
- * @param cr <code>ContentResolver</code> used to query history table and bulkInsert visit records
- * @param operations Collection of operations for queueing inserts
- * @param visitsToSynthesize List of ContentValues describing visit information for each history record:
- * (History URL, LAST DATE VISITED, VISIT COUNT)
- */
- public void insertVisitsFromImportHistoryInBatch(ContentResolver cr,
- Collection<ContentProviderOperation> operations,
- ArrayList<ContentValues> visitsToSynthesize) {
- // If for any reason we fail to obtain history GUID for a tuple we're processing,
- // let's just ignore it. It's possible that the "best-effort" history import
- // did not fully succeed, so we could be missing some of the records.
- int historyGUIDCol = -1;
- for (ContentValues visitsInformation : visitsToSynthesize) {
- final Cursor cursor = cr.query(mHistoryUriWithProfile,
- new String[] {History.GUID},
- History.URL + " = ?",
- new String[] {visitsInformation.getAsString(HISTORY_VISITS_URL)},
- null);
- if (cursor == null) {
- continue;
- }
-
- final String historyGUID;
-
- try {
- if (!cursor.moveToFirst()) {
- continue;
- }
- if (historyGUIDCol == -1) {
- historyGUIDCol = cursor.getColumnIndexOrThrow(History.GUID);
- }
-
- historyGUID = cursor.getString(historyGUIDCol);
- } finally {
- // We "continue" on a null cursor above, so it's safe to act upon it without checking.
- cursor.close();
- }
- if (historyGUID == null) {
- continue;
- }
-
- // This fakes the individual visit records, using last visited date as the starting point.
- for (int i = 0; i < visitsInformation.getAsInteger(HISTORY_VISITS_COUNT); i++) {
- // We rely on database defaults for IS_LOCAL and VISIT_TYPE.
- final ContentValues visitToInsert = new ContentValues();
- visitToInsert.put(BrowserContract.Visits.HISTORY_GUID, historyGUID);
-
- // Visit timestamps are stored in microseconds, while Android Browser visit timestmaps
- // are in milliseconds. This is the conversion point for imports.
- visitToInsert.put(BrowserContract.Visits.DATE_VISITED,
- (visitsInformation.getAsLong(HISTORY_VISITS_DATE) - i) * 1000);
-
- final ContentProviderOperation.Builder builder =
- ContentProviderOperation.newInsert(BrowserContract.Visits.CONTENT_URI);
- builder.withValues(visitToInsert);
-
- // Queue the insert operation
- operations.add(builder.build());
- }
- }
- }
-
- @Override
- public void updateBookmarkInBatch(ContentResolver cr,
- Collection<ContentProviderOperation> operations,
- String url, String title, String guid,
- long parent, long added,
- long modified, long position,
- String keyword, int type) {
- ContentValues values = new ContentValues();
- if (title == null && url != null) {
- title = url;
- }
- if (title != null) {
- values.put(Bookmarks.TITLE, title);
- }
- if (url != null) {
- values.put(Bookmarks.URL, url);
- }
- if (guid != null) {
- values.put(SyncColumns.GUID, guid);
- }
- if (keyword != null) {
- values.put(Bookmarks.KEYWORD, keyword);
- }
- if (added > 0) {
- values.put(SyncColumns.DATE_CREATED, added);
- }
- if (modified > 0) {
- values.put(SyncColumns.DATE_MODIFIED, modified);
- }
- values.put(Bookmarks.POSITION, position);
- // Restore deleted record if possible
- values.put(Bookmarks.IS_DELETED, 0);
-
- // This assumes no "real" folder has a negative ID. Only
- // things like the reading list folder do.
- if (parent < 0) {
- parent = getFolderIdFromGuid(cr, Bookmarks.MOBILE_FOLDER_GUID);
- }
- values.put(Bookmarks.PARENT, parent);
- values.put(Bookmarks.TYPE, type);
-
- Uri bookmarkUri = withDeleted(mBookmarksUriWithProfile).buildUpon().
- appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
- // Update or insert
- ContentProviderOperation.Builder builder =
- ContentProviderOperation.newUpdate(bookmarkUri);
- if (url != null) {
- // Bookmarks are defined by their URL and Folder.
- builder.withSelection(Bookmarks.URL + " = ? AND "
- + Bookmarks.PARENT + " = ?",
- new String[] { url,
- Long.toString(parent)
- });
- } else if (title != null) {
- // Or their title and parent folder. (Folders!)
- builder.withSelection(Bookmarks.TITLE + " = ? AND "
- + Bookmarks.PARENT + " = ?",
- new String[]{ title,
- Long.toString(parent)
- });
- } else if (type == Bookmarks.TYPE_SEPARATOR) {
- // Or their their position (separators)
- builder.withSelection(Bookmarks.POSITION + " = ? AND "
- + Bookmarks.PARENT + " = ?",
- new String[] { Long.toString(position),
- Long.toString(parent)
- });
- } else {
- Log.e(LOGTAG, "Bookmark entry without url or title and not a separator, not added.");
- }
- builder.withValues(values);
-
- // Queue the operation
- operations.add(builder.build());
- }
-
- @Override
- public void pinSite(ContentResolver cr, String url, String title, int position) {
- ContentValues values = new ContentValues();
- final long now = System.currentTimeMillis();
- values.put(Bookmarks.TITLE, title);
- values.put(Bookmarks.URL, url);
- values.put(Bookmarks.PARENT, Bookmarks.FIXED_PINNED_LIST_ID);
- values.put(Bookmarks.DATE_MODIFIED, now);
- values.put(Bookmarks.POSITION, position);
- values.put(Bookmarks.IS_DELETED, 0);
-
- // We do an update-and-replace here without deleting any existing pins for the given URL.
- // That means if the user pins a URL, then edits another thumbnail to use the same URL,
- // we'll end up with two pins for that site. This is the intended behavior, which
- // incidentally saves us a delete query.
- Uri uri = mBookmarksUriWithProfile.buildUpon()
- .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
- cr.update(uri,
- values,
- Bookmarks.POSITION + " = ? AND " +
- Bookmarks.PARENT + " = ?",
- new String[] { Integer.toString(position),
- String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) });
- }
-
- @Override
- public void unpinSite(ContentResolver cr, int position) {
- cr.delete(mBookmarksUriWithProfile,
- Bookmarks.PARENT + " == ? AND " + Bookmarks.POSITION + " = ?",
- new String[] {
- String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID),
- Integer.toString(position)
- });
- }
-
- @Override
- @RobocopTarget
- public Cursor getBookmarkForUrl(ContentResolver cr, String url) {
- Cursor c = cr.query(bookmarksUriWithLimit(1),
- new String[] { Bookmarks._ID,
- Bookmarks.URL,
- Bookmarks.TITLE,
- Bookmarks.KEYWORD },
- Bookmarks.URL + " = ?",
- new String[] { url },
- null);
-
- if (c != null && c.getCount() == 0) {
- c.close();
- c = null;
- }
-
- return c;
- }
-
- @Override
- public Cursor getBookmarksForPartialUrl(ContentResolver cr, String partialUrl) {
- Cursor c = cr.query(mBookmarksUriWithProfile,
- new String[] { Bookmarks.GUID, Bookmarks._ID, Bookmarks.URL },
- Bookmarks.URL + " LIKE '%" + partialUrl + "%'", // TODO: Escaping!
- null,
- null);
-
- if (c != null && c.getCount() == 0) {
- c.close();
- c = null;
- }
-
- return c;
- }
-
- @Override
- public void setSuggestedSites(SuggestedSites suggestedSites) {
- mSuggestedSites = suggestedSites;
- }
-
- @Override
- public SuggestedSites getSuggestedSites() {
- return mSuggestedSites;
- }
-
- @Override
- public boolean hasSuggestedImageUrl(String url) {
- if (mSuggestedSites == null) {
- return false;
- }
- return mSuggestedSites.contains(url);
- }
-
- @Override
- public String getSuggestedImageUrlForUrl(String url) {
- if (mSuggestedSites == null) {
- return null;
- }
- return mSuggestedSites.getImageUrlForUrl(url);
- }
-
- @Override
- public int getSuggestedBackgroundColorForUrl(String url) {
- if (mSuggestedSites == null) {
- return 0;
- }
- final String bgColor = mSuggestedSites.getBackgroundColorForUrl(url);
- if (bgColor != null) {
- return Color.parseColor(bgColor);
- }
-
- return 0;
- }
-
- private static void appendUrlsFromCursor(List<String> urls, Cursor c) {
- if (!c.moveToFirst()) {
- return;
- }
-
- do {
- String url = c.getString(c.getColumnIndex(History.URL));
-
- // Do a simpler check before decoding to avoid parsing
- // all URLs unnecessarily.
- if (StringUtils.isUserEnteredUrl(url)) {
- url = StringUtils.decodeUserEnteredUrl(url);
- }
-
- urls.add(url);
- } while (c.moveToNext());
- }
-
-
- /**
- * Internal CursorLoader that extends the framework CursorLoader in order to measure
- * performance for telemetry purposes.
- */
- private static final class TelemetrisedCursorLoader extends CursorLoader {
- final String mHistogramName;
-
- public TelemetrisedCursorLoader(Context context, Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder,
- final String histogramName) {
- super(context, uri, projection, selection, selectionArgs, sortOrder);
- mHistogramName = histogramName;
- }
-
- @Override
- public Cursor loadInBackground() {
- final long start = SystemClock.uptimeMillis();
-
- final Cursor cursor = super.loadInBackground();
-
- final long end = SystemClock.uptimeMillis();
- final long took = end - start;
-
- Telemetry.addToHistogram(mHistogramName, (int) Math.min(took, Integer.MAX_VALUE));
- return cursor;
- }
- }
-
- public CursorLoader getActivityStreamTopSites(Context context, int limit) {
- final Uri uri = mTopSitesUriWithProfile.buildUpon()
- .appendQueryParameter(BrowserContract.PARAM_LIMIT,
- String.valueOf(limit))
- .appendQueryParameter(BrowserContract.PARAM_TOPSITES_DISABLE_PINNED, Boolean.TRUE.toString())
- .build();
-
- return new TelemetrisedCursorLoader(context,
- uri,
- new String[]{ Combined._ID,
- Combined.URL,
- Combined.TITLE,
- Combined.BOOKMARK_ID,
- Combined.HISTORY_ID },
- null,
- null,
- null,
- TELEMETRY_HISTOGRAM_ACITIVITY_STREAM_TOPSITES);
- }
-
- @Override
- public Cursor getTopSites(ContentResolver cr, int suggestedRangeLimit, int limit) {
- final Uri uri = mTopSitesUriWithProfile.buildUpon()
- .appendQueryParameter(BrowserContract.PARAM_LIMIT,
- String.valueOf(limit))
- .appendQueryParameter(BrowserContract.PARAM_SUGGESTEDSITES_LIMIT,
- String.valueOf(suggestedRangeLimit))
- .build();
-
- Cursor topSitesCursor = cr.query(uri,
- new String[] { Combined._ID,
- Combined.URL,
- Combined.TITLE,
- Combined.BOOKMARK_ID,
- Combined.HISTORY_ID },
- null,
- null,
- null);
-
- // It's possible that we will retrieve fewer sites than are required to fill the top-sites panel - in this case
- // we need to add "blank" tiles. It's much easier to add these here (as opposed to SQL), since we don't care
- // about their ordering (they go after all the other sites), but we do care about their number (and calculating
- // that inside out topsites SQL query would be difficult given the other processing we're already doing there).
- final int blanksRequired = suggestedRangeLimit - topSitesCursor.getCount();
-
- if (blanksRequired <= 0) {
- return topSitesCursor;
- }
-
- MatrixCursor blanksCursor = new MatrixCursor(new String[] {
- TopSites._ID,
- TopSites.BOOKMARK_ID,
- TopSites.HISTORY_ID,
- TopSites.URL,
- TopSites.TITLE,
- TopSites.TYPE});
-
- final MatrixCursor.RowBuilder rb = blanksCursor.newRow();
- rb.add(-1);
- rb.add(-1);
- rb.add(-1);
- rb.add("");
- rb.add("");
- rb.add(TopSites.TYPE_BLANK);
-
- return new MergeCursor(new Cursor[] {topSitesCursor, blanksCursor});
- }
-
- @Override
- public CursorLoader getHighlights(Context context, int limit) {
- final Uri uri = mHighlightsUriWithProfile.buildUpon()
- .appendQueryParameter(BrowserContract.PARAM_LIMIT, String.valueOf(limit))
- .build();
-
- return new CursorLoader(context, uri, null, null, null, null);
- }
-
- @Override
- public void blockActivityStreamSite(ContentResolver cr, String url) {
- final ContentValues values = new ContentValues();
- values.put(ActivityStreamBlocklist.URL, url);
- cr.insert(mActivityStreamBlockedUriWithProfile, values);
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/LocalSearches.java b/mobile/android/base/java/org/mozilla/gecko/db/LocalSearches.java
deleted file mode 100644
index a9a55e51d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalSearches.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.net.Uri;
-
-/**
- * Helper class for dealing with the search provider inside Fennec.
- */
-public class LocalSearches implements Searches {
- private final Uri uriWithProfile;
-
- public LocalSearches(String mProfile) {
- uriWithProfile = DBUtils.appendProfileWithDefault(mProfile, BrowserContract.SearchHistory.CONTENT_URI);
- }
-
- @Override
- public void insert(ContentResolver cr, String query) {
- final ContentValues values = new ContentValues();
- values.put(BrowserContract.SearchHistory.QUERY, query);
- cr.insert(uriWithProfile, values);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/LocalTabsAccessor.java b/mobile/android/base/java/org/mozilla/gecko/db/LocalTabsAccessor.java
deleted file mode 100644
index c7bd9475c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalTabsAccessor.java
+++ /dev/null
@@ -1,320 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.regex.Pattern;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.UIAsyncTask;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.util.Log;
-
-public class LocalTabsAccessor implements TabsAccessor {
- private static final String LOGTAG = "GeckoTabsAccessor";
- private static final long THREE_WEEKS_IN_MILLISECONDS = TimeUnit.MILLISECONDS.convert(21L, TimeUnit.DAYS);
-
- public static final String[] TABS_PROJECTION_COLUMNS = new String[] {
- BrowserContract.Tabs.TITLE,
- BrowserContract.Tabs.URL,
- BrowserContract.Clients.GUID,
- BrowserContract.Clients.NAME,
- BrowserContract.Tabs.LAST_USED,
- BrowserContract.Clients.LAST_MODIFIED,
- BrowserContract.Clients.DEVICE_TYPE,
- };
-
- public static final String[] CLIENTS_PROJECTION_COLUMNS = new String[] {
- BrowserContract.Clients.GUID,
- BrowserContract.Clients.NAME,
- BrowserContract.Clients.LAST_MODIFIED,
- BrowserContract.Clients.DEVICE_TYPE
- };
-
- private static final String REMOTE_CLIENTS_SELECTION = BrowserContract.Clients.GUID + " IS NOT NULL";
- private static final String LOCAL_TABS_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS NULL";
- private static final String REMOTE_TABS_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS NOT NULL";
- private static final String REMOTE_TABS_SELECTION_CLIENT_RECENCY = REMOTE_TABS_SELECTION +
- " AND " + BrowserContract.Clients.LAST_MODIFIED + " > ?";
-
- private static final String REMOTE_TABS_SORT_ORDER =
- // Most recently synced clients first.
- BrowserContract.Clients.LAST_MODIFIED + " DESC, " +
- // If two clients somehow had the same last modified time, this will
- // group them (arbitrarily).
- BrowserContract.Clients.GUID + " DESC, " +
- // Within a single client, most recently used tabs first.
- BrowserContract.Tabs.LAST_USED + " DESC";
-
- private static final String LOCAL_CLIENT_SELECTION = BrowserContract.Clients.GUID + " IS NULL";
-
- private static final Pattern FILTERED_URL_PATTERN = Pattern.compile("^(about|chrome|wyciwyg|file):");
-
- private final Uri clientsRecencyUriWithProfile;
- private final Uri tabsUriWithProfile;
- private final Uri clientsUriWithProfile;
-
- public LocalTabsAccessor(String profileName) {
- tabsUriWithProfile = DBUtils.appendProfileWithDefault(profileName, BrowserContract.Tabs.CONTENT_URI);
- clientsUriWithProfile = DBUtils.appendProfileWithDefault(profileName, BrowserContract.Clients.CONTENT_URI);
- clientsRecencyUriWithProfile = DBUtils.appendProfileWithDefault(profileName, BrowserContract.Clients.CONTENT_RECENCY_URI);
- }
-
- /**
- * Extracts a List of just RemoteClients from a cursor.
- * The supplied cursor should be grouped by guid and sorted by most recently used.
- */
- @Override
- public List<RemoteClient> getClientsWithoutTabsByRecencyFromCursor(Cursor cursor) {
- final ArrayList<RemoteClient> clients = new ArrayList<>(cursor.getCount());
-
- final int originalPosition = cursor.getPosition();
- try {
- if (!cursor.moveToFirst()) {
- return clients;
- }
-
- final int clientGuidIndex = cursor.getColumnIndex(BrowserContract.Clients.GUID);
- final int clientNameIndex = cursor.getColumnIndex(BrowserContract.Clients.NAME);
- final int clientLastModifiedIndex = cursor.getColumnIndex(BrowserContract.Clients.LAST_MODIFIED);
- final int clientDeviceTypeIndex = cursor.getColumnIndex(BrowserContract.Clients.DEVICE_TYPE);
-
- while (!cursor.isAfterLast()) {
- final String clientGuid = cursor.getString(clientGuidIndex);
- final String clientName = cursor.getString(clientNameIndex);
- final String deviceType = cursor.getString(clientDeviceTypeIndex);
- final long lastModified = cursor.getLong(clientLastModifiedIndex);
-
- clients.add(new RemoteClient(clientGuid, clientName, lastModified, deviceType));
-
- cursor.moveToNext();
- }
- } finally {
- cursor.moveToPosition(originalPosition);
- }
- return clients;
- }
-
- /**
- * Extract client and tab records from a cursor.
- * <p>
- * The position of the cursor is moved to before the first record before
- * reading. The cursor is advanced until there are no more records to be
- * read. The position of the cursor is restored before returning.
- *
- * @param cursor
- * to extract records from. The records should already be grouped
- * by client GUID.
- * @return list of clients, each containing list of tabs.
- */
- @Override
- public List<RemoteClient> getClientsFromCursor(final Cursor cursor) {
- final ArrayList<RemoteClient> clients = new ArrayList<RemoteClient>();
-
- final int originalPosition = cursor.getPosition();
- try {
- if (!cursor.moveToFirst()) {
- return clients;
- }
-
- final int tabTitleIndex = cursor.getColumnIndex(BrowserContract.Tabs.TITLE);
- final int tabUrlIndex = cursor.getColumnIndex(BrowserContract.Tabs.URL);
- final int tabLastUsedIndex = cursor.getColumnIndex(BrowserContract.Tabs.LAST_USED);
- final int clientGuidIndex = cursor.getColumnIndex(BrowserContract.Clients.GUID);
- final int clientNameIndex = cursor.getColumnIndex(BrowserContract.Clients.NAME);
- final int clientLastModifiedIndex = cursor.getColumnIndex(BrowserContract.Clients.LAST_MODIFIED);
- final int clientDeviceTypeIndex = cursor.getColumnIndex(BrowserContract.Clients.DEVICE_TYPE);
-
- // A walking partition, chunking by client GUID. We assume the
- // cursor records are already grouped by client GUID; see the query
- // sort order.
- RemoteClient lastClient = null;
- while (!cursor.isAfterLast()) {
- final String clientGuid = cursor.getString(clientGuidIndex);
- if (lastClient == null || !TextUtils.equals(lastClient.guid, clientGuid)) {
- final String clientName = cursor.getString(clientNameIndex);
- final long lastModified = cursor.getLong(clientLastModifiedIndex);
- final String deviceType = cursor.getString(clientDeviceTypeIndex);
- lastClient = new RemoteClient(clientGuid, clientName, lastModified, deviceType);
- clients.add(lastClient);
- }
-
- final String tabTitle = cursor.getString(tabTitleIndex);
- final String tabUrl = cursor.getString(tabUrlIndex);
- final long tabLastUsed = cursor.getLong(tabLastUsedIndex);
- lastClient.tabs.add(new RemoteTab(tabTitle, tabUrl, tabLastUsed));
-
- cursor.moveToNext();
- }
- } finally {
- cursor.moveToPosition(originalPosition);
- }
-
- return clients;
- }
-
- @Override
- public Cursor getRemoteClientsByRecencyCursor(Context context) {
- final Uri uri = clientsRecencyUriWithProfile;
- return context.getContentResolver().query(uri, CLIENTS_PROJECTION_COLUMNS,
- REMOTE_CLIENTS_SELECTION, null, null);
- }
-
- @Override
- public Cursor getRemoteTabsCursor(Context context) {
- return getRemoteTabsCursor(context, -1);
- }
-
- @Override
- public Cursor getRemoteTabsCursor(Context context, int limit) {
- Uri uri = tabsUriWithProfile;
-
- if (limit > 0) {
- uri = uri.buildUpon()
- .appendQueryParameter(BrowserContract.PARAM_LIMIT, String.valueOf(limit))
- .build();
- }
-
- final String threeWeeksAgoTimestampMillis = Long.valueOf(
- System.currentTimeMillis() - THREE_WEEKS_IN_MILLISECONDS).toString();
- return context.getContentResolver().query(uri,
- TABS_PROJECTION_COLUMNS,
- REMOTE_TABS_SELECTION_CLIENT_RECENCY,
- new String[] {threeWeeksAgoTimestampMillis},
- REMOTE_TABS_SORT_ORDER);
- }
-
- // This method returns all tabs from all remote clients,
- // ordered by most recent client first, most recent tab first
- @Override
- public void getTabs(final Context context, final OnQueryTabsCompleteListener listener) {
- getTabs(context, 0, listener);
- }
-
- // This method returns limited number of tabs from all remote clients,
- // ordered by most recent client first, most recent tab first
- @Override
- public void getTabs(final Context context, final int limit, final OnQueryTabsCompleteListener listener) {
- // If there is no listener, no point in doing work.
- if (listener == null)
- return;
-
- (new UIAsyncTask.WithoutParams<List<RemoteClient>>(ThreadUtils.getBackgroundHandler()) {
- @Override
- protected List<RemoteClient> doInBackground() {
- final Cursor cursor = getRemoteTabsCursor(context, limit);
- if (cursor == null)
- return null;
-
- try {
- return Collections.unmodifiableList(getClientsFromCursor(cursor));
- } finally {
- cursor.close();
- }
- }
-
- @Override
- protected void onPostExecute(List<RemoteClient> clients) {
- listener.onQueryTabsComplete(clients);
- }
- }).execute();
- }
-
- // Updates the modified time of the local client with the current time.
- private void updateLocalClient(final ContentResolver cr) {
- ContentValues values = new ContentValues();
- values.put(BrowserContract.Clients.LAST_MODIFIED, System.currentTimeMillis());
-
- cr.update(clientsUriWithProfile, values, LOCAL_CLIENT_SELECTION, null);
- }
-
- // Deletes all local tabs.
- private void deleteLocalTabs(final ContentResolver cr) {
- cr.delete(tabsUriWithProfile, LOCAL_TABS_SELECTION, null);
- }
-
- /**
- * Tabs are positioned in the DB in the same order that they appear in the tabs param.
- * - URL should never empty or null. Skip this tab if there's no URL.
- * - TITLE should always a string, either a page title or empty.
- * - LAST_USED should always be numeric.
- * - FAVICON should be a URL or null.
- * - HISTORY should be serialized JSON array of URLs.
- * - POSITION should always be numeric.
- * - CLIENT_GUID should always be null to represent the local client.
- */
- private void insertLocalTabs(final ContentResolver cr, final Iterable<Tab> tabs) {
- // Reuse this for serializing individual history URLs as JSON.
- JSONArray history = new JSONArray();
- ArrayList<ContentValues> valuesToInsert = new ArrayList<ContentValues>();
-
- int position = 0;
- for (Tab tab : tabs) {
- // Skip this tab if it has a null URL or is in private browsing mode, or is a filtered URL.
- String url = tab.getURL();
- if (url == null || tab.isPrivate() || isFilteredURL(url))
- continue;
-
- ContentValues values = new ContentValues();
- values.put(BrowserContract.Tabs.URL, url);
- values.put(BrowserContract.Tabs.TITLE, tab.getTitle());
- values.put(BrowserContract.Tabs.LAST_USED, tab.getLastUsed());
-
- String favicon = tab.getFaviconURL();
- if (favicon != null)
- values.put(BrowserContract.Tabs.FAVICON, favicon);
- else
- values.putNull(BrowserContract.Tabs.FAVICON);
-
- // We don't have access to session history in Java, so for now, we'll
- // just use a JSONArray that holds most recent history item.
- try {
- history.put(0, tab.getURL());
- values.put(BrowserContract.Tabs.HISTORY, history.toString());
- } catch (JSONException e) {
- Log.w(LOGTAG, "JSONException adding URL to tab history array.", e);
- }
-
- values.put(BrowserContract.Tabs.POSITION, position++);
-
- // A null client guid corresponds to the local client.
- values.putNull(BrowserContract.Tabs.CLIENT_GUID);
-
- valuesToInsert.add(values);
- }
-
- ContentValues[] valuesToInsertArray = valuesToInsert.toArray(new ContentValues[valuesToInsert.size()]);
- cr.bulkInsert(tabsUriWithProfile, valuesToInsertArray);
- }
-
- // Deletes all local tabs and replaces them with a new list of tabs.
- @Override
- public synchronized void persistLocalTabs(final ContentResolver cr, final Iterable<Tab> tabs) {
- deleteLocalTabs(cr);
- insertLocalTabs(cr, tabs);
- updateLocalClient(cr);
- }
-
- /**
- * Matches the supplied URL string against the set of URLs to filter.
- *
- * @return true if the supplied URL should be skipped; false otherwise.
- */
- private boolean isFilteredURL(String url) {
- return FILTERED_URL_PATTERN.matcher(url).lookingAt();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/LocalURLMetadata.java b/mobile/android/base/java/org/mozilla/gecko/db/LocalURLMetadata.java
deleted file mode 100644
index 7f2c4a736..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalURLMetadata.java
+++ /dev/null
@@ -1,240 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
-package org.mozilla.gecko.db;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.util.Log;
-import android.util.LruCache;
-
-// Holds metadata info about URLs. Supports some helper functions for getting back a HashMap of key value data.
-public class LocalURLMetadata implements URLMetadata {
- private static final String LOGTAG = "GeckoURLMetadata";
- private final Uri uriWithProfile;
-
- public LocalURLMetadata(String mProfile) {
- uriWithProfile = DBUtils.appendProfileWithDefault(mProfile, URLMetadataTable.CONTENT_URI);
- }
-
- // A list of columns in the table. It's used to simplify some loops for reading/writing data.
- private static final Set<String> COLUMNS;
- static {
- final HashSet<String> tempModel = new HashSet<>(4);
- tempModel.add(URLMetadataTable.URL_COLUMN);
- tempModel.add(URLMetadataTable.TILE_IMAGE_URL_COLUMN);
- tempModel.add(URLMetadataTable.TILE_COLOR_COLUMN);
- tempModel.add(URLMetadataTable.TOUCH_ICON_COLUMN);
- COLUMNS = Collections.unmodifiableSet(tempModel);
- }
-
- // Store a cache of recent results. This number is chosen to match the max number of tiles on about:home
- private static final int CACHE_SIZE = 9;
- // Note: Members of this cache are unmodifiable.
- private final LruCache<String, Map<String, Object>> cache = new LruCache<String, Map<String, Object>>(CACHE_SIZE);
-
- /**
- * Converts a JSON object into a unmodifiable Map of known metadata properties.
- * Will throw away any properties that aren't stored in the database.
- *
- * Incoming data can include a list like: {touchIconList:{56:"http://x.com/56.png", 76:"http://x.com/76.png"}}.
- * This will then be filtered to find the most appropriate touchIcon, i.e. the closest icon size that is larger
- * than (or equal to) the preferred homescreen launcher icon size, which is then stored in the "touchIcon" property.
- */
- @Override
- public Map<String, Object> fromJSON(JSONObject obj) {
- Map<String, Object> data = new HashMap<String, Object>();
-
- for (String key : COLUMNS) {
- if (obj.has(key)) {
- data.put(key, obj.optString(key));
- }
- }
-
-
- try {
- JSONObject icons;
- if (obj.has("touchIconList") &&
- (icons = obj.getJSONObject("touchIconList")).length() > 0) {
- int preferredSize = GeckoAppShell.getPreferredIconSize();
-
- Iterator<String> keys = icons.keys();
-
- ArrayList<Integer> sizes = new ArrayList<Integer>(icons.length());
- while (keys.hasNext()) {
- sizes.add(new Integer(keys.next()));
- }
-
- final int bestSize = LoadFaviconResult.selectBestSizeFromList(sizes, preferredSize);
- final String iconURL = icons.getString(Integer.toString(bestSize));
-
- data.put(URLMetadataTable.TOUCH_ICON_COLUMN, iconURL);
- }
- } catch (JSONException e) {
- Log.w(LOGTAG, "Exception processing touchIconList for LocalURLMetadata; ignoring.", e);
- }
-
- return Collections.unmodifiableMap(data);
- }
-
- /**
- * Converts a Cursor into a unmodifiable Map of known metadata properties.
- * Will throw away any properties that aren't stored in the database.
- * Will also not iterate through multiple rows in the cursor.
- */
- private Map<String, Object> fromCursor(Cursor c) {
- Map<String, Object> data = new HashMap<String, Object>();
-
- String[] columns = c.getColumnNames();
- for (String column : columns) {
- if (COLUMNS.contains(column)) {
- try {
- data.put(column, c.getString(c.getColumnIndexOrThrow(column)));
- } catch (Exception ex) {
- Log.i(LOGTAG, "Error getting data for " + column, ex);
- }
- }
- }
-
- return Collections.unmodifiableMap(data);
- }
-
- /**
- * Returns an unmodifiable Map of url->Metadata (i.e. A second HashMap) for a list of urls.
- * Must not be called from UI or Gecko threads.
- */
- @Override
- public Map<String, Map<String, Object>> getForURLs(final ContentResolver cr,
- final Collection<String> urls,
- final List<String> requestedColumns) {
- ThreadUtils.assertNotOnUiThread();
- ThreadUtils.assertNotOnGeckoThread();
-
- final Map<String, Map<String, Object>> data = new HashMap<String, Map<String, Object>>();
-
- // Nothing to query for
- if (urls.isEmpty() || requestedColumns.isEmpty()) {
- Log.e(LOGTAG, "Queried metadata for nothing");
- return data;
- }
-
- // Search the cache for any of these urls
- List<String> urlsToQuery = new ArrayList<String>();
- for (String url : urls) {
- final Map<String, Object> hit = cache.get(url);
- if (hit != null) {
- // Cache hit: we've found the URL in the cache, however we may not have cached the desired columns
- // for that URL. Hence we need to check whether our cache hit contains those columns, and directly
- // retrieve the desired data if not. (E.g. the top sites panel retrieves the tile, and tilecolor. If
- // we later try to retrieve the touchIcon for a top-site the cache hit will only point to
- // tile+tilecolor, and not the required touchIcon. In this case we don't want to use the cache.)
- boolean useCache = true;
- for (String c: requestedColumns) {
- if (!hit.containsKey(c)) {
- useCache = false;
- }
- }
- if (useCache) {
- data.put(url, hit);
- } else {
- urlsToQuery.add(url);
- }
- } else {
- urlsToQuery.add(url);
- }
- }
-
- // If everything was in the cache, we're done!
- if (urlsToQuery.size() == 0) {
- return Collections.unmodifiableMap(data);
- }
-
- final String selection = DBUtils.computeSQLInClause(urlsToQuery.size(), URLMetadataTable.URL_COLUMN);
- List<String> columns = requestedColumns;
- // We need the url to build our final HashMap, so we force it to be included in the query.
- if (!columns.contains(URLMetadataTable.URL_COLUMN)) {
- // The requestedColumns may be immutable (e.g. if the caller used Collections.singletonList), hence
- // we have to create a copy.
- columns = new ArrayList<String>(columns);
- columns.add(URLMetadataTable.URL_COLUMN);
- }
-
- final Cursor cursor = cr.query(uriWithProfile,
- columns.toArray(new String[columns.size()]), // columns,
- selection, // selection
- urlsToQuery.toArray(new String[urlsToQuery.size()]), // selectionargs
- null);
- try {
- if (!cursor.moveToFirst()) {
- return Collections.unmodifiableMap(data);
- }
-
- do {
- final Map<String, Object> metadata = fromCursor(cursor);
- final String url = cursor.getString(cursor.getColumnIndexOrThrow(URLMetadataTable.URL_COLUMN));
-
- data.put(url, metadata);
- cache.put(url, metadata);
- } while (cursor.moveToNext());
-
- } finally {
- cursor.close();
- }
-
- return Collections.unmodifiableMap(data);
- }
-
- /**
- * Saves a HashMap of metadata into the database. Will iterate through columns
- * in the Database and only save rows with matching keys in the HashMap.
- * Must not be called from UI or Gecko threads.
- */
- @Override
- public void save(final ContentResolver cr, final Map<String, Object> data) {
- ThreadUtils.assertNotOnUiThread();
- ThreadUtils.assertNotOnGeckoThread();
-
- try {
- ContentValues values = new ContentValues();
-
- for (String key : COLUMNS) {
- if (data.containsKey(key)) {
- values.put(key, (String) data.get(key));
- }
- }
-
- if (values.size() == 0) {
- return;
- }
-
- Uri uri = uriWithProfile.buildUpon()
- .appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true")
- .build();
- cr.update(uri, values, URLMetadataTable.URL_COLUMN + "=?", new String[] {
- (String) data.get(URLMetadataTable.URL_COLUMN)
- });
- } catch (Exception ex) {
- Log.e(LOGTAG, "error saving", ex);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/LocalUrlAnnotations.java b/mobile/android/base/java/org/mozilla/gecko/db/LocalUrlAnnotations.java
deleted file mode 100644
index 9df41a169..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalUrlAnnotations.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.util.Log;
-
-import org.json.JSONException;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.db.BrowserContract.UrlAnnotations.Key;
-import org.mozilla.gecko.feeds.subscriptions.FeedSubscription;
-
-public class LocalUrlAnnotations implements UrlAnnotations {
- private static final String LOGTAG = "LocalUrlAnnotations";
-
- private Uri urlAnnotationsTableWithProfile;
-
- public LocalUrlAnnotations(final String profile) {
- urlAnnotationsTableWithProfile = DBUtils.appendProfile(profile, BrowserContract.UrlAnnotations.CONTENT_URI);
- }
-
- /**
- * Get all feed subscriptions.
- */
- @Override
- public Cursor getFeedSubscriptions(ContentResolver cr) {
- return queryByKey(cr,
- Key.FEED_SUBSCRIPTION,
- new String[] { BrowserContract.UrlAnnotations.URL, BrowserContract.UrlAnnotations.VALUE },
- null);
- }
-
- /**
- * Insert mapping from website URL to URL of the feed.
- */
- @Override
- public void insertFeedUrl(ContentResolver cr, String originUrl, String feedUrl) {
- insertAnnotation(cr, originUrl, Key.FEED, feedUrl);
- }
-
- @Override
- public boolean hasAcceptedOrDeclinedHomeScreenShortcut(ContentResolver cr, String url) {
- return hasResultsForSelection(cr,
- BrowserContract.UrlAnnotations.URL + " = ?",
- new String[]{url});
- }
-
- @Override
- public void insertHomeScreenShortcut(ContentResolver cr, String url, boolean hasCreatedShortCut) {
- insertAnnotation(cr, url, Key.HOME_SCREEN_SHORTCUT, String.valueOf(hasCreatedShortCut));
- }
-
- /**
- * Returns true if there's a mapping from the given website URL to a feed URL. False otherwise.
- */
- @Override
- public boolean hasFeedUrlForWebsite(ContentResolver cr, String websiteUrl) {
- return hasResultsForSelection(cr,
- BrowserContract.UrlAnnotations.URL + " = ? AND " + BrowserContract.UrlAnnotations.KEY + " = ?",
- new String[]{websiteUrl, Key.FEED.getDbValue()});
- }
-
- /**
- * Returns true if there's a website URL with this feed URL. False otherwise.
- */
- @Override
- public boolean hasWebsiteForFeedUrl(ContentResolver cr, String feedUrl) {
- return hasResultsForSelection(cr,
- BrowserContract.UrlAnnotations.VALUE + " = ? AND " + BrowserContract.UrlAnnotations.KEY + " = ?",
- new String[]{feedUrl, Key.FEED.getDbValue()});
- }
-
- /**
- * Delete the feed URL mapping for this website URL.
- */
- @Override
- public void deleteFeedUrl(ContentResolver cr, String websiteUrl) {
- deleteAnnotation(cr, websiteUrl, Key.FEED);
- }
-
- /**
- * Get website URLs that are mapped to the given feed URL.
- */
- @Override
- public Cursor getWebsitesWithFeedUrl(ContentResolver cr) {
- return cr.query(urlAnnotationsTableWithProfile,
- new String[] { BrowserContract.UrlAnnotations.URL },
- BrowserContract.UrlAnnotations.KEY + " = ?",
- new String[] { Key.FEED.getDbValue() },
- null);
- }
-
- /**
- * Returns true if there's a subscription for this feed URL. False otherwise.
- */
- @Override
- public boolean hasFeedSubscription(ContentResolver cr, String feedUrl) {
- return hasResultsForSelection(cr,
- BrowserContract.UrlAnnotations.URL + " = ? AND " + BrowserContract.UrlAnnotations.KEY + " = ?",
- new String[]{feedUrl, Key.FEED_SUBSCRIPTION.getDbValue()});
- }
-
- /**
- * Insert the given feed subscription (Mapping from feed URL to the subscription object).
- */
- @Override
- public void insertFeedSubscription(ContentResolver cr, FeedSubscription subscription) {
- try {
- insertAnnotation(cr, subscription.getFeedUrl(), Key.FEED_SUBSCRIPTION, subscription.toJSON().toString());
- } catch (JSONException e) {
- Log.w(LOGTAG, "Could not serialize subscription");
- }
- }
-
- /**
- * Update the feed subscription with new values.
- */
- @Override
- public void updateFeedSubscription(ContentResolver cr, FeedSubscription subscription) {
- try {
- updateAnnotation(cr, subscription.getFeedUrl(), Key.FEED_SUBSCRIPTION, subscription.toJSON().toString());
- } catch (JSONException e) {
- Log.w(LOGTAG, "Could not serialize subscription");
- }
- }
-
- /**
- * Delete the subscription for the feed URL.
- */
- @Override
- public void deleteFeedSubscription(ContentResolver cr, FeedSubscription subscription) {
- deleteAnnotation(cr, subscription.getFeedUrl(), Key.FEED_SUBSCRIPTION);
- }
-
- private int deleteAnnotation(final ContentResolver cr, final String url, final Key key) {
- return cr.delete(urlAnnotationsTableWithProfile,
- BrowserContract.UrlAnnotations.KEY + " = ? AND " + BrowserContract.UrlAnnotations.URL + " = ?",
- new String[] { key.getDbValue(), url });
- }
-
- private int updateAnnotation(final ContentResolver cr, final String url, final Key key, final String value) {
- ContentValues values = new ContentValues();
- values.put(BrowserContract.UrlAnnotations.VALUE, value);
- values.put(BrowserContract.UrlAnnotations.DATE_MODIFIED, System.currentTimeMillis());
-
- return cr.update(urlAnnotationsTableWithProfile,
- values,
- BrowserContract.UrlAnnotations.KEY + " = ? AND " + BrowserContract.UrlAnnotations.URL + " = ?",
- new String[]{key.getDbValue(), url});
- }
-
- private void insertAnnotation(final ContentResolver cr, final String url, final Key key, final String value) {
- insertAnnotation(cr, url, key.getDbValue(), value);
- }
-
- @RobocopTarget
- @Override
- public void insertAnnotation(final ContentResolver cr, final String url, final String key, final String value) {
- final long creationTime = System.currentTimeMillis();
- final ContentValues values = new ContentValues(5);
- values.put(BrowserContract.UrlAnnotations.URL, url);
- values.put(BrowserContract.UrlAnnotations.KEY, key);
- values.put(BrowserContract.UrlAnnotations.VALUE, value);
- values.put(BrowserContract.UrlAnnotations.DATE_CREATED, creationTime);
- values.put(BrowserContract.UrlAnnotations.DATE_MODIFIED, creationTime);
- cr.insert(urlAnnotationsTableWithProfile, values);
- }
-
- /**
- * @return true if the table contains rows for the given selection.
- */
- private boolean hasResultsForSelection(ContentResolver cr, String selection, String[] selectionArgs) {
- Cursor cursor = cr.query(urlAnnotationsTableWithProfile,
- new String[] { BrowserContract.UrlAnnotations._ID },
- selection,
- selectionArgs,
- null);
- if (cursor == null) {
- return false;
- }
-
- try {
- return cursor.getCount() > 0;
- } finally {
- cursor.close();
- }
- }
-
- private Cursor queryByKey(final ContentResolver cr, @NonNull final Key key, @Nullable final String[] projections,
- @Nullable final String sortOrder) {
- return cr.query(urlAnnotationsTableWithProfile,
- projections,
- BrowserContract.UrlAnnotations.KEY + " = ?", new String[] { key.getDbValue() },
- sortOrder);
- }
-
- @Override
- public Cursor getScreenshots(ContentResolver cr) {
- return queryByKey(cr,
- Key.SCREENSHOT,
- new String[] {
- BrowserContract.UrlAnnotations._ID,
- BrowserContract.UrlAnnotations.URL,
- BrowserContract.UrlAnnotations.KEY,
- BrowserContract.UrlAnnotations.VALUE,
- BrowserContract.UrlAnnotations.DATE_CREATED,
- },
- BrowserContract.UrlAnnotations.DATE_CREATED + " DESC");
- }
-
- public void insertScreenshot(final ContentResolver cr, final String pageUrl, final String screenshotPath) {
- insertAnnotation(cr, pageUrl, Key.SCREENSHOT.getDbValue(), screenshotPath);
- }
-
- @Override
- public void insertReaderViewUrl(final ContentResolver cr, final String pageUrl) {
- insertAnnotation(cr, pageUrl, Key.READER_VIEW.getDbValue(), BrowserContract.UrlAnnotations.READER_VIEW_SAVED_VALUE);
- }
-
- @Override
- public void deleteReaderViewUrl(ContentResolver cr, String pageURL) {
- deleteAnnotation(cr, pageURL, Key.READER_VIEW);
- }
-
- public int getAnnotationCount(ContentResolver cr, Key key) {
- final String countColumnname = "count";
- final Cursor c = queryByKey(cr,
- key,
- new String[] {
- "COUNT(*) AS " + countColumnname
- },
- null);
-
- try {
- if (c != null && c.moveToFirst()) {
- return c.getInt(c.getColumnIndexOrThrow(countColumnname));
- } else {
- return 0;
- }
- } finally {
- if (c != null) {
- c.close();
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/LoginsProvider.java b/mobile/android/base/java/org/mozilla/gecko/db/LoginsProvider.java
deleted file mode 100644
index d2d504851..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/LoginsProvider.java
+++ /dev/null
@@ -1,520 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.MatrixCursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.net.Uri;
-import android.support.annotation.NonNull;
-import android.text.TextUtils;
-import android.util.Base64;
-
-import org.mozilla.gecko.db.BrowserContract.DeletedLogins;
-import org.mozilla.gecko.db.BrowserContract.Logins;
-import org.mozilla.gecko.db.BrowserContract.LoginsDisabledHosts;
-import org.mozilla.gecko.sync.Utils;
-
-import java.io.UnsupportedEncodingException;
-import java.security.GeneralSecurityException;
-import java.util.HashMap;
-
-import javax.crypto.Cipher;
-import javax.crypto.NullCipher;
-
-import static org.mozilla.gecko.db.BrowserContract.DeletedLogins.TABLE_DELETED_LOGINS;
-import static org.mozilla.gecko.db.BrowserContract.Logins.TABLE_LOGINS;
-import static org.mozilla.gecko.db.BrowserContract.LoginsDisabledHosts.TABLE_DISABLED_HOSTS;
-
-public class LoginsProvider extends SharedBrowserDatabaseProvider {
-
- private static final int LOGINS = 100;
- private static final int LOGINS_ID = 101;
- private static final int DELETED_LOGINS = 102;
- private static final int DELETED_LOGINS_ID = 103;
- private static final int DISABLED_HOSTS = 104;
- private static final int DISABLED_HOSTS_HOSTNAME = 105;
- private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
-
- private static final HashMap<String, String> LOGIN_PROJECTION_MAP;
- private static final HashMap<String, String> DELETED_LOGIN_PROJECTION_MAP;
- private static final HashMap<String, String> DISABLED_HOSTS_PROJECTION_MAP;
-
- private static final String DEFAULT_LOGINS_SORT_ORDER = Logins.HOSTNAME + " ASC";
- private static final String DEFAULT_DELETED_LOGINS_SORT_ORDER = DeletedLogins.TIME_DELETED + " ASC";
- private static final String DEFAULT_DISABLED_HOSTS_SORT_ORDER = LoginsDisabledHosts.HOSTNAME + " ASC";
- private static final String WHERE_GUID_IS_NULL = DeletedLogins.GUID + " IS NULL";
- private static final String WHERE_GUID_IS_VALUE = DeletedLogins.GUID + " = ?";
-
- protected static final String INDEX_LOGINS_HOSTNAME = "login_hostname_index";
- protected static final String INDEX_LOGINS_HOSTNAME_FORM_SUBMIT_URL = "login_hostname_formSubmitURL_index";
- protected static final String INDEX_LOGINS_HOSTNAME_HTTP_REALM = "login_hostname_httpRealm_index";
-
- static {
- URI_MATCHER.addURI(BrowserContract.LOGINS_AUTHORITY, "logins", LOGINS);
- URI_MATCHER.addURI(BrowserContract.LOGINS_AUTHORITY, "logins/#", LOGINS_ID);
- URI_MATCHER.addURI(BrowserContract.LOGINS_AUTHORITY, "deleted-logins", DELETED_LOGINS);
- URI_MATCHER.addURI(BrowserContract.LOGINS_AUTHORITY, "deleted-logins/#", DELETED_LOGINS_ID);
- URI_MATCHER.addURI(BrowserContract.LOGINS_AUTHORITY, "logins-disabled-hosts", DISABLED_HOSTS);
- URI_MATCHER.addURI(BrowserContract.LOGINS_AUTHORITY, "logins-disabled-hosts/hostname/*", DISABLED_HOSTS_HOSTNAME);
-
- LOGIN_PROJECTION_MAP = new HashMap<>();
- LOGIN_PROJECTION_MAP.put(Logins._ID, Logins._ID);
- LOGIN_PROJECTION_MAP.put(Logins.HOSTNAME, Logins.HOSTNAME);
- LOGIN_PROJECTION_MAP.put(Logins.HTTP_REALM, Logins.HTTP_REALM);
- LOGIN_PROJECTION_MAP.put(Logins.FORM_SUBMIT_URL, Logins.FORM_SUBMIT_URL);
- LOGIN_PROJECTION_MAP.put(Logins.USERNAME_FIELD, Logins.USERNAME_FIELD);
- LOGIN_PROJECTION_MAP.put(Logins.PASSWORD_FIELD, Logins.PASSWORD_FIELD);
- LOGIN_PROJECTION_MAP.put(Logins.ENCRYPTED_USERNAME, Logins.ENCRYPTED_USERNAME);
- LOGIN_PROJECTION_MAP.put(Logins.ENCRYPTED_PASSWORD, Logins.ENCRYPTED_PASSWORD);
- LOGIN_PROJECTION_MAP.put(Logins.GUID, Logins.GUID);
- LOGIN_PROJECTION_MAP.put(Logins.ENC_TYPE, Logins.ENC_TYPE);
- LOGIN_PROJECTION_MAP.put(Logins.TIME_CREATED, Logins.TIME_CREATED);
- LOGIN_PROJECTION_MAP.put(Logins.TIME_LAST_USED, Logins.TIME_LAST_USED);
- LOGIN_PROJECTION_MAP.put(Logins.TIME_PASSWORD_CHANGED, Logins.TIME_PASSWORD_CHANGED);
- LOGIN_PROJECTION_MAP.put(Logins.TIMES_USED, Logins.TIMES_USED);
-
- DELETED_LOGIN_PROJECTION_MAP = new HashMap<>();
- DELETED_LOGIN_PROJECTION_MAP.put(DeletedLogins._ID, DeletedLogins._ID);
- DELETED_LOGIN_PROJECTION_MAP.put(DeletedLogins.GUID, DeletedLogins.GUID);
- DELETED_LOGIN_PROJECTION_MAP.put(DeletedLogins.TIME_DELETED, DeletedLogins.TIME_DELETED);
-
- DISABLED_HOSTS_PROJECTION_MAP = new HashMap<>();
- DISABLED_HOSTS_PROJECTION_MAP.put(LoginsDisabledHosts._ID, LoginsDisabledHosts._ID);
- DISABLED_HOSTS_PROJECTION_MAP.put(LoginsDisabledHosts.HOSTNAME, LoginsDisabledHosts.HOSTNAME);
- }
-
- private static String projectColumn(String table, String column) {
- return table + "." + column;
- }
-
- private static String selectColumn(String table, String column) {
- return projectColumn(table, column) + " = ?";
- }
-
- @Override
- protected Uri insertInTransaction(Uri uri, ContentValues values) {
- trace("Calling insert in transaction on URI: " + uri);
-
- final int match = URI_MATCHER.match(uri);
- final SQLiteDatabase db = getWritableDatabase(uri);
- final long id;
- String guid;
-
- setupDefaultValues(values, uri);
- switch (match) {
- case LOGINS:
- removeDeletedLoginsByGUIDInTransaction(values, db);
- // Encrypt sensitive data.
- encryptContentValueFields(values);
- guid = values.getAsString(Logins.GUID);
- debug("Inserting login in database with GUID: " + guid);
- id = db.insertOrThrow(TABLE_LOGINS, Logins.GUID, values);
- break;
-
- case DELETED_LOGINS:
- guid = values.getAsString(DeletedLogins.GUID);
- debug("Inserting deleted-login in database with GUID: " + guid);
- id = db.insertOrThrow(TABLE_DELETED_LOGINS, DeletedLogins.GUID, values);
- break;
-
- case DISABLED_HOSTS:
- String hostname = values.getAsString(LoginsDisabledHosts.HOSTNAME);
- debug("Inserting disabled-host in database with hostname: " + hostname);
- id = db.insertOrThrow(TABLE_DISABLED_HOSTS, LoginsDisabledHosts.HOSTNAME, values);
- break;
-
- default:
- throw new UnsupportedOperationException("Unknown insert URI " + uri);
- }
-
- debug("Inserted ID in database: " + id);
-
- if (id >= 0) {
- return ContentUris.withAppendedId(uri, id);
- }
-
- return null;
- }
-
- @Override
- @SuppressWarnings("fallthrough")
- protected int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
- trace("Calling delete in transaction on URI: " + uri);
-
- final int match = URI_MATCHER.match(uri);
- final String table;
- final SQLiteDatabase db = getWritableDatabase(uri);
-
- beginWrite(db);
- switch (match) {
- case LOGINS_ID:
- trace("Delete on LOGINS_ID: " + uri);
- selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_LOGINS, Logins._ID));
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[]{Long.toString(ContentUris.parseId(uri))});
- // Store the deleted client in deleted-logins table.
- final String guid = getLoginGUIDByID(selection, selectionArgs, db);
- if (guid == null) {
- // No matching logins found for the id.
- return 0;
- }
- boolean isInsertSuccessful = storeDeletedLoginForGUIDInTransaction(guid, db);
- if (!isInsertSuccessful) {
- // Failed to insert into deleted-logins, return early.
- return 0;
- }
- // fall through
- case LOGINS:
- trace("Delete on LOGINS: " + uri);
- table = TABLE_LOGINS;
- break;
-
- case DELETED_LOGINS_ID:
- trace("Delete on DELETED_LOGINS_ID: " + uri);
- selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_DELETED_LOGINS, DeletedLogins._ID));
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[]{Long.toString(ContentUris.parseId(uri))});
- // fall through
- case DELETED_LOGINS:
- trace("Delete on DELETED_LOGINS_ID: " + uri);
- table = TABLE_DELETED_LOGINS;
- break;
-
- case DISABLED_HOSTS_HOSTNAME:
- trace("Delete on DISABLED_HOSTS_HOSTNAME: " + uri);
- selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_DISABLED_HOSTS, LoginsDisabledHosts.HOSTNAME));
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[]{uri.getLastPathSegment()});
- // fall through
- case DISABLED_HOSTS:
- trace("Delete on DISABLED_HOSTS: " + uri);
- table = TABLE_DISABLED_HOSTS;
- break;
-
- default:
- throw new UnsupportedOperationException("Unknown delete URI " + uri);
- }
-
- debug("Deleting " + table + " for URI: " + uri);
- return db.delete(table, selection, selectionArgs);
- }
-
- @Override
- @SuppressWarnings("fallthrough")
- protected int updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- trace("Calling update in transaction on URI: " + uri);
-
- final int match = URI_MATCHER.match(uri);
- final SQLiteDatabase db = getWritableDatabase(uri);
- final String table;
-
- beginWrite(db);
- switch (match) {
- case LOGINS_ID:
- trace("Update on LOGINS_ID: " + uri);
- selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_LOGINS, Logins._ID));
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[]{Long.toString(ContentUris.parseId(uri))});
-
- case LOGINS:
- trace("Update on LOGINS: " + uri);
- table = TABLE_LOGINS;
- // Encrypt sensitive data.
- encryptContentValueFields(values);
- break;
-
- default:
- throw new UnsupportedOperationException("Unknown update URI " + uri);
- }
-
- trace("Updating " + table + " on URI: " + uri);
- return db.update(table, values, selection, selectionArgs);
-
- }
-
- @Override
- @SuppressWarnings("fallthrough")
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
- trace("Calling query on URI: " + uri);
-
- final SQLiteDatabase db = getReadableDatabase(uri);
- final int match = URI_MATCHER.match(uri);
- final String groupBy = null;
- final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- final String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
-
- switch (match) {
- case LOGINS_ID:
- trace("Query is on LOGINS_ID: " + uri);
- selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_LOGINS, Logins._ID));
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
-
- // fall through
- case LOGINS:
- trace("Query is on LOGINS: " + uri);
- if (TextUtils.isEmpty(sortOrder)) {
- sortOrder = DEFAULT_LOGINS_SORT_ORDER;
- } else {
- debug("Using sort order " + sortOrder + ".");
- }
-
- qb.setProjectionMap(LOGIN_PROJECTION_MAP);
- qb.setTables(TABLE_LOGINS);
- break;
-
- case DELETED_LOGINS_ID:
- trace("Query is on DELETED_LOGINS_ID: " + uri);
- selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_DELETED_LOGINS, DeletedLogins._ID));
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
-
- // fall through
- case DELETED_LOGINS:
- trace("Query is on DELETED_LOGINS: " + uri);
- if (TextUtils.isEmpty(sortOrder)) {
- sortOrder = DEFAULT_DELETED_LOGINS_SORT_ORDER;
- } else {
- debug("Using sort order " + sortOrder + ".");
- }
-
- qb.setProjectionMap(DELETED_LOGIN_PROJECTION_MAP);
- qb.setTables(TABLE_DELETED_LOGINS);
- break;
-
- case DISABLED_HOSTS_HOSTNAME:
- trace("Query is on DISABLED_HOSTS_HOSTNAME: " + uri);
- selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_DISABLED_HOSTS, LoginsDisabledHosts.HOSTNAME));
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { uri.getLastPathSegment() });
-
- // fall through
- case DISABLED_HOSTS:
- trace("Query is on DISABLED_HOSTS: " + uri);
- if (TextUtils.isEmpty(sortOrder)) {
- sortOrder = DEFAULT_DISABLED_HOSTS_SORT_ORDER;
- } else {
- debug("Using sort order " + sortOrder + ".");
- }
-
- qb.setProjectionMap(DISABLED_HOSTS_PROJECTION_MAP);
- qb.setTables(TABLE_DISABLED_HOSTS);
- break;
-
- default:
- throw new UnsupportedOperationException("Unknown query URI " + uri);
- }
-
- trace("Running built query.");
- Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy, null, sortOrder, limit);
- // If decryptManyCursorRows does not return the original cursor, it closes it, so there's
- // no need to close here.
- cursor = decryptManyCursorRows(cursor);
- cursor.setNotificationUri(getContext().getContentResolver(), BrowserContract.LOGINS_AUTHORITY_URI);
- return cursor;
- }
-
- @Override
- public String getType(@NonNull Uri uri) {
- final int match = URI_MATCHER.match(uri);
-
- switch (match) {
- case LOGINS:
- return Logins.CONTENT_TYPE;
-
- case LOGINS_ID:
- return Logins.CONTENT_ITEM_TYPE;
-
- case DELETED_LOGINS:
- return DeletedLogins.CONTENT_TYPE;
-
- case DELETED_LOGINS_ID:
- return DeletedLogins.CONTENT_ITEM_TYPE;
-
- case DISABLED_HOSTS:
- return LoginsDisabledHosts.CONTENT_TYPE;
-
- case DISABLED_HOSTS_HOSTNAME:
- return LoginsDisabledHosts.CONTENT_ITEM_TYPE;
-
- default:
- throw new UnsupportedOperationException("Unknown type " + uri);
- }
- }
-
- /**
- * Caller is responsible for invoking this method inside a transaction.
- */
- private String getLoginGUIDByID(final String selection, final String[] selectionArgs, final SQLiteDatabase db) {
- final Cursor cursor = db.query(Logins.TABLE_LOGINS, new String[]{Logins.GUID}, selection, selectionArgs, null, null, DEFAULT_LOGINS_SORT_ORDER);
- try {
- if (!cursor.moveToFirst()) {
- return null;
- }
- return cursor.getString(cursor.getColumnIndexOrThrow(Logins.GUID));
- } finally {
- cursor.close();
- }
- }
-
- /**
- * Caller is responsible for invoking this method inside a transaction.
- */
- private boolean storeDeletedLoginForGUIDInTransaction(final String guid, final SQLiteDatabase db) {
- if (guid == null) {
- return false;
- }
- final ContentValues values = new ContentValues();
- values.put(DeletedLogins.GUID, guid);
- values.put(DeletedLogins.TIME_DELETED, System.currentTimeMillis());
- return db.insert(TABLE_DELETED_LOGINS, DeletedLogins.GUID, values) > 0;
- }
-
- /**
- * Caller is responsible for invoking this method inside a transaction.
- */
- private void removeDeletedLoginsByGUIDInTransaction(ContentValues values, SQLiteDatabase db) {
- if (values.containsKey(Logins.GUID)) {
- final String guid = values.getAsString(Logins.GUID);
- if (guid == null) {
- db.delete(TABLE_DELETED_LOGINS, WHERE_GUID_IS_NULL, null);
- } else {
- String[] args = new String[]{guid};
- db.delete(TABLE_DELETED_LOGINS, WHERE_GUID_IS_VALUE, args);
- }
- }
- }
-
- private void setupDefaultValues(ContentValues values, Uri uri) throws IllegalArgumentException {
- final int match = URI_MATCHER.match(uri);
- final long now = System.currentTimeMillis();
- switch (match) {
- case DELETED_LOGINS:
- values.put(DeletedLogins.TIME_DELETED, now);
- // deleted-logins must contain a guid
- if (!values.containsKey(DeletedLogins.GUID)) {
- throw new IllegalArgumentException("Must provide GUID for deleted-login");
- }
- break;
-
- case LOGINS:
- values.put(Logins.TIME_CREATED, now);
- // Generate GUID for new login. Don't override specified GUIDs.
- if (!values.containsKey(Logins.GUID)) {
- final String guid = Utils.generateGuid();
- values.put(Logins.GUID, guid);
- }
- // The database happily accepts strings for long values; this just lets us re-use
- // the existing helper method.
- String nowString = Long.toString(now);
- DBUtils.replaceKey(values, null, Logins.HTTP_REALM, null);
- DBUtils.replaceKey(values, null, Logins.FORM_SUBMIT_URL, null);
- DBUtils.replaceKey(values, null, Logins.ENC_TYPE, "0");
- DBUtils.replaceKey(values, null, Logins.TIME_LAST_USED, nowString);
- DBUtils.replaceKey(values, null, Logins.TIME_PASSWORD_CHANGED, nowString);
- DBUtils.replaceKey(values, null, Logins.TIMES_USED, "0");
- break;
-
- case DISABLED_HOSTS:
- if (!values.containsKey(LoginsDisabledHosts.HOSTNAME)) {
- throw new IllegalArgumentException("Must provide hostname for disabled-host");
- }
- break;
-
- default:
- throw new UnsupportedOperationException("Unknown URI in setupDefaultValues " + uri);
- }
- }
-
- private void encryptContentValueFields(final ContentValues values) {
- if (values.containsKey(Logins.ENCRYPTED_PASSWORD)) {
- final String res = encrypt(values.getAsString(Logins.ENCRYPTED_PASSWORD));
- values.put(Logins.ENCRYPTED_PASSWORD, res);
- }
-
- if (values.containsKey(Logins.ENCRYPTED_USERNAME)) {
- final String res = encrypt(values.getAsString(Logins.ENCRYPTED_USERNAME));
- values.put(Logins.ENCRYPTED_USERNAME, res);
- }
- }
-
- /**
- * Replace each password and username encrypted ciphertext with its equivalent decrypted
- * plaintext in the given cursor.
- * <p/>
- * The encryption algorithm used to protect logins is unspecified; and further, a consumer of
- * consumers should never have access to encrypted ciphertext.
- *
- * @param cursor containing at least one of password and username encrypted ciphertexts.
- * @return a new {@link Cursor} with password and username decrypted plaintexts.
- */
- private Cursor decryptManyCursorRows(final Cursor cursor) {
- final int passwordIndex = cursor.getColumnIndex(Logins.ENCRYPTED_PASSWORD);
- final int usernameIndex = cursor.getColumnIndex(Logins.ENCRYPTED_USERNAME);
-
- if (passwordIndex == -1 && usernameIndex == -1) {
- return cursor;
- }
-
- // Special case, decrypt the encrypted username or password before returning the cursor.
- final MatrixCursor newCursor = new MatrixCursor(cursor.getColumnNames(), cursor.getColumnCount());
- try {
- for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
- final ContentValues values = new ContentValues();
- DatabaseUtils.cursorRowToContentValues(cursor, values);
-
- if (passwordIndex > -1) {
- String decrypted = decrypt(values.getAsString(Logins.ENCRYPTED_PASSWORD));
- values.put(Logins.ENCRYPTED_PASSWORD, decrypted);
- }
-
- if (usernameIndex > -1) {
- String decrypted = decrypt(values.getAsString(Logins.ENCRYPTED_USERNAME));
- values.put(Logins.ENCRYPTED_USERNAME, decrypted);
- }
-
- final MatrixCursor.RowBuilder rowBuilder = newCursor.newRow();
- for (String key : cursor.getColumnNames()) {
- rowBuilder.add(values.get(key));
- }
- }
- } finally {
- // Close the old cursor before returning the new one.
- cursor.close();
- }
-
- return newCursor;
- }
-
- private String encrypt(@NonNull String initialValue) {
- try {
- final Cipher cipher = getCipher(Cipher.ENCRYPT_MODE);
- return Base64.encodeToString(cipher.doFinal(initialValue.getBytes("UTF-8")), Base64.URL_SAFE);
- } catch (Exception e) {
- debug("encryption failed : " + e);
- throw new IllegalStateException("Logins encryption failed", e);
- }
- }
-
- private String decrypt(@NonNull String initialValue) {
- try {
- final Cipher cipher = getCipher(Cipher.DECRYPT_MODE);
- return new String(cipher.doFinal(Base64.decode(initialValue.getBytes("UTF-8"), Base64.URL_SAFE)));
- } catch (Exception e) {
- debug("Decryption failed : " + e);
- throw new IllegalStateException("Logins decryption failed", e);
- }
- }
-
- private Cipher getCipher(int mode) throws UnsupportedEncodingException, GeneralSecurityException {
- return new NullCipher();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/PasswordsProvider.java b/mobile/android/base/java/org/mozilla/gecko/db/PasswordsProvider.java
deleted file mode 100644
index 2f5e11ed4..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/PasswordsProvider.java
+++ /dev/null
@@ -1,348 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import java.util.HashMap;
-
-import org.mozilla.gecko.CrashHandler;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoMessageReceiver;
-import org.mozilla.gecko.NSSBridge;
-import org.mozilla.gecko.db.BrowserContract.DeletedPasswords;
-import org.mozilla.gecko.db.BrowserContract.GeckoDisabledHosts;
-import org.mozilla.gecko.db.BrowserContract.Passwords;
-import org.mozilla.gecko.mozglue.GeckoLoader;
-import org.mozilla.gecko.sqlite.MatrixBlobCursor;
-import org.mozilla.gecko.sqlite.SQLiteBridge;
-import org.mozilla.gecko.sync.Utils;
-
-import android.content.ContentValues;
-import android.content.Intent;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.util.Log;
-
-public class PasswordsProvider extends SQLiteBridgeContentProvider {
- static final String TABLE_PASSWORDS = "moz_logins";
- static final String TABLE_DELETED_PASSWORDS = "moz_deleted_logins";
- static final String TABLE_DISABLED_HOSTS = "moz_disabledHosts";
-
- private static final String TELEMETRY_TAG = "SQLITEBRIDGE_PROVIDER_PASSWORDS";
-
- private static final int PASSWORDS = 100;
- private static final int DELETED_PASSWORDS = 101;
- private static final int DISABLED_HOSTS = 102;
-
- static final String DEFAULT_PASSWORDS_SORT_ORDER = Passwords.HOSTNAME + " ASC";
- static final String DEFAULT_DELETED_PASSWORDS_SORT_ORDER = DeletedPasswords.TIME_DELETED + " ASC";
-
- private static final UriMatcher URI_MATCHER;
-
- private static final HashMap<String, String> PASSWORDS_PROJECTION_MAP;
- private static final HashMap<String, String> DELETED_PASSWORDS_PROJECTION_MAP;
- private static final HashMap<String, String> DISABLED_HOSTS_PROJECTION_MAP;
-
- // this should be kept in sync with the version in toolkit/components/passwordmgr/storage-mozStorage.js
- private static final int DB_VERSION = 6;
- private static final String DB_FILENAME = "signons.sqlite";
- private static final String WHERE_GUID_IS_NULL = BrowserContract.DeletedPasswords.GUID + " IS NULL";
- private static final String WHERE_GUID_IS_VALUE = BrowserContract.DeletedPasswords.GUID + " = ?";
-
- private static final String LOG_TAG = "GeckoPasswordsProvider";
-
- private CrashHandler mCrashHandler;
-
- static {
- URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
-
- // content://org.mozilla.gecko.providers.browser/passwords/#
- URI_MATCHER.addURI(BrowserContract.PASSWORDS_AUTHORITY, "passwords", PASSWORDS);
-
- PASSWORDS_PROJECTION_MAP = new HashMap<String, String>();
- PASSWORDS_PROJECTION_MAP.put(Passwords.ID, Passwords.ID);
- PASSWORDS_PROJECTION_MAP.put(Passwords.HOSTNAME, Passwords.HOSTNAME);
- PASSWORDS_PROJECTION_MAP.put(Passwords.HTTP_REALM, Passwords.HTTP_REALM);
- PASSWORDS_PROJECTION_MAP.put(Passwords.FORM_SUBMIT_URL, Passwords.FORM_SUBMIT_URL);
- PASSWORDS_PROJECTION_MAP.put(Passwords.USERNAME_FIELD, Passwords.USERNAME_FIELD);
- PASSWORDS_PROJECTION_MAP.put(Passwords.PASSWORD_FIELD, Passwords.PASSWORD_FIELD);
- PASSWORDS_PROJECTION_MAP.put(Passwords.ENCRYPTED_USERNAME, Passwords.ENCRYPTED_USERNAME);
- PASSWORDS_PROJECTION_MAP.put(Passwords.ENCRYPTED_PASSWORD, Passwords.ENCRYPTED_PASSWORD);
- PASSWORDS_PROJECTION_MAP.put(Passwords.GUID, Passwords.GUID);
- PASSWORDS_PROJECTION_MAP.put(Passwords.ENC_TYPE, Passwords.ENC_TYPE);
- PASSWORDS_PROJECTION_MAP.put(Passwords.TIME_CREATED, Passwords.TIME_CREATED);
- PASSWORDS_PROJECTION_MAP.put(Passwords.TIME_LAST_USED, Passwords.TIME_LAST_USED);
- PASSWORDS_PROJECTION_MAP.put(Passwords.TIME_PASSWORD_CHANGED, Passwords.TIME_PASSWORD_CHANGED);
- PASSWORDS_PROJECTION_MAP.put(Passwords.TIMES_USED, Passwords.TIMES_USED);
-
- URI_MATCHER.addURI(BrowserContract.PASSWORDS_AUTHORITY, "deleted-passwords", DELETED_PASSWORDS);
-
- DELETED_PASSWORDS_PROJECTION_MAP = new HashMap<String, String>();
- DELETED_PASSWORDS_PROJECTION_MAP.put(DeletedPasswords.ID, DeletedPasswords.ID);
- DELETED_PASSWORDS_PROJECTION_MAP.put(DeletedPasswords.GUID, DeletedPasswords.GUID);
- DELETED_PASSWORDS_PROJECTION_MAP.put(DeletedPasswords.TIME_DELETED, DeletedPasswords.TIME_DELETED);
-
- URI_MATCHER.addURI(BrowserContract.PASSWORDS_AUTHORITY, "disabled-hosts", DISABLED_HOSTS);
-
- DISABLED_HOSTS_PROJECTION_MAP = new HashMap<String, String>();
- DISABLED_HOSTS_PROJECTION_MAP.put(GeckoDisabledHosts.HOSTNAME, GeckoDisabledHosts.HOSTNAME);
- }
-
- public PasswordsProvider() {
- super(LOG_TAG);
- }
-
- @Override
- public boolean onCreate() {
- mCrashHandler = CrashHandler.createDefaultCrashHandler(getContext());
-
- // We don't use .loadMozGlue because we're in a different process,
- // and we just want to reuse code rather than use the loader lock etc.
- GeckoLoader.doLoadLibrary(getContext(), "mozglue");
- return super.onCreate();
- }
-
- @Override
- public void shutdown() {
- super.shutdown();
-
- if (mCrashHandler != null) {
- mCrashHandler.unregister();
- mCrashHandler = null;
- }
- }
-
- @Override
- protected String getDBName() {
- return DB_FILENAME;
- }
-
- @Override
- protected String getTelemetryPrefix() {
- return TELEMETRY_TAG;
- }
-
- @Override
- protected int getDBVersion() {
- return DB_VERSION;
- }
-
- @Override
- public String getType(Uri uri) {
- final int match = URI_MATCHER.match(uri);
-
- switch (match) {
- case PASSWORDS:
- return Passwords.CONTENT_TYPE;
-
- case DELETED_PASSWORDS:
- return DeletedPasswords.CONTENT_TYPE;
-
- case DISABLED_HOSTS:
- return GeckoDisabledHosts.CONTENT_TYPE;
-
- default:
- throw new UnsupportedOperationException("Unknown type " + uri);
- }
- }
-
- @Override
- public String getTable(Uri uri) {
- final int match = URI_MATCHER.match(uri);
- switch (match) {
- case DELETED_PASSWORDS:
- return TABLE_DELETED_PASSWORDS;
-
- case PASSWORDS:
- return TABLE_PASSWORDS;
-
- case DISABLED_HOSTS:
- return TABLE_DISABLED_HOSTS;
-
- default:
- throw new UnsupportedOperationException("Unknown table " + uri);
- }
- }
-
- @Override
- public String getSortOrder(Uri uri, String aRequested) {
- if (!TextUtils.isEmpty(aRequested)) {
- return aRequested;
- }
-
- final int match = URI_MATCHER.match(uri);
- switch (match) {
- case DELETED_PASSWORDS:
- return DEFAULT_DELETED_PASSWORDS_SORT_ORDER;
-
- case PASSWORDS:
- return DEFAULT_PASSWORDS_SORT_ORDER;
-
- case DISABLED_HOSTS:
- return null;
-
- default:
- throw new UnsupportedOperationException("Unknown URI " + uri);
- }
- }
-
- @Override
- public void setupDefaults(Uri uri, ContentValues values)
- throws IllegalArgumentException {
- int match = URI_MATCHER.match(uri);
- long now = System.currentTimeMillis();
- switch (match) {
- case DELETED_PASSWORDS:
- values.put(DeletedPasswords.TIME_DELETED, now);
-
- // Deleted passwords must contain a guid
- if (!values.containsKey(Passwords.GUID)) {
- throw new IllegalArgumentException("Must provide a GUID for a deleted password");
- }
- break;
-
- case PASSWORDS:
- values.put(Passwords.TIME_CREATED, now);
-
- // Generate GUID for new password. Don't override specified GUIDs.
- if (!values.containsKey(Passwords.GUID)) {
- String guid = Utils.generateGuid();
- values.put(Passwords.GUID, guid);
- }
- String nowString = Long.toString(now);
- DBUtils.replaceKey(values, null, Passwords.HOSTNAME, "");
- DBUtils.replaceKey(values, null, Passwords.HTTP_REALM, "");
- DBUtils.replaceKey(values, null, Passwords.FORM_SUBMIT_URL, "");
- DBUtils.replaceKey(values, null, Passwords.USERNAME_FIELD, "");
- DBUtils.replaceKey(values, null, Passwords.PASSWORD_FIELD, "");
- DBUtils.replaceKey(values, null, Passwords.ENCRYPTED_USERNAME, "");
- DBUtils.replaceKey(values, null, Passwords.ENCRYPTED_PASSWORD, "");
- DBUtils.replaceKey(values, null, Passwords.ENC_TYPE, "0");
- DBUtils.replaceKey(values, null, Passwords.TIME_LAST_USED, nowString);
- DBUtils.replaceKey(values, null, Passwords.TIME_PASSWORD_CHANGED, nowString);
- DBUtils.replaceKey(values, null, Passwords.TIMES_USED, "0");
- break;
-
- case DISABLED_HOSTS:
- if (!values.containsKey(GeckoDisabledHosts.HOSTNAME)) {
- throw new IllegalArgumentException("Must provide a hostname for a disabled host");
- }
- break;
-
- default:
- throw new UnsupportedOperationException("Unknown URI " + uri);
- }
- }
-
- @Override
- public void initGecko() {
- // We're not in the main process. The receiver of this Intent can
- // communicate with Gecko in the main process.
- Intent initIntent = new Intent(getContext(), GeckoMessageReceiver.class);
- initIntent.setAction(GeckoApp.ACTION_INIT_PW);
- mContext.sendBroadcast(initIntent);
- }
-
- private String doCrypto(String initialValue, Uri uri, Boolean encrypt) {
- String profilePath = null;
- if (uri != null) {
- profilePath = uri.getQueryParameter(BrowserContract.PARAM_PROFILE_PATH);
- }
-
- String result = "";
- try {
- if (encrypt) {
- if (profilePath != null) {
- result = NSSBridge.encrypt(mContext, profilePath, initialValue);
- } else {
- result = NSSBridge.encrypt(mContext, initialValue);
- }
- } else {
- if (profilePath != null) {
- result = NSSBridge.decrypt(mContext, profilePath, initialValue);
- } else {
- result = NSSBridge.decrypt(mContext, initialValue);
- }
- }
- } catch (Exception ex) {
- Log.e(LOG_TAG, "Error in NSSBridge");
- throw new RuntimeException(ex);
- }
- return result;
- }
-
- @Override
- public void onPreInsert(ContentValues values, Uri uri, SQLiteBridge db) {
- if (values.containsKey(Passwords.GUID)) {
- String guid = values.getAsString(Passwords.GUID);
- if (guid == null) {
- db.delete(TABLE_DELETED_PASSWORDS, WHERE_GUID_IS_NULL, null);
- return;
- }
- String[] args = new String[] { guid };
- db.delete(TABLE_DELETED_PASSWORDS, WHERE_GUID_IS_VALUE, args);
- }
-
- if (values.containsKey(Passwords.ENCRYPTED_PASSWORD)) {
- String res = doCrypto(values.getAsString(Passwords.ENCRYPTED_PASSWORD), uri, true);
- values.put(Passwords.ENCRYPTED_PASSWORD, res);
- values.put(Passwords.ENC_TYPE, Passwords.ENCTYPE_SDR);
- }
-
- if (values.containsKey(Passwords.ENCRYPTED_USERNAME)) {
- String res = doCrypto(values.getAsString(Passwords.ENCRYPTED_USERNAME), uri, true);
- values.put(Passwords.ENCRYPTED_USERNAME, res);
- values.put(Passwords.ENC_TYPE, Passwords.ENCTYPE_SDR);
- }
- }
-
- @Override
- public void onPreUpdate(ContentValues values, Uri uri, SQLiteBridge db) {
- if (values.containsKey(Passwords.ENCRYPTED_PASSWORD)) {
- String res = doCrypto(values.getAsString(Passwords.ENCRYPTED_PASSWORD), uri, true);
- values.put(Passwords.ENCRYPTED_PASSWORD, res);
- values.put(Passwords.ENC_TYPE, Passwords.ENCTYPE_SDR);
- }
-
- if (values.containsKey(Passwords.ENCRYPTED_USERNAME)) {
- String res = doCrypto(values.getAsString(Passwords.ENCRYPTED_USERNAME), uri, true);
- values.put(Passwords.ENCRYPTED_USERNAME, res);
- values.put(Passwords.ENC_TYPE, Passwords.ENCTYPE_SDR);
- }
- }
-
- @Override
- public void onPostQuery(Cursor cursor, Uri uri, SQLiteBridge db) {
- int passwordIndex = -1;
- int usernameIndex = -1;
- String profilePath = null;
-
- try {
- passwordIndex = cursor.getColumnIndexOrThrow(Passwords.ENCRYPTED_PASSWORD);
- } catch (Exception ex) { }
- try {
- usernameIndex = cursor.getColumnIndexOrThrow(Passwords.ENCRYPTED_USERNAME);
- } catch (Exception ex) { }
-
- if (passwordIndex > -1 || usernameIndex > -1) {
- MatrixBlobCursor m = (MatrixBlobCursor)cursor;
- if (cursor.moveToFirst()) {
- do {
- if (passwordIndex > -1) {
- String decrypted = doCrypto(cursor.getString(passwordIndex), uri, false);;
- m.set(passwordIndex, decrypted);
- }
-
- if (usernameIndex > -1) {
- String decrypted = doCrypto(cursor.getString(usernameIndex), uri, false);
- m.set(usernameIndex, decrypted);
- }
- } while (cursor.moveToNext());
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/PerProfileDatabaseProvider.java b/mobile/android/base/java/org/mozilla/gecko/db/PerProfileDatabaseProvider.java
deleted file mode 100644
index 7075c6e8a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/PerProfileDatabaseProvider.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.db.PerProfileDatabases.DatabaseHelperFactory;
-
-import android.content.Context;
-import android.database.sqlite.SQLiteOpenHelper;
-
-/**
- * Abstract class containing methods needed to make a SQLite-based content
- * provider with a database helper of type T, where one database helper is
- * held per profile.
- */
-public abstract class PerProfileDatabaseProvider<T extends SQLiteOpenHelper> extends AbstractPerProfileDatabaseProvider {
- private PerProfileDatabases<T> databases;
-
- @Override
- protected PerProfileDatabases<T> getDatabases() {
- return databases;
- }
-
- protected abstract String getDatabaseName();
-
- /**
- * Creates and returns an instance of the appropriate DB helper.
- *
- * @param context to use to create the database helper
- * @param databasePath path to the DB file
- * @return instance of the database helper
- */
- protected abstract T createDatabaseHelper(Context context, String databasePath);
-
- @Override
- public boolean onCreate() {
- synchronized (this) {
- databases = new PerProfileDatabases<T>(
- getContext(), getDatabaseName(), new DatabaseHelperFactory<T>() {
- @Override
- public T makeDatabaseHelper(Context context, String databasePath) {
- final T helper = createDatabaseHelper(context, databasePath);
- if (Versions.feature16Plus) {
- helper.setWriteAheadLoggingEnabled(true);
- }
- return helper;
- }
- });
- }
-
- return true;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/PerProfileDatabases.java b/mobile/android/base/java/org/mozilla/gecko/db/PerProfileDatabases.java
deleted file mode 100644
index 288d9cae7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/PerProfileDatabases.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import java.io.File;
-import java.util.HashMap;
-
-import org.mozilla.gecko.GeckoProfile;
-
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.text.TextUtils;
-
-/**
- * Manages a set of per-profile database storage helpers.
- */
-public class PerProfileDatabases<T extends SQLiteOpenHelper> {
-
- private final HashMap<String, T> mStorages = new HashMap<String, T>();
-
- private final Context mContext;
- private final String mDatabaseName;
- private final DatabaseHelperFactory<T> mHelperFactory;
-
- // Only used during tests.
- public void shutdown() {
- synchronized (this) {
- for (T t : mStorages.values()) {
- try {
- t.close();
- } catch (Throwable e) {
- // Never mind.
- }
- }
- }
- }
-
- public interface DatabaseHelperFactory<T> {
- public T makeDatabaseHelper(Context context, String databasePath);
- }
-
- public PerProfileDatabases(final Context context, final String databaseName, final DatabaseHelperFactory<T> helperFactory) {
- mContext = context;
- mDatabaseName = databaseName;
- mHelperFactory = helperFactory;
- }
-
- public String getDatabasePathForProfile(String profile) {
- final File profileDir = GeckoProfile.get(mContext, profile).getDir();
- if (profileDir == null) {
- return null;
- }
-
- return new File(profileDir, mDatabaseName).getAbsolutePath();
- }
-
- public T getDatabaseHelperForProfile(String profile) {
- return getDatabaseHelperForProfile(profile, false);
- }
-
- public T getDatabaseHelperForProfile(String profile, boolean isTest) {
- // Always fall back to default profile if none has been provided.
- if (profile == null) {
- profile = GeckoProfile.get(mContext).getName();
- }
-
- synchronized (this) {
- if (mStorages.containsKey(profile)) {
- return mStorages.get(profile);
- }
-
- final String databasePath = isTest ? mDatabaseName : getDatabasePathForProfile(profile);
- if (databasePath == null) {
- throw new IllegalStateException("Database path is null for profile: " + profile);
- }
-
- final T helper = mHelperFactory.makeDatabaseHelper(mContext, databasePath);
- DBUtils.ensureDatabaseIsNotLocked(helper, databasePath);
-
- mStorages.put(profile, helper);
- return helper;
- }
- }
-
- public synchronized void shrinkMemory() {
- for (T t : mStorages.values()) {
- final SQLiteDatabase db = t.getWritableDatabase();
- db.execSQL("PRAGMA shrink_memory");
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/RemoteClient.java b/mobile/android/base/java/org/mozilla/gecko/db/RemoteClient.java
deleted file mode 100644
index 07f057c11..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/RemoteClient.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import java.util.ArrayList;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * A thin representation of a remote client.
- * <p>
- * We use the hash of the client's GUID as the ID elsewhere.
- */
-public class RemoteClient implements Parcelable {
- public final String guid;
- public final String name;
- public final long lastModified;
- public final String deviceType;
- public final ArrayList<RemoteTab> tabs;
-
- public RemoteClient(String guid, String name, long lastModified, String deviceType) {
- this.guid = guid;
- this.name = name;
- this.lastModified = lastModified;
- this.deviceType = deviceType;
- this.tabs = new ArrayList<>();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeString(guid);
- parcel.writeString(name);
- parcel.writeLong(lastModified);
- parcel.writeString(deviceType);
- parcel.writeTypedList(tabs);
- }
-
- public static final Creator<RemoteClient> CREATOR = new Creator<RemoteClient>() {
- @Override
- public RemoteClient createFromParcel(final Parcel source) {
- final String guid = source.readString();
- final String name = source.readString();
- final long lastModified = source.readLong();
- final String deviceType = source.readString();
-
- final RemoteClient client = new RemoteClient(guid, name, lastModified, deviceType);
- source.readTypedList(client.tabs, RemoteTab.CREATOR);
-
- return client;
- }
-
- @Override
- public RemoteClient[] newArray(final int size) {
- return new RemoteClient[size];
- }
- };
-
- public boolean isDesktop() {
- return "desktop".equals(deviceType);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/RemoteTab.java b/mobile/android/base/java/org/mozilla/gecko/db/RemoteTab.java
deleted file mode 100644
index f7660c1f7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/RemoteTab.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * A thin representation of a remote tab.
- * <p>
- * These are generated functions.
- */
-public class RemoteTab implements Parcelable {
- public final String title;
- public final String url;
- public final long lastUsed;
-
- public RemoteTab(String title, String url, long lastUsed) {
- this.title = title;
- this.url = url;
- this.lastUsed = lastUsed;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeString(title);
- parcel.writeString(url);
- parcel.writeLong(lastUsed);
- }
-
- public static final Creator<RemoteTab> CREATOR = new Creator<RemoteTab>() {
- @Override
- public RemoteTab createFromParcel(final Parcel source) {
- final String title = source.readString();
- final String url = source.readString();
- final long lastUsed = source.readLong();
- return new RemoteTab(title, url, lastUsed);
- }
-
- @Override
- public RemoteTab[] newArray(final int size) {
- return new RemoteTab[size];
- }
- };
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((title == null) ? 0 : title.hashCode());
- result = prime * result + ((url == null) ? 0 : url.hashCode());
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- RemoteTab other = (RemoteTab) obj;
- if (title == null) {
- if (other.title != null) {
- return false;
- }
- } else if (!title.equals(other.title)) {
- return false;
- }
- if (url == null) {
- if (other.url != null) {
- return false;
- }
- } else if (!url.equals(other.url)) {
- return false;
- }
- return true;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/SQLiteBridgeContentProvider.java b/mobile/android/base/java/org/mozilla/gecko/db/SQLiteBridgeContentProvider.java
deleted file mode 100644
index d48604f03..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/SQLiteBridgeContentProvider.java
+++ /dev/null
@@ -1,471 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import java.io.File;
-import java.util.HashMap;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.GeckoThread;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.mozglue.GeckoLoader;
-import org.mozilla.gecko.sqlite.SQLiteBridge;
-import org.mozilla.gecko.sqlite.SQLiteBridgeException;
-
-import android.content.ContentProvider;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.util.Log;
-
-/*
- * Provides a basic ContentProvider that sets up and sends queries through
- * SQLiteBridge. Content providers should extend this by setting the appropriate
- * table and version numbers in onCreate, and implementing the abstract methods:
- *
- * public abstract String getTable(Uri uri);
- * public abstract String getSortOrder(Uri uri, String aRequested);
- * public abstract void setupDefaults(Uri uri, ContentValues values);
- * public abstract void initGecko();
- */
-
-public abstract class SQLiteBridgeContentProvider extends ContentProvider {
- private static final String ERROR_MESSAGE_DATABASE_IS_LOCKED = "Can't step statement: (5) database is locked";
-
- private HashMap<String, SQLiteBridge> mDatabasePerProfile;
- protected Context mContext;
- private final String mLogTag;
-
- protected SQLiteBridgeContentProvider(String logTag) {
- mLogTag = logTag;
- }
-
- /**
- * Subclasses must override this to allow error reporting code to compose
- * the correct histogram name.
- *
- * Ensure that you define the new histograms if you define a new class!
- */
- protected abstract String getTelemetryPrefix();
-
- /**
- * Errors are recorded in telemetry using an enumerated histogram.
- *
- * <https://developer.mozilla.org/en-US/docs/Mozilla/Performance/
- * Adding_a_new_Telemetry_probe#Choosing_a_Histogram_Type>
- *
- * These are the allowable enumeration values. Keep these in sync with the
- * histogram definition!
- *
- */
- private static enum TelemetryErrorOp {
- BULKINSERT (0),
- DELETE (1),
- INSERT (2),
- QUERY (3),
- UPDATE (4);
-
- private final int bucket;
-
- TelemetryErrorOp(final int bucket) {
- this.bucket = bucket;
- }
-
- public int getBucket() {
- return bucket;
- }
- }
-
- @Override
- public void shutdown() {
- if (mDatabasePerProfile == null) {
- return;
- }
-
- synchronized (this) {
- for (SQLiteBridge bridge : mDatabasePerProfile.values()) {
- if (bridge != null) {
- try {
- bridge.close();
- } catch (Exception ex) { }
- }
- }
- mDatabasePerProfile = null;
- }
- super.shutdown();
- }
-
- @Override
- public void finalize() {
- shutdown();
- }
-
- /**
- * Return true of the query is from Firefox Sync.
- * @param uri query URI
- */
- public static boolean isCallerSync(Uri uri) {
- String isSync = uri.getQueryParameter(BrowserContract.PARAM_IS_SYNC);
- return !TextUtils.isEmpty(isSync);
- }
-
- private SQLiteBridge getDB(Context context, final String databasePath) {
- SQLiteBridge bridge = null;
-
- boolean dbNeedsSetup = true;
- try {
- String resourcePath = context.getPackageResourcePath();
- GeckoLoader.loadSQLiteLibs(context, resourcePath);
- GeckoLoader.loadNSSLibs(context, resourcePath);
- bridge = SQLiteBridge.openDatabase(databasePath, null, 0);
- int version = bridge.getVersion();
- dbNeedsSetup = version != getDBVersion();
- } catch (SQLiteBridgeException ex) {
- // close the database
- if (bridge != null) {
- bridge.close();
- }
-
- // this will throw if the database can't be found
- // we should attempt to set it up if Gecko is running
- dbNeedsSetup = true;
- Log.e(mLogTag, "Error getting version ", ex);
-
- // if Gecko is not running, we should bail out. Otherwise we try to
- // let Gecko build the database for us
- if (!GeckoThread.isRunning()) {
- Log.e(mLogTag, "Can not set up database. Gecko is not running");
- return null;
- }
- }
-
- // If the database is not set up yet, or is the wrong schema version, we send an initialize
- // call to Gecko. Gecko will handle building the database file correctly, as well as any
- // migrations that are necessary
- if (dbNeedsSetup) {
- bridge = null;
- initGecko();
- }
- return bridge;
- }
-
- /**
- * Returns the absolute path of a database file depending on the specified profile and dbName.
- * @param profile
- * the profile whose dbPath must be returned
- * @param dbName
- * the name of the db file whose absolute path must be returned
- * @return the absolute path of the db file or <code>null</code> if it was not possible to retrieve a valid path
- *
- */
- private String getDatabasePathForProfile(String profile, String dbName) {
- // Depends on the vagaries of GeckoProfile.get, so null check for safety.
- File profileDir = GeckoProfile.get(mContext, profile).getDir();
- if (profileDir == null) {
- return null;
- }
-
- String databasePath = new File(profileDir, dbName).getAbsolutePath();
- return databasePath;
- }
-
- /**
- * Returns a SQLiteBridge object according to the specified profile id and to the name of db related to the
- * current provider instance.
- * @param profile
- * the id of the profile to be used to retrieve the related SQLiteBridge
- * @return the <code>SQLiteBridge</code> related to the specified profile id or <code>null</code> if it was
- * not possible to retrieve a valid SQLiteBridge
- */
- private SQLiteBridge getDatabaseForProfile(String profile) {
- if (profile == null) {
- profile = GeckoProfile.get(mContext).getName();
- Log.d(mLogTag, "No profile provided, using '" + profile + "'");
- }
-
- final String dbName = getDBName();
- String mapKey = profile + "/" + dbName;
-
- SQLiteBridge db = null;
- synchronized (this) {
- db = mDatabasePerProfile.get(mapKey);
- if (db != null) {
- return db;
- }
- final String dbPath = getDatabasePathForProfile(profile, dbName);
- if (dbPath == null) {
- Log.e(mLogTag, "Failed to get a valid db path for profile '" + profile + "'' dbName '" + dbName + "'");
- return null;
- }
- db = getDB(mContext, dbPath);
- if (db != null) {
- mDatabasePerProfile.put(mapKey, db);
- }
- }
- return db;
- }
-
- /**
- * Returns a SQLiteBridge object according to the specified profile path and to the name of db related to the
- * current provider instance.
- * @param profilePath
- * the profilePath to be used to retrieve the related SQLiteBridge
- * @return the <code>SQLiteBridge</code> related to the specified profile path or <code>null</code> if it was
- * not possible to retrieve a valid <code>SQLiteBridge</code>
- */
- private SQLiteBridge getDatabaseForProfilePath(String profilePath) {
- File profileDir = new File(profilePath, getDBName());
- final String dbPath = profileDir.getPath();
- return getDatabaseForDBPath(dbPath);
- }
-
- /**
- * Returns a SQLiteBridge object according to the specified file path.
- * @param dbPath
- * the path of the file to be used to retrieve the related SQLiteBridge
- * @return the <code>SQLiteBridge</code> related to the specified file path or <code>null</code> if it was
- * not possible to retrieve a valid <code>SQLiteBridge</code>
- *
- */
- private SQLiteBridge getDatabaseForDBPath(String dbPath) {
- SQLiteBridge db = null;
- synchronized (this) {
- db = mDatabasePerProfile.get(dbPath);
- if (db != null) {
- return db;
- }
- db = getDB(mContext, dbPath);
- if (db != null) {
- mDatabasePerProfile.put(dbPath, db);
- }
- }
- return db;
- }
-
- /**
- * Returns a SQLiteBridge object to be used to perform operations on the given <code>Uri</code>.
- * @param uri
- * the <code>Uri</code> to be used to retrieve the related SQLiteBridge
- * @return a <code>SQLiteBridge</code> object to be used on the given uri or <code>null</code> if it was
- * not possible to retrieve a valid <code>SQLiteBridge</code>
- *
- */
- private SQLiteBridge getDatabase(Uri uri) {
- String profile = null;
- String profilePath = null;
-
- profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
- profilePath = uri.getQueryParameter(BrowserContract.PARAM_PROFILE_PATH);
-
- // Testing will specify the absolute profile path
- if (profilePath != null) {
- return getDatabaseForProfilePath(profilePath);
- }
- return getDatabaseForProfile(profile);
- }
-
- @Override
- public boolean onCreate() {
- mContext = getContext();
- synchronized (this) {
- mDatabasePerProfile = new HashMap<String, SQLiteBridge>();
- }
- return true;
- }
-
- @Override
- public String getType(Uri uri) {
- return null;
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- int deleted = 0;
- final SQLiteBridge db = getDatabase(uri);
- if (db == null) {
- return deleted;
- }
-
- try {
- deleted = db.delete(getTable(uri), selection, selectionArgs);
- } catch (SQLiteBridgeException ex) {
- reportError(ex, TelemetryErrorOp.DELETE);
- throw ex;
- }
-
- return deleted;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- long id = -1;
- final SQLiteBridge db = getDatabase(uri);
-
- // If we can not get a SQLiteBridge instance, its likely that the database
- // has not been set up and Gecko is not running. We return null and expect
- // callers to try again later
- if (db == null) {
- return null;
- }
-
- setupDefaults(uri, values);
-
- boolean useTransaction = !db.inTransaction();
- try {
- if (useTransaction) {
- db.beginTransaction();
- }
-
- // onPreInsert does a check for the item in the deleted table in some cases
- // so we put it inside this transaction
- onPreInsert(values, uri, db);
- id = db.insert(getTable(uri), null, values);
-
- if (useTransaction) {
- db.setTransactionSuccessful();
- }
- } catch (SQLiteBridgeException ex) {
- reportError(ex, TelemetryErrorOp.INSERT);
- throw ex;
- } finally {
- if (useTransaction) {
- db.endTransaction();
- }
- }
-
- return ContentUris.withAppendedId(uri, id);
- }
-
- @Override
- public int bulkInsert(Uri uri, ContentValues[] allValues) {
- final SQLiteBridge db = getDatabase(uri);
- // If we can not get a SQLiteBridge instance, its likely that the database
- // has not been set up and Gecko is not running. We return 0 and expect
- // callers to try again later
- if (db == null) {
- return 0;
- }
-
- int rowsAdded = 0;
-
- String table = getTable(uri);
-
- try {
- db.beginTransaction();
- for (ContentValues initialValues : allValues) {
- ContentValues values = new ContentValues(initialValues);
- setupDefaults(uri, values);
- onPreInsert(values, uri, db);
- db.insert(table, null, values);
- rowsAdded++;
- }
- db.setTransactionSuccessful();
- } catch (SQLiteBridgeException ex) {
- reportError(ex, TelemetryErrorOp.BULKINSERT);
- throw ex;
- } finally {
- db.endTransaction();
- }
-
- if (rowsAdded > 0) {
- final boolean shouldSyncToNetwork = !isCallerSync(uri);
- mContext.getContentResolver().notifyChange(uri, null, shouldSyncToNetwork);
- }
-
- return rowsAdded;
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
- int updated = 0;
- final SQLiteBridge db = getDatabase(uri);
-
- // If we can not get a SQLiteBridge instance, its likely that the database
- // has not been set up and Gecko is not running. We return null and expect
- // callers to try again later
- if (db == null) {
- return updated;
- }
-
- onPreUpdate(values, uri, db);
-
- try {
- updated = db.update(getTable(uri), values, selection, selectionArgs);
- } catch (SQLiteBridgeException ex) {
- reportError(ex, TelemetryErrorOp.UPDATE);
- throw ex;
- }
-
- return updated;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder) {
- Cursor cursor = null;
- final SQLiteBridge db = getDatabase(uri);
-
- // If we can not get a SQLiteBridge instance, its likely that the database
- // has not been set up and Gecko is not running. We return null and expect
- // callers to try again later
- if (db == null) {
- return cursor;
- }
-
- sortOrder = getSortOrder(uri, sortOrder);
-
- try {
- cursor = db.query(getTable(uri), projection, selection, selectionArgs, null, null, sortOrder, null);
- onPostQuery(cursor, uri, db);
- } catch (SQLiteBridgeException ex) {
- reportError(ex, TelemetryErrorOp.QUERY);
- throw ex;
- }
-
- return cursor;
- }
-
- private String getHistogram(SQLiteBridgeException e) {
- // If you add values here, make sure to update
- // toolkit/components/telemetry/Histograms.json.
- if (ERROR_MESSAGE_DATABASE_IS_LOCKED.equals(e.getMessage())) {
- return getTelemetryPrefix() + "_LOCKED";
- }
- return null;
- }
-
- protected void reportError(SQLiteBridgeException e, TelemetryErrorOp op) {
- Log.e(mLogTag, "Error in database " + op.name(), e);
- final String histogram = getHistogram(e);
- if (histogram == null) {
- return;
- }
-
- Telemetry.addToHistogram(histogram, op.getBucket());
- }
-
- protected abstract String getDBName();
-
- protected abstract int getDBVersion();
-
- protected abstract String getTable(Uri uri);
-
- protected abstract String getSortOrder(Uri uri, String aRequested);
-
- protected abstract void setupDefaults(Uri uri, ContentValues values);
-
- protected abstract void initGecko();
-
- protected abstract void onPreInsert(ContentValues values, Uri uri, SQLiteBridge db);
-
- protected abstract void onPreUpdate(ContentValues values, Uri uri, SQLiteBridge db);
-
- protected abstract void onPostQuery(Cursor cursor, Uri uri, SQLiteBridge db);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/SearchHistoryProvider.java b/mobile/android/base/java/org/mozilla/gecko/db/SearchHistoryProvider.java
deleted file mode 100644
index 05d31fefd..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/SearchHistoryProvider.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.SearchHistory;
-
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.util.Log;
-
-public class SearchHistoryProvider extends SharedBrowserDatabaseProvider {
- private static final String LOG_TAG = "GeckoSearchProvider";
- private static final boolean DEBUG_ENABLED = false;
-
- /**
- * Collapse whitespace.
- */
- private String stripWhitespace(String query) {
- if (TextUtils.isEmpty(query)) {
- return "";
- }
-
- // Collapse whitespace
- return query.trim().replaceAll("\\s+", " ");
- }
-
-
- @Override
- public Uri insertInTransaction(Uri uri, ContentValues cv) {
- final String query = stripWhitespace(cv.getAsString(SearchHistory.QUERY));
-
- // We don't support inserting empty search queries.
- if (TextUtils.isEmpty(query)) {
- return null;
- }
-
- final SQLiteDatabase db = getWritableDatabase(uri);
- long id = -1;
-
- /*
- * Attempt to insert the query. The catch block handles the case when
- * the query already exists in the DB.
- */
- try {
- cv.put(SearchHistory.QUERY, query);
- cv.put(SearchHistory.VISITS, 1);
- cv.put(SearchHistory.DATE_LAST_VISITED, System.currentTimeMillis());
-
- id = db.insertOrThrow(SearchHistory.TABLE_NAME, null, cv);
-
- if (id > 0) {
- return ContentUris.withAppendedId(uri, id);
- }
- } catch (SQLException e) {
- // This happens when the column already exists for this term.
- if (DEBUG_ENABLED) {
- Log.w(LOG_TAG, String.format("Query `%s` already in db", query));
- }
- }
-
- /*
- * Increment the VISITS counter and update the DATE_LAST_VISITED.
- */
- final String sql = "UPDATE " + SearchHistory.TABLE_NAME + " SET " +
- SearchHistory.VISITS + " = " + SearchHistory.VISITS + " + 1, " +
- SearchHistory.DATE_LAST_VISITED + " = " + System.currentTimeMillis() +
- " WHERE " + SearchHistory.QUERY + " = ?";
-
- final Cursor c = db.rawQuery(sql, new String[] { query });
-
- try {
- if (c.getCount() > 1) {
- // There is a UNIQUE constraint on the QUERY column,
- // so there should only be one match.
- return null;
- }
- if (c.moveToFirst()) {
- return ContentUris.withAppendedId(uri, c.getInt(c.getColumnIndex(SearchHistory._ID)));
- }
- } finally {
- c.close();
- }
-
- return null;
- }
-
- @Override
- public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
- return getWritableDatabase(uri).delete(SearchHistory.TABLE_NAME,
- selection, selectionArgs);
- }
-
- /**
- * Since we are managing counts and the full-text db, an update
- * could mangle the internal state. So we disable it.
- */
- @Override
- public int updateInTransaction(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
- throw new UnsupportedOperationException("This content provider does not support updating items");
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder) {
- final String groupBy = null;
- final String having = null;
- final String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
- final Cursor cursor = getReadableDatabase(uri).query(SearchHistory.TABLE_NAME, projection,
- selection, selectionArgs, groupBy, having, sortOrder, limit);
- cursor.setNotificationUri(getContext().getContentResolver(), uri);
- return cursor;
- }
-
- @Override
- public String getType(Uri uri) {
- return SearchHistory.CONTENT_TYPE;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/Searches.java b/mobile/android/base/java/org/mozilla/gecko/db/Searches.java
deleted file mode 100644
index e050a4f93..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/Searches.java
+++ /dev/null
@@ -1,12 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import android.content.ContentResolver;
-
-public interface Searches {
- public void insert(ContentResolver cr, String query);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/SharedBrowserDatabaseProvider.java b/mobile/android/base/java/org/mozilla/gecko/db/SharedBrowserDatabaseProvider.java
deleted file mode 100644
index 8be18c089..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/SharedBrowserDatabaseProvider.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.db.BrowserContract.CommonColumns;
-import org.mozilla.gecko.db.BrowserContract.SyncColumns;
-import org.mozilla.gecko.db.PerProfileDatabases.DatabaseHelperFactory;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.util.Log;
-
-/**
- * A ContentProvider subclass that provides per-profile browser.db access
- * that can be safely shared between multiple providers.
- *
- * If multiple ContentProvider classes wish to share a database, it's
- * vitally important that they use the same SQLiteOpenHelpers for access.
- *
- * Failure to do so can cause accidental concurrent writes, with the result
- * being unexpected SQLITE_BUSY errors.
- *
- * This class provides a static {@link PerProfileDatabases} instance, lazily
- * initialized within {@link SharedBrowserDatabaseProvider#onCreate()}.
- */
-public abstract class SharedBrowserDatabaseProvider extends AbstractPerProfileDatabaseProvider {
- private static final String LOGTAG = SharedBrowserDatabaseProvider.class.getSimpleName();
-
- private static PerProfileDatabases<BrowserDatabaseHelper> databases;
-
- @Override
- protected PerProfileDatabases<BrowserDatabaseHelper> getDatabases() {
- return databases;
- }
-
- @Override
- public void shutdown() {
- synchronized (SharedBrowserDatabaseProvider.class) {
- databases.shutdown();
- databases = null;
- }
- }
-
- @Override
- public boolean onCreate() {
- // If necessary, do the shared DB work.
- synchronized (SharedBrowserDatabaseProvider.class) {
- if (databases != null) {
- return true;
- }
-
- final DatabaseHelperFactory<BrowserDatabaseHelper> helperFactory = new DatabaseHelperFactory<BrowserDatabaseHelper>() {
- @Override
- public BrowserDatabaseHelper makeDatabaseHelper(Context context, String databasePath) {
- final BrowserDatabaseHelper helper = new BrowserDatabaseHelper(context, databasePath);
- if (Versions.feature16Plus) {
- helper.setWriteAheadLoggingEnabled(true);
- }
- return helper;
- }
- };
-
- databases = new PerProfileDatabases<BrowserDatabaseHelper>(getContext(), BrowserDatabaseHelper.DATABASE_NAME, helperFactory);
- }
-
- return true;
- }
-
- /**
- * Clean up some deleted records from the specified table.
- *
- * If called in an existing transaction, it is the caller's responsibility
- * to ensure that the transaction is already upgraded to a writer, because
- * this method issues a read followed by a write, and thus is potentially
- * vulnerable to an unhandled SQLITE_BUSY failure during the upgrade.
- *
- * If not called in an existing transaction, no new explicit transaction
- * will be begun.
- */
- protected void cleanUpSomeDeletedRecords(Uri fromUri, String tableName) {
- Log.d(LOGTAG, "Cleaning up deleted records from " + tableName);
-
- // We clean up records marked as deleted that are older than a
- // predefined max age. It's important not be too greedy here and
- // remove only a few old deleted records at a time.
-
- // we cleanup records marked as deleted that are older than a
- // predefined max age. It's important not be too greedy here and
- // remove only a few old deleted records at a time.
-
- // Maximum age of deleted records to be cleaned up (20 days in ms)
- final long MAX_AGE_OF_DELETED_RECORDS = 86400000 * 20;
-
- // Number of records marked as deleted to be removed
- final long DELETED_RECORDS_PURGE_LIMIT = 5;
-
- // Android SQLite doesn't have LIMIT on DELETE. Instead, query for the
- // IDs of matching rows, then delete them in one go.
- final long now = System.currentTimeMillis();
- final String selection = getDeletedItemSelection(now - MAX_AGE_OF_DELETED_RECORDS);
-
- final String profile = fromUri.getQueryParameter(BrowserContract.PARAM_PROFILE);
- final SQLiteDatabase db = getWritableDatabaseForProfile(profile, isTest(fromUri));
- final String limit = Long.toString(DELETED_RECORDS_PURGE_LIMIT, 10);
- final Cursor cursor = db.query(tableName, new String[] { CommonColumns._ID }, selection, null, null, null, null, limit);
- final String inClause;
- try {
- inClause = DBUtils.computeSQLInClauseFromLongs(cursor, CommonColumns._ID);
- } finally {
- cursor.close();
- }
-
- db.delete(tableName, inClause, null);
- }
-
- // Override this, or override cleanUpSomeDeletedRecords.
- protected String getDeletedItemSelection(long earlierThan) {
- if (earlierThan == -1L) {
- return SyncColumns.IS_DELETED + " = 1";
- }
- return SyncColumns.IS_DELETED + " = 1 AND " + SyncColumns.DATE_MODIFIED + " <= " + earlierThan;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/SuggestedSites.java b/mobile/android/base/java/org/mozilla/gecko/db/SuggestedSites.java
deleted file mode 100644
index 89b12904b..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/SuggestedSites.java
+++ /dev/null
@@ -1,629 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import android.content.Context;
-import android.content.ContentResolver;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.database.MatrixCursor.RowBuilder;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Scanner;
-import java.util.Set;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.Locales;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.distribution.Distribution;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.util.RawResource;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-
-/**
- * {@code SuggestedSites} provides API to get a list of locale-specific
- * suggested sites to be used in Fennec's top sites panel. It provides
- * only a single method to fetch the list as a {@code Cursor}. This cursor
- * will then be wrapped by {@code TopSitesCursorWrapper} to blend top,
- * pinned, and suggested sites in the UI. The returned {@code Cursor}
- * uses its own schema defined in {@code BrowserContract.SuggestedSites}
- * for clarity.
- *
- * Under the hood, {@code SuggestedSites} keeps reference to the
- * parsed list of sites to avoid reparsing the JSON file on every
- * {@code get()} call.
- *
- * The default list of suggested sites is stored in a raw Android
- * resource ({@code R.raw.suggestedsites}) which is dynamically
- * generated at build time for each target locale.
- *
- * Changes to the list of suggested sites are saved in SharedPreferences.
- */
-@RobocopTarget
-public class SuggestedSites {
- private static final String LOGTAG = "GeckoSuggestedSites";
-
- // SharedPreference key for suggested sites that should be hidden.
- public static final String PREF_SUGGESTED_SITES_HIDDEN = GeckoPreferences.NON_PREF_PREFIX + "suggestedSites.hidden";
- public static final String PREF_SUGGESTED_SITES_HIDDEN_OLD = "suggestedSites.hidden";
-
- // Locale used to generate the current suggested sites.
- public static final String PREF_SUGGESTED_SITES_LOCALE = GeckoPreferences.NON_PREF_PREFIX + "suggestedSites.locale";
- public static final String PREF_SUGGESTED_SITES_LOCALE_OLD = "suggestedSites.locale";
-
- // File in profile dir with the list of suggested sites.
- private static final String FILENAME = "suggestedsites.json";
-
- private static final String[] COLUMNS = new String[] {
- BrowserContract.SuggestedSites._ID,
- BrowserContract.SuggestedSites.URL,
- BrowserContract.SuggestedSites.TITLE,
- BrowserContract.Combined.HISTORY_ID
- };
-
- private static final String JSON_KEY_URL = "url";
- private static final String JSON_KEY_TITLE = "title";
- private static final String JSON_KEY_IMAGE_URL = "imageurl";
- private static final String JSON_KEY_BG_COLOR = "bgcolor";
- private static final String JSON_KEY_RESTRICTED = "restricted";
-
- private static class Site {
- public final String url;
- public final String title;
- public final String imageUrl;
- public final String bgColor;
- public final boolean restricted;
-
- public Site(JSONObject json) throws JSONException {
- this.restricted = !json.isNull(JSON_KEY_RESTRICTED);
- this.url = json.getString(JSON_KEY_URL);
- this.title = json.getString(JSON_KEY_TITLE);
- this.imageUrl = json.getString(JSON_KEY_IMAGE_URL);
- this.bgColor = json.getString(JSON_KEY_BG_COLOR);
-
- validate();
- }
-
- public Site(String url, String title, String imageUrl, String bgColor) {
- this.url = url;
- this.title = title;
- this.imageUrl = imageUrl;
- this.bgColor = bgColor;
- this.restricted = false;
-
- validate();
- }
-
- private void validate() {
- // Site instances must have non-empty values for all properties except IDs.
- if (TextUtils.isEmpty(url) ||
- TextUtils.isEmpty(title) ||
- TextUtils.isEmpty(imageUrl) ||
- TextUtils.isEmpty(bgColor)) {
- throw new IllegalStateException("Suggested sites must have a URL, title, " +
- "image URL, and background color.");
- }
- }
-
- @Override
- public String toString() {
- return "{ url = " + url + "\n" +
- "restricted = " + restricted + "\n" +
- "title = " + title + "\n" +
- "imageUrl = " + imageUrl + "\n" +
- "bgColor = " + bgColor + " }";
- }
-
- public JSONObject toJSON() throws JSONException {
- final JSONObject json = new JSONObject();
-
- if (restricted) {
- json.put(JSON_KEY_RESTRICTED, true);
- }
-
- json.put(JSON_KEY_URL, url);
- json.put(JSON_KEY_TITLE, title);
- json.put(JSON_KEY_IMAGE_URL, imageUrl);
- json.put(JSON_KEY_BG_COLOR, bgColor);
-
- return json;
- }
- }
-
- final Context context;
- final Distribution distribution;
- private File cachedFile;
- private Map<String, Site> cachedSites;
- private Set<String> cachedBlacklist;
-
- public SuggestedSites(Context appContext) {
- this(appContext, null);
- }
-
- public SuggestedSites(Context appContext, Distribution distribution) {
- this(appContext, distribution, null);
- }
-
- public SuggestedSites(Context appContext, Distribution distribution, File file) {
- this.context = appContext;
- this.distribution = distribution;
- this.cachedFile = file;
- }
-
- synchronized File getFile() {
- if (cachedFile == null) {
- cachedFile = GeckoProfile.get(context).getFile(FILENAME);
- }
- return cachedFile;
- }
-
- private static boolean isNewLocale(Context context, Locale requestedLocale) {
- final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
-
- String locale = prefs.getString(PREF_SUGGESTED_SITES_LOCALE_OLD, null);
- if (locale != null) {
- // Migrate the old pref and remove it
- final Editor editor = prefs.edit();
- editor.remove(PREF_SUGGESTED_SITES_LOCALE_OLD);
- editor.putString(PREF_SUGGESTED_SITES_LOCALE, locale);
- editor.apply();
- } else {
- locale = prefs.getString(PREF_SUGGESTED_SITES_LOCALE, null);
- }
- if (locale == null) {
- // Initialize config with the current locale
- updateSuggestedSitesLocale(context);
- return true;
- }
-
- return !TextUtils.equals(requestedLocale.toString(), locale);
- }
-
- /**
- * Return the current locale and its fallback (en_US) in order.
- */
- private static List<Locale> getAcceptableLocales() {
- final List<Locale> locales = new ArrayList<Locale>();
-
- final Locale defaultLocale = Locale.getDefault();
- locales.add(defaultLocale);
-
- if (!defaultLocale.equals(Locale.US)) {
- locales.add(Locale.US);
- }
-
- return locales;
- }
-
- private static Map<String, Site> loadSites(File f) throws IOException {
- Scanner scanner = null;
-
- try {
- scanner = new Scanner(f, "UTF-8");
- return loadSites(scanner.useDelimiter("\\A").next());
- } finally {
- if (scanner != null) {
- scanner.close();
- }
- }
- }
-
- private static Map<String, Site> loadSites(String jsonString) {
- if (TextUtils.isEmpty(jsonString)) {
- return null;
- }
-
- Map<String, Site> sites = null;
-
- try {
- final JSONArray jsonSites = new JSONArray(jsonString);
- sites = new LinkedHashMap<String, Site>(jsonSites.length());
-
- final int count = jsonSites.length();
- for (int i = 0; i < count; i++) {
- final Site site = new Site(jsonSites.getJSONObject(i));
- sites.put(site.url, site);
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Failed to refresh suggested sites", e);
- return null;
- }
-
- return sites;
- }
-
- /**
- * Saves suggested sites file to disk. Access to this method should
- * be synchronized on 'file'.
- */
- static void saveSites(File f, Map<String, Site> sites) {
- ThreadUtils.assertNotOnUiThread();
-
- if (sites == null || sites.isEmpty()) {
- return;
- }
-
- OutputStreamWriter osw = null;
-
- try {
- final JSONArray jsonSites = new JSONArray();
- for (Site site : sites.values()) {
- jsonSites.put(site.toJSON());
- }
-
- osw = new OutputStreamWriter(new FileOutputStream(f), "UTF-8");
-
- final String jsonString = jsonSites.toString();
- osw.write(jsonString, 0, jsonString.length());
- } catch (Exception e) {
- Log.e(LOGTAG, "Failed to save suggested sites", e);
- } finally {
- if (osw != null) {
- try {
- osw.close();
- } catch (IOException e) {
- // Ignore.
- }
- }
- }
- }
-
- private void maybeWaitForDistribution() {
- if (distribution == null) {
- return;
- }
-
- distribution.addOnDistributionReadyCallback(new Distribution.ReadyCallback() {
- @Override
- public void distributionNotFound() {
- // If distribution doesn't exist, simply continue to load
- // suggested sites directly from resources. See refresh().
- }
-
- @Override
- public void distributionFound(Distribution distribution) {
- Log.d(LOGTAG, "Running post-distribution task: suggested sites.");
- // Merge suggested sites from distribution with the
- // default ones. Distribution takes precedence.
- Map<String, Site> sites = loadFromDistribution(distribution);
- if (sites == null) {
- sites = new LinkedHashMap<String, Site>();
- }
- sites.putAll(loadFromResource());
-
- // Update cached list of sites.
- setCachedSites(sites);
-
- // Save the result to disk.
- final File file = getFile();
- synchronized (file) {
- saveSites(file, sites);
- }
-
- // Then notify any active loaders about the changes.
- final ContentResolver cr = context.getContentResolver();
- cr.notifyChange(BrowserContract.SuggestedSites.CONTENT_URI, null);
- }
-
- @Override
- public void distributionArrivedLate(Distribution distribution) {
- distributionFound(distribution);
- }
- });
- }
-
- /**
- * Loads suggested sites from a distribution file either matching the
- * current locale or with the fallback locale (en-US).
- *
- * It's assumed that the given distribution instance is ready to be
- * used and exists.
- */
- static Map<String, Site> loadFromDistribution(Distribution dist) {
- for (Locale locale : getAcceptableLocales()) {
- try {
- final String languageTag = Locales.getLanguageTag(locale);
- final String path = String.format("suggestedsites/locales/%s/%s",
- languageTag, FILENAME);
-
- final File f = dist.getDistributionFile(path);
- if (f == null) {
- Log.d(LOGTAG, "No suggested sites for locale: " + languageTag);
- continue;
- }
-
- return loadSites(f);
- } catch (Exception e) {
- Log.e(LOGTAG, "Failed to open suggested sites for locale " +
- locale + " in distribution.", e);
- }
- }
-
- return null;
- }
-
- private Map<String, Site> loadFromProfile() {
- try {
- final File file = getFile();
- synchronized (file) {
- return loadSites(file);
- }
- } catch (FileNotFoundException e) {
- maybeWaitForDistribution();
- } catch (IOException e) {
- // Fall through, return null.
- }
-
- return null;
- }
-
- Map<String, Site> loadFromResource() {
- try {
- return loadSites(RawResource.getAsString(context, R.raw.suggestedsites));
- } catch (IOException e) {
- return null;
- }
- }
-
- private synchronized void setCachedSites(Map<String, Site> sites) {
- cachedSites = Collections.unmodifiableMap(sites);
- updateSuggestedSitesLocale(context);
- }
-
- /**
- * Refreshes the cached list of sites either from the default raw
- * source or standard file location. This will be called on every
- * cache miss during a {@code get()} call.
- */
- private void refresh() {
- Log.d(LOGTAG, "Refreshing suggested sites from file");
-
- Map<String, Site> sites = loadFromProfile();
- if (sites == null) {
- sites = loadFromResource();
- }
-
- // Update cached list of sites.
- if (sites != null) {
- setCachedSites(sites);
- }
- }
-
- private static void updateSuggestedSitesLocale(Context context) {
- final Editor editor = GeckoSharedPrefs.forProfile(context).edit();
- editor.putString(PREF_SUGGESTED_SITES_LOCALE, Locale.getDefault().toString());
- editor.apply();
- }
-
- private synchronized Site getSiteForUrl(String url) {
- if (cachedSites == null) {
- return null;
- }
-
- return cachedSites.get(url);
- }
-
- /**
- * Returns a {@code Cursor} with the list of suggested websites.
- *
- * @param limit maximum number of suggested sites.
- */
- public Cursor get(int limit) {
- return get(limit, Locale.getDefault());
- }
-
- /**
- * Returns a {@code Cursor} with the list of suggested websites.
- *
- * @param limit maximum number of suggested sites.
- * @param locale the target locale.
- */
- public Cursor get(int limit, Locale locale) {
- return get(limit, locale, null);
- }
-
- /**
- * Returns a {@code Cursor} with the list of suggested websites.
- *
- * @param limit maximum number of suggested sites.
- * @param excludeUrls list of URLs to be excluded from the list.
- */
- public Cursor get(int limit, List<String> excludeUrls) {
- return get(limit, Locale.getDefault(), excludeUrls);
- }
-
- /**
- * Returns a {@code Cursor} with the list of suggested websites.
- *
- * @param limit maximum number of suggested sites.
- * @param locale the target locale.
- * @param excludeUrls list of URLs to be excluded from the list.
- */
- public synchronized Cursor get(int limit, Locale locale, List<String> excludeUrls) {
- final MatrixCursor cursor = new MatrixCursor(COLUMNS);
- final boolean isNewLocale = isNewLocale(context, locale);
-
- // Force the suggested sites file in profile dir to be re-generated
- // if the locale has changed.
- if (isNewLocale) {
- getFile().delete();
- }
-
- if (cachedSites == null || isNewLocale) {
- Log.d(LOGTAG, "No cached sites, refreshing.");
- refresh();
- }
-
- // Return empty cursor if there was an error when
- // loading the suggested sites or the list is empty.
- if (cachedSites == null || cachedSites.isEmpty()) {
- return cursor;
- }
-
- excludeUrls = includeBlacklist(excludeUrls);
-
- final int sitesCount = cachedSites.size();
- Log.d(LOGTAG, "Number of suggested sites: " + sitesCount);
-
- final int maxCount = Math.min(limit, sitesCount);
- // History IDS: real history is positive, -1 is no history id in the combined table
- // hence we can start at -2 for suggested sites
- int id = -1;
- for (Site site : cachedSites.values()) {
- // Decrement ID here: this ensure we have a consistent ID to URL mapping, even if items
- // are removed. If we instead decremented at the point of insertion we'd end up with
- // ID conflicts when a suggested site is removed. (note that cachedSites does not change
- // while we're already showing topsites)
- --id;
- if (cursor.getCount() == maxCount) {
- break;
- }
-
- if (excludeUrls != null && excludeUrls.contains(site.url)) {
- continue;
- }
-
- final boolean restrictedProfile = Restrictions.isRestrictedProfile(context);
-
- if (restrictedProfile == site.restricted) {
- final RowBuilder row = cursor.newRow();
- row.add(id);
- row.add(site.url);
- row.add(site.title);
- row.add(id);
- }
- }
-
- cursor.setNotificationUri(context.getContentResolver(),
- BrowserContract.SuggestedSites.CONTENT_URI);
-
- return cursor;
- }
-
- public boolean contains(String url) {
- return (getSiteForUrl(url) != null);
- }
-
- public String getImageUrlForUrl(String url) {
- final Site site = getSiteForUrl(url);
- return (site != null ? site.imageUrl : null);
- }
-
- public String getBackgroundColorForUrl(String url) {
- final Site site = getSiteForUrl(url);
- return (site != null ? site.bgColor : null);
- }
-
- private Set<String> loadBlacklist() {
- Log.d(LOGTAG, "Loading blacklisted suggested sites from SharedPreferences.");
- final Set<String> blacklist = new HashSet<String>();
-
- final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
- String sitesString = prefs.getString(PREF_SUGGESTED_SITES_HIDDEN_OLD, null);
- if (sitesString != null) {
- // Migrate the old pref and remove it
- final Editor editor = prefs.edit();
- editor.remove(PREF_SUGGESTED_SITES_HIDDEN_OLD);
- editor.putString(PREF_SUGGESTED_SITES_HIDDEN, sitesString);
- editor.apply();
- } else {
- sitesString = prefs.getString(PREF_SUGGESTED_SITES_HIDDEN, null);
- }
-
- if (sitesString != null) {
- for (String site : sitesString.trim().split(" ")) {
- blacklist.add(Uri.decode(site));
- }
- }
-
- return blacklist;
- }
-
- private List<String> includeBlacklist(List<String> originalList) {
- if (cachedBlacklist == null) {
- cachedBlacklist = loadBlacklist();
- }
-
- if (cachedBlacklist.isEmpty()) {
- return originalList;
- }
-
- if (originalList == null) {
- originalList = new ArrayList<String>();
- }
-
- originalList.addAll(cachedBlacklist);
- return originalList;
- }
-
- /**
- * Blacklist a suggested site so it will no longer be returned as a suggested site.
- * This method should only be called from a background thread because it may write
- * to SharedPreferences.
- *
- * Urls that are not Suggested Sites are ignored.
- *
- * @param url String url of site to blacklist
- * @return true is blacklisted, false otherwise
- */
- public synchronized boolean hideSite(String url) {
- ThreadUtils.assertNotOnUiThread();
-
- if (cachedSites == null) {
- refresh();
- if (cachedSites == null) {
- Log.w(LOGTAG, "Could not load suggested sites!");
- return false;
- }
- }
-
- if (cachedSites.containsKey(url)) {
- if (cachedBlacklist == null) {
- cachedBlacklist = loadBlacklist();
- }
-
- // Check if site has already been blacklisted, just in case.
- if (!cachedBlacklist.contains(url)) {
-
- saveToBlacklist(url);
- cachedBlacklist.add(url);
-
- return true;
- }
- }
-
- return false;
- }
-
- private void saveToBlacklist(String url) {
- final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
- final String prefString = prefs.getString(PREF_SUGGESTED_SITES_HIDDEN, "");
- final String siteString = prefString.concat(" " + Uri.encode(url));
- prefs.edit().putString(PREF_SUGGESTED_SITES_HIDDEN, siteString).apply();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/Table.java b/mobile/android/base/java/org/mozilla/gecko/db/Table.java
deleted file mode 100644
index 37a605ee1..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/Table.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-
-// Tables provide a basic wrapper around ContentProvider methods to make it simpler to add new tables into storage.
-// If you create a new Table type, make sure to add it to the sTables list in BrowserProvider to ensure it is queried.
-interface Table {
- // Provides information to BrowserProvider about the type of URIs this Table can handle.
- public static class ContentProviderInfo {
- public final int id; // A number of ID for this table. Used by the UriMatcher in BrowserProvider
- public final String name; // A name for this table. Will be appended onto uris querying this table
- // This is also used to define the mimetype of data returned from this db, i.e.
- // BrowserProvider will return "vnd.android.cursor.item/" + name
-
- public ContentProviderInfo(int id, String name) {
- if (name == null) {
- throw new IllegalArgumentException("Content provider info must specify a name");
- }
- this.id = id;
- this.name = name;
- }
- }
-
- // Return a list of Info about the ContentProvider URIs this will match
- ContentProviderInfo[] getContentProviderInfo();
-
- // Called by BrowserDBHelper whenever the database is created or upgraded.
- // Order in which tables are created/upgraded isn't guaranteed (yet), so be careful if your Table depends on something in a
- // separate table.
- void onCreate(SQLiteDatabase db);
- void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
-
- // Called by BrowserProvider when this database queried/modified
- // The dbId here should match the dbId's you returned in your getContentProviderInfo() call
- Cursor query(SQLiteDatabase db, Uri uri, int dbId, String[] projection, String selection, String[] selectionArgs, String sortOrder, String groupBy, String limit);
- int update(SQLiteDatabase db, Uri uri, int dbId, ContentValues values, String selection, String[] selectionArgs);
- long insert(SQLiteDatabase db, Uri uri, int dbId, ContentValues values);
- int delete(SQLiteDatabase db, Uri uri, int dbId, String selection, String[] selectionArgs);
-};
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/TabsAccessor.java b/mobile/android/base/java/org/mozilla/gecko/db/TabsAccessor.java
deleted file mode 100644
index 1be004ca7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/TabsAccessor.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-
-import org.mozilla.gecko.Tab;
-
-import java.util.List;
-
-public interface TabsAccessor {
- public interface OnQueryTabsCompleteListener {
- public void onQueryTabsComplete(List<RemoteClient> clients);
- }
-
- public Cursor getRemoteClientsByRecencyCursor(Context context);
- public Cursor getRemoteTabsCursor(Context context);
- public Cursor getRemoteTabsCursor(Context context, int limit);
- public List<RemoteClient> getClientsWithoutTabsByRecencyFromCursor(final Cursor cursor);
- public List<RemoteClient> getClientsFromCursor(final Cursor cursor);
- public void getTabs(final Context context, final OnQueryTabsCompleteListener listener);
- public void getTabs(final Context context, final int limit, final OnQueryTabsCompleteListener listener);
- public void persistLocalTabs(final ContentResolver cr, final Iterable<Tab> tabs);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/TabsProvider.java b/mobile/android/base/java/org/mozilla/gecko/db/TabsProvider.java
deleted file mode 100644
index 09e4d9cf5..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/TabsProvider.java
+++ /dev/null
@@ -1,361 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.mozilla.gecko.db.BrowserContract.Clients;
-import org.mozilla.gecko.db.BrowserContract.Tabs;
-
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.net.Uri;
-import android.text.TextUtils;
-
-public class TabsProvider extends SharedBrowserDatabaseProvider {
- private static final long ONE_DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24;
- private static final long ONE_WEEK_IN_MILLISECONDS = 7 * ONE_DAY_IN_MILLISECONDS;
- private static final long THREE_WEEKS_IN_MILLISECONDS = 3 * ONE_WEEK_IN_MILLISECONDS;
-
- static final String TABLE_TABS = "tabs";
- static final String TABLE_CLIENTS = "clients";
-
- static final int TABS = 600;
- static final int TABS_ID = 601;
- static final int CLIENTS = 602;
- static final int CLIENTS_ID = 603;
- static final int CLIENTS_RECENCY = 604;
-
- // Exclude clients that are more than three weeks old and also any duplicates that are older than one week old.
- static final String EXCLUDE_STALE_CLIENTS_SUBQUERY =
- "(SELECT " + Clients.GUID +
- ", " + Clients.NAME +
- ", " + Clients.LAST_MODIFIED +
- ", " + Clients.DEVICE_TYPE +
- " FROM " + TABLE_CLIENTS +
- " WHERE " + Clients.LAST_MODIFIED + " > %1$s " +
- " GROUP BY " + Clients.NAME +
- " UNION ALL " +
- " SELECT c." + Clients.GUID + " AS " + Clients.GUID +
- ", c." + Clients.NAME + " AS " + Clients.NAME +
- ", c." + Clients.LAST_MODIFIED + " AS " + Clients.LAST_MODIFIED +
- ", c." + Clients.DEVICE_TYPE + " AS " + Clients.DEVICE_TYPE +
- " FROM " + TABLE_CLIENTS + " AS c " +
- " JOIN (" +
- " SELECT " + Clients.GUID +
- ", " + "MAX( " + Clients.LAST_MODIFIED + ") AS " + Clients.LAST_MODIFIED +
- " FROM " + TABLE_CLIENTS +
- " WHERE (" + Clients.LAST_MODIFIED + " < %1$s" + " AND " + Clients.LAST_MODIFIED + " > %2$s) AND " +
- Clients.NAME + " NOT IN " + "( SELECT " + Clients.NAME + " FROM " + TABLE_CLIENTS + " WHERE " + Clients.LAST_MODIFIED + " > %1$s)" +
- " GROUP BY " + Clients.NAME +
- ") AS c2" +
- " ON c." + Clients.GUID + " = c2." + Clients.GUID + ")";
-
- static final String DEFAULT_TABS_SORT_ORDER = Clients.LAST_MODIFIED + " DESC, " + Tabs.LAST_USED + " DESC";
- static final String DEFAULT_CLIENTS_SORT_ORDER = Clients.LAST_MODIFIED + " DESC";
- static final String DEFAULT_CLIENTS_RECENCY_SORT_ORDER = "COALESCE(MAX(" + Tabs.LAST_USED + "), " + Clients.LAST_MODIFIED + ") DESC";
-
- static final String INDEX_TABS_GUID = "tabs_guid_index";
- static final String INDEX_TABS_POSITION = "tabs_position_index";
-
- static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
-
- static final Map<String, String> TABS_PROJECTION_MAP;
- static final Map<String, String> CLIENTS_PROJECTION_MAP;
- static final Map<String, String> CLIENTS_RECENCY_PROJECTION_MAP;
-
- static {
- URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "tabs", TABS);
- URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "tabs/#", TABS_ID);
- URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "clients", CLIENTS);
- URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "clients/#", CLIENTS_ID);
- URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "clients_recency", CLIENTS_RECENCY);
-
- HashMap<String, String> map;
-
- map = new HashMap<String, String>();
- map.put(Tabs._ID, Tabs._ID);
- map.put(Tabs.TITLE, Tabs.TITLE);
- map.put(Tabs.URL, Tabs.URL);
- map.put(Tabs.HISTORY, Tabs.HISTORY);
- map.put(Tabs.FAVICON, Tabs.FAVICON);
- map.put(Tabs.LAST_USED, Tabs.LAST_USED);
- map.put(Tabs.POSITION, Tabs.POSITION);
- map.put(Clients.GUID, Clients.GUID);
- map.put(Clients.NAME, Clients.NAME);
- map.put(Clients.LAST_MODIFIED, Clients.LAST_MODIFIED);
- map.put(Clients.DEVICE_TYPE, Clients.DEVICE_TYPE);
- TABS_PROJECTION_MAP = Collections.unmodifiableMap(map);
-
- map = new HashMap<String, String>();
- map.put(Clients.GUID, Clients.GUID);
- map.put(Clients.NAME, Clients.NAME);
- map.put(Clients.LAST_MODIFIED, Clients.LAST_MODIFIED);
- map.put(Clients.DEVICE_TYPE, Clients.DEVICE_TYPE);
- CLIENTS_PROJECTION_MAP = Collections.unmodifiableMap(map);
-
- map = new HashMap<>();
- map.put(Clients.GUID, projectColumn(TABLE_CLIENTS, Clients.GUID) + " AS guid");
- map.put(Clients.NAME, projectColumn(TABLE_CLIENTS, Clients.NAME) + " AS name");
- map.put(Clients.LAST_MODIFIED, projectColumn(TABLE_CLIENTS, Clients.LAST_MODIFIED) + " AS last_modified");
- map.put(Clients.DEVICE_TYPE, projectColumn(TABLE_CLIENTS, Clients.DEVICE_TYPE) + " AS device_type");
- // last_used is the max of the tab last_used times, or if there are no tabs,
- // the client's last_modified time.
- map.put(Tabs.LAST_USED, "COALESCE(MAX(" + projectColumn(TABLE_TABS, Tabs.LAST_USED) + "), " + projectColumn(TABLE_CLIENTS, Clients.LAST_MODIFIED) + ") AS last_used");
- CLIENTS_RECENCY_PROJECTION_MAP = Collections.unmodifiableMap(map);
- }
-
- private static final String projectColumn(String table, String column) {
- return table + "." + column;
- }
-
- private static final String selectColumn(String table, String column) {
- return projectColumn(table, column) + " = ?";
- }
-
- @Override
- public String getType(Uri uri) {
- final int match = URI_MATCHER.match(uri);
-
- trace("Getting URI type: " + uri);
-
- switch (match) {
- case TABS:
- trace("URI is TABS: " + uri);
- return Tabs.CONTENT_TYPE;
-
- case TABS_ID:
- trace("URI is TABS_ID: " + uri);
- return Tabs.CONTENT_ITEM_TYPE;
-
- case CLIENTS:
- trace("URI is CLIENTS: " + uri);
- return Clients.CONTENT_TYPE;
-
- case CLIENTS_ID:
- trace("URI is CLIENTS_ID: " + uri);
- return Clients.CONTENT_ITEM_TYPE;
- }
-
- debug("URI has unrecognized type: " + uri);
-
- return null;
- }
-
- @Override
- @SuppressWarnings("fallthrough")
- public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
- trace("Calling delete in transaction on URI: " + uri);
-
- final int match = URI_MATCHER.match(uri);
- int deleted = 0;
-
- switch (match) {
- case CLIENTS_ID:
- trace("Delete on CLIENTS_ID: " + uri);
- selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_CLIENTS, Clients._ID));
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- // fall through
- case CLIENTS:
- trace("Delete on CLIENTS: " + uri);
- deleted = deleteValues(uri, selection, selectionArgs, TABLE_CLIENTS);
- break;
-
- case TABS_ID:
- trace("Delete on TABS_ID: " + uri);
- selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_TABS, Tabs._ID));
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- // fall through
- case TABS:
- trace("Deleting on TABS: " + uri);
- deleted = deleteValues(uri, selection, selectionArgs, TABLE_TABS);
- break;
-
- default:
- throw new UnsupportedOperationException("Unknown delete URI " + uri);
- }
-
- debug("Deleted " + deleted + " rows for URI: " + uri);
-
- return deleted;
- }
-
- @Override
- public Uri insertInTransaction(Uri uri, ContentValues values) {
- trace("Calling insert in transaction on URI: " + uri);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
- int match = URI_MATCHER.match(uri);
- long id = -1;
-
- switch (match) {
- case CLIENTS:
- String guid = values.getAsString(Clients.GUID);
- debug("Inserting client in database with GUID: " + guid);
- id = db.insertOrThrow(TABLE_CLIENTS, Clients.GUID, values);
- break;
-
- case TABS:
- String url = values.getAsString(Tabs.URL);
- debug("Inserting tab in database with URL: " + url);
- id = db.insertOrThrow(TABLE_TABS, Tabs.TITLE, values);
- break;
-
- default:
- throw new UnsupportedOperationException("Unknown insert URI " + uri);
- }
-
- debug("Inserted ID in database: " + id);
-
- if (id >= 0)
- return ContentUris.withAppendedId(uri, id);
-
- return null;
- }
-
- @Override
- public int updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- trace("Calling update in transaction on URI: " + uri);
-
- int match = URI_MATCHER.match(uri);
- int updated = 0;
-
- switch (match) {
- case CLIENTS_ID:
- trace("Update on CLIENTS_ID: " + uri);
- selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_CLIENTS, Clients._ID));
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- // fall through
- case CLIENTS:
- trace("Update on CLIENTS: " + uri);
- updated = updateValues(uri, values, selection, selectionArgs, TABLE_CLIENTS);
- break;
-
- case TABS_ID:
- trace("Update on TABS_ID: " + uri);
- selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_TABS, Tabs._ID));
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- // fall through
- case TABS:
- trace("Update on TABS: " + uri);
- updated = updateValues(uri, values, selection, selectionArgs, TABLE_TABS);
- break;
-
- default:
- throw new UnsupportedOperationException("Unknown update URI " + uri);
- }
-
- debug("Updated " + updated + " rows for URI: " + uri);
-
- return updated;
- }
-
- @Override
- @SuppressWarnings("fallthrough")
- public Cursor query(Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder) {
- SQLiteDatabase db = getReadableDatabase(uri);
- final int match = URI_MATCHER.match(uri);
-
- String groupBy = null;
- SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
-
- switch (match) {
- case TABS_ID:
- trace("Query is on TABS_ID: " + uri);
- selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_TABS, Tabs._ID));
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- // fall through
- case TABS:
- trace("Query is on TABS: " + uri);
- if (TextUtils.isEmpty(sortOrder)) {
- sortOrder = DEFAULT_TABS_SORT_ORDER;
- } else {
- debug("Using sort order " + sortOrder + ".");
- }
-
- qb.setProjectionMap(TABS_PROJECTION_MAP);
- qb.setTables(TABLE_TABS + " LEFT OUTER JOIN " + TABLE_CLIENTS + " ON (" + TABLE_TABS + "." + Tabs.CLIENT_GUID + " = " + TABLE_CLIENTS + "." + Clients.GUID + ")");
- break;
-
- case CLIENTS_ID:
- trace("Query is on CLIENTS_ID: " + uri);
- selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_CLIENTS, Clients._ID));
- selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
- new String[] { Long.toString(ContentUris.parseId(uri)) });
- // fall through
- case CLIENTS:
- trace("Query is on CLIENTS: " + uri);
- if (TextUtils.isEmpty(sortOrder)) {
- sortOrder = DEFAULT_CLIENTS_SORT_ORDER;
- } else {
- debug("Using sort order " + sortOrder + ".");
- }
-
- qb.setProjectionMap(CLIENTS_PROJECTION_MAP);
- qb.setTables(TABLE_CLIENTS);
- break;
-
- case CLIENTS_RECENCY:
- trace("Query is on CLIENTS_RECENCY: " + uri);
- if (TextUtils.isEmpty(sortOrder)) {
- sortOrder = DEFAULT_CLIENTS_RECENCY_SORT_ORDER;
- } else {
- debug("Using sort order " + sortOrder + ".");
- }
-
- final long oneWeekAgo = System.currentTimeMillis() - ONE_WEEK_IN_MILLISECONDS;
- final long threeWeeksAgo = System.currentTimeMillis() - THREE_WEEKS_IN_MILLISECONDS;
-
- final String excludeStaleClientsTable = String.format(EXCLUDE_STALE_CLIENTS_SUBQUERY, oneWeekAgo, threeWeeksAgo);
-
- qb.setProjectionMap(CLIENTS_RECENCY_PROJECTION_MAP);
-
- // Use a subquery to quietly exclude stale duplicate client records.
- qb.setTables(excludeStaleClientsTable + " AS " + TABLE_CLIENTS + " LEFT OUTER JOIN " + TABLE_TABS +
- " ON (" + projectColumn(TABLE_CLIENTS, Clients.GUID) +
- " = " + projectColumn(TABLE_TABS, Tabs.CLIENT_GUID) + ")");
- groupBy = projectColumn(TABLE_CLIENTS, Clients.GUID);
- break;
-
- default:
- throw new UnsupportedOperationException("Unknown query URI " + uri);
- }
-
- trace("Running built query.");
- final Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy, null, sortOrder, limit);
- cursor.setNotificationUri(getContext().getContentResolver(), BrowserContract.TABS_AUTHORITY_URI);
-
- return cursor;
- }
-
- int updateValues(Uri uri, ContentValues values, String selection, String[] selectionArgs, String table) {
- trace("Updating tabs on URI: " + uri);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
- beginWrite(db);
- return db.update(table, values, selection, selectionArgs);
- }
-
- int deleteValues(Uri uri, String selection, String[] selectionArgs, String table) {
- debug("Deleting tabs for URI: " + uri);
-
- final SQLiteDatabase db = getWritableDatabase(uri);
- beginWrite(db);
- return db.delete(table, selection, selectionArgs);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/URLMetadata.java b/mobile/android/base/java/org/mozilla/gecko/db/URLMetadata.java
deleted file mode 100644
index 7973839e2..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/URLMetadata.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
-package org.mozilla.gecko.db;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import org.json.JSONObject;
-
-import org.mozilla.gecko.annotation.RobocopTarget;
-
-import android.content.ContentResolver;
-
-@RobocopTarget
-public interface URLMetadata {
- public Map<String, Object> fromJSON(JSONObject obj);
- public Map<String, Map<String, Object>> getForURLs(final ContentResolver cr,
- final Collection<String> urls,
- final List<String> columns);
- public void save(final ContentResolver cr, final Map<String, Object> data);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/URLMetadataTable.java b/mobile/android/base/java/org/mozilla/gecko/db/URLMetadataTable.java
deleted file mode 100644
index 49bbb74e7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/URLMetadataTable.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
-
-package org.mozilla.gecko.db;
-
-import org.mozilla.gecko.db.BrowserContract.Bookmarks;
-import org.mozilla.gecko.db.BrowserContract.History;
-
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-
-// Holds metadata info about urls. Supports some helper functions for getting back a HashMap of key value data.
-public class URLMetadataTable extends BaseTable {
- private static final String LOGTAG = "GeckoURLMetadataTable";
-
- private static final String TABLE = "metadata"; // Name of the table in the db
- private static final int TABLE_ID_NUMBER = BrowserProvider.METADATA;
-
- // Uri for querying this table
- public static final Uri CONTENT_URI = Uri.withAppendedPath(BrowserContract.AUTHORITY_URI, "metadata");
-
- // Columns in the table
- public static final String ID_COLUMN = "id";
- public static final String URL_COLUMN = "url";
- public static final String TILE_IMAGE_URL_COLUMN = "tileImage";
- public static final String TILE_COLOR_COLUMN = "tileColor";
- public static final String TOUCH_ICON_COLUMN = "touchIcon";
-
- URLMetadataTable() { }
-
- @Override
- protected String getTable() {
- return TABLE;
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- String create = "CREATE TABLE " + TABLE + " (" +
- ID_COLUMN + " INTEGER PRIMARY KEY, " +
- URL_COLUMN + " TEXT NON NULL UNIQUE, " +
- TILE_IMAGE_URL_COLUMN + " STRING, " +
- TILE_COLOR_COLUMN + " STRING, " +
- TOUCH_ICON_COLUMN + " STRING);";
- db.execSQL(create);
- }
-
- private void upgradeDatabaseFrom26To27(SQLiteDatabase db) {
- db.execSQL("ALTER TABLE " + TABLE +
- " ADD COLUMN " + TOUCH_ICON_COLUMN + " STRING");
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- // This table was added in v21 of the db. Force its creation if we're coming from an earlier version
- if (newVersion >= 21 && oldVersion < 21) {
- onCreate(db);
- return;
- }
-
- // Removed the redundant metadata_url_idx index in version 26
- if (newVersion >= 26 && oldVersion < 26) {
- db.execSQL("DROP INDEX IF EXISTS metadata_url_idx");
- }
- if (newVersion >= 27 && oldVersion < 27) {
- upgradeDatabaseFrom26To27(db);
- }
- }
-
- @Override
- public Table.ContentProviderInfo[] getContentProviderInfo() {
- return new Table.ContentProviderInfo[] {
- new Table.ContentProviderInfo(TABLE_ID_NUMBER, TABLE)
- };
- }
-
- public int deleteUnused(final SQLiteDatabase db) {
- final String selection = URL_COLUMN + " NOT IN " +
- "(SELECT " + History.URL +
- " FROM " + History.TABLE_NAME +
- " WHERE " + History.IS_DELETED + " = 0" +
- " UNION " +
- " SELECT " + Bookmarks.URL +
- " FROM " + Bookmarks.TABLE_NAME +
- " WHERE " + Bookmarks.IS_DELETED + " = 0 " +
- " AND " + Bookmarks.URL + " IS NOT NULL)";
-
- return db.delete(getTable(), selection, null);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/db/UrlAnnotations.java b/mobile/android/base/java/org/mozilla/gecko/db/UrlAnnotations.java
deleted file mode 100644
index dcae6ee79..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/db/UrlAnnotations.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.db;
-
-import android.content.ContentResolver;
-import android.database.Cursor;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.feeds.subscriptions.FeedSubscription;
-
-public interface UrlAnnotations {
- @RobocopTarget void insertAnnotation(ContentResolver cr, String url, String key, String value);
-
- Cursor getScreenshots(ContentResolver cr);
- void insertScreenshot(ContentResolver cr, String pageUrl, String screenshotPath);
-
- Cursor getFeedSubscriptions(ContentResolver cr);
- Cursor getWebsitesWithFeedUrl(ContentResolver cr);
- void deleteFeedUrl(ContentResolver cr, String websiteUrl);
- boolean hasWebsiteForFeedUrl(ContentResolver cr, String feedUrl);
- void deleteFeedSubscription(ContentResolver cr, FeedSubscription subscription);
- void updateFeedSubscription(ContentResolver cr, FeedSubscription subscription);
- boolean hasFeedSubscription(ContentResolver cr, String feedUrl);
- void insertFeedSubscription(ContentResolver cr, FeedSubscription subscription);
- boolean hasFeedUrlForWebsite(ContentResolver cr, String websiteUrl);
- void insertFeedUrl(ContentResolver cr, String originUrl, String feedUrl);
-
- void insertReaderViewUrl(ContentResolver cr, String pageURL);
- void deleteReaderViewUrl(ContentResolver cr, String pageURL);
-
- /**
- * Did the user ever interact with this URL in regards to home screen shortcuts?
- *
- * @return true if the user has created a home screen shortcut or declined to create one in the
- * past. This method will still return true if the shortcut has been removed from the
- * home screen by the user.
- */
- boolean hasAcceptedOrDeclinedHomeScreenShortcut(ContentResolver cr, String url);
-
- /**
- * Insert an indication that the user has interacted with this URL in regards to home screen
- * shortcuts.
- *
- * @param hasCreatedShortCut True if a home screen shortcut has been created for this URL. False
- * if the user has actively declined to create a shortcut for this URL.
- */
- void insertHomeScreenShortcut(ContentResolver cr, String url, boolean hasCreatedShortCut);
-
- int getAnnotationCount(ContentResolver cr, BrowserContract.UrlAnnotations.Key key);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/delegates/BookmarkStateChangeDelegate.java b/mobile/android/base/java/org/mozilla/gecko/delegates/BookmarkStateChangeDelegate.java
deleted file mode 100644
index a1c54d3c3..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/delegates/BookmarkStateChangeDelegate.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.delegates;
-
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.support.design.widget.Snackbar;
-import android.support.v4.content.ContextCompat;
-import android.util.Log;
-import android.view.View;
-import android.widget.ListView;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.EditBookmarkDialog;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.SnackbarBuilder;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.home.HomeConfig;
-import org.mozilla.gecko.promotion.SimpleHelperUI;
-import org.mozilla.gecko.prompts.Prompt;
-import org.mozilla.gecko.prompts.PromptListItem;
-import org.mozilla.gecko.util.DrawableUtil;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.lang.ref.WeakReference;
-
-/**
- * Delegate to watch for bookmark state changes.
- *
- * This is responsible for showing snackbars and helper UIs related to the addition/removal
- * of bookmarks, or reader view bookmarks.
- */
-public class BookmarkStateChangeDelegate extends BrowserAppDelegateWithReference implements Tabs.OnTabsChangedListener {
- private static final String LOGTAG = "BookmarkDelegate";
-
- @Override
- public void onResume(BrowserApp browserApp) {
- Tabs.registerOnTabsChangedListener(this);
- }
-
- @Override
- public void onPause(BrowserApp browserApp) {
- Tabs.unregisterOnTabsChangedListener(this);
- }
-
- @Override
- public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
- switch (msg) {
- case BOOKMARK_ADDED:
- // We always show the special offline snackbar whenever we bookmark a reader page.
- // It's possible that the page is already stored offline, however this is highly
- // unlikely, and even so it is probably nicer to show the same offline notification
- // every time we bookmark an about:reader page.
- if (!AboutPages.isAboutReader(tab.getURL())) {
- showBookmarkAddedSnackbar();
- } else {
- if (!promoteReaderViewBookmarkAdded()) {
- showReaderModeBookmarkAddedSnackbar();
- }
- }
- break;
-
- case BOOKMARK_REMOVED:
- showBookmarkRemovedSnackbar();
- break;
- }
- }
-
- @Override
- public void onActivityResult(BrowserApp browserApp, int requestCode, int resultCode, Intent data) {
- if (requestCode == BrowserApp.ACTIVITY_REQUEST_FIRST_READERVIEW_BOOKMARK) {
- if (resultCode == BrowserApp.ACTIVITY_RESULT_FIRST_READERVIEW_BOOKMARKS_GOTO_BOOKMARKS) {
- browserApp.openUrlAndStopEditing("about:home?panel=" + HomeConfig.getIdForBuiltinPanelType(HomeConfig.PanelType.BOOKMARKS));
- } else if (resultCode == BrowserApp.ACTIVITY_RESULT_FIRST_READERVIEW_BOOKMARKS_IGNORE) {
- showReaderModeBookmarkAddedSnackbar();
- }
- }
- }
-
- private boolean promoteReaderViewBookmarkAdded() {
- final BrowserApp browserApp = getBrowserApp();
- if (browserApp == null) {
- return false;
- }
-
- final SharedPreferences prefs = GeckoSharedPrefs.forProfile(browserApp);
-
- final boolean hasFirstReaderViewPromptBeenShownBefore = prefs.getBoolean(SimpleHelperUI.PREF_FIRST_RVBP_SHOWN, false);
-
- if (hasFirstReaderViewPromptBeenShownBefore) {
- return false;
- }
-
- SimpleHelperUI.show(browserApp,
- SimpleHelperUI.FIRST_RVBP_SHOWN_TELEMETRYEXTRA,
- BrowserApp.ACTIVITY_REQUEST_FIRST_READERVIEW_BOOKMARK,
- R.string.helper_first_offline_bookmark_title, R.string.helper_first_offline_bookmark_message,
- R.drawable.helper_readerview_bookmark, R.string.helper_first_offline_bookmark_button,
- BrowserApp.ACTIVITY_RESULT_FIRST_READERVIEW_BOOKMARKS_GOTO_BOOKMARKS,
- BrowserApp.ACTIVITY_RESULT_FIRST_READERVIEW_BOOKMARKS_IGNORE);
-
- GeckoSharedPrefs.forProfile(browserApp)
- .edit()
- .putBoolean(SimpleHelperUI.PREF_FIRST_RVBP_SHOWN, true)
- .apply();
-
- return true;
- }
-
- private void showBookmarkAddedSnackbar() {
- final BrowserApp browserApp = getBrowserApp();
- if (browserApp == null) {
- return;
- }
-
- // This flow is from the option menu which has check to see if a bookmark was already added.
- // So, it is safe here to show the snackbar that bookmark_added without any checks.
- final SnackbarBuilder.SnackbarCallback callback = new SnackbarBuilder.SnackbarCallback() {
- @Override
- public void onClick(View v) {
- Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.TOAST, "bookmark_options");
- showBookmarkDialog(browserApp);
- }
- };
-
- SnackbarBuilder.builder(browserApp)
- .message(R.string.bookmark_added)
- .duration(Snackbar.LENGTH_LONG)
- .action(R.string.bookmark_options)
- .callback(callback)
- .buildAndShow();
- }
-
- private void showBookmarkRemovedSnackbar() {
- final BrowserApp browserApp = getBrowserApp();
- if (browserApp == null) {
- return;
- }
-
- SnackbarBuilder.builder(browserApp)
- .message(R.string.bookmark_removed)
- .duration(Snackbar.LENGTH_LONG)
- .buildAndShow();
- }
-
- private static void showBookmarkDialog(final BrowserApp browserApp) {
- final Resources res = browserApp.getResources();
- final Tab tab = Tabs.getInstance().getSelectedTab();
-
- final Prompt ps = new Prompt(browserApp, new Prompt.PromptCallback() {
- @Override
- public void onPromptFinished(String result) {
- int itemId = -1;
- try {
- itemId = new JSONObject(result).getInt("button");
- } catch (JSONException ex) {
- Log.e(LOGTAG, "Exception reading bookmark prompt result", ex);
- }
-
- if (tab == null) {
- return;
- }
-
- if (itemId == 0) {
- final String extrasId = res.getResourceEntryName(R.string.contextmenu_edit_bookmark);
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION,
- TelemetryContract.Method.DIALOG, extrasId);
-
- new EditBookmarkDialog(browserApp).show(tab.getURL());
- } else if (itemId == 1) {
- final String extrasId = res.getResourceEntryName(R.string.contextmenu_add_to_launcher);
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION,
- TelemetryContract.Method.DIALOG, extrasId);
-
- final String url = tab.getURL();
- final String title = tab.getDisplayTitle();
-
- if (url != null && title != null) {
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- GeckoAppShell.createShortcut(title, url);
- }
- });
- }
- }
- }
- });
-
- final PromptListItem[] items = new PromptListItem[2];
- items[0] = new PromptListItem(res.getString(R.string.contextmenu_edit_bookmark));
- items[1] = new PromptListItem(res.getString(R.string.contextmenu_add_to_launcher));
-
- ps.show("", "", items, ListView.CHOICE_MODE_NONE);
- }
-
- private void showReaderModeBookmarkAddedSnackbar() {
- final BrowserApp browserApp = getBrowserApp();
- if (browserApp == null) {
- return;
- }
-
- final Drawable iconDownloaded = DrawableUtil.tintDrawable(browserApp, R.drawable.status_icon_readercache, Color.WHITE);
-
- final SnackbarBuilder.SnackbarCallback callback = new SnackbarBuilder.SnackbarCallback() {
- @Override
- public void onClick(View v) {
- browserApp.openUrlAndStopEditing("about:home?panel=" + HomeConfig.getIdForBuiltinPanelType(HomeConfig.PanelType.BOOKMARKS));
- }
- };
-
- SnackbarBuilder.builder(browserApp)
- .message(R.string.reader_saved_offline)
- .duration(Snackbar.LENGTH_LONG)
- .action(R.string.reader_switch_to_bookmarks)
- .callback(callback)
- .icon(iconDownloaded)
- .backgroundColor(ContextCompat.getColor(browserApp, R.color.link_blue))
- .actionColor(Color.WHITE)
- .buildAndShow();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/delegates/BrowserAppDelegate.java b/mobile/android/base/java/org/mozilla/gecko/delegates/BrowserAppDelegate.java
deleted file mode 100644
index 70b134992..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/delegates/BrowserAppDelegate.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.delegates;
-
-import android.content.Intent;
-import android.os.Bundle;
-
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.mozglue.SafeIntent;
-import org.mozilla.gecko.tabs.TabsPanel;
-
-/**
- * Abstract class for extending the behavior of BrowserApp without adding additional code to the
- * already huge class.
- */
-public abstract class BrowserAppDelegate {
- /**
- * Called when the BrowserApp activity is first created.
- */
- public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {}
-
- /**
- * Called after the BrowserApp activity has been stopped, prior to it being started again.
- */
- public void onRestart(BrowserApp browserApp) {}
-
- /**
- * Called when the BrowserApp activity is becoming visible to the user.
- */
- public void onStart(BrowserApp browserApp) {}
-
- /**
- * Called when the BrowserApp activity will start interacting with the user.
- */
- public void onResume(BrowserApp browserApp) {}
-
- /**
- * Called when the system is about to start resuming a previous activity.
- */
- public void onPause(BrowserApp browserApp) {}
-
- /**
- * Called when BrowserApp activity is no longer visible to the user.
- */
- public void onStop(BrowserApp browserApp) {}
-
- /**
- * The final call before the BrowserApp activity is destroyed.
- */
- public void onDestroy(BrowserApp browserApp) {}
-
- /**
- * Called when BrowserApp already exists and a new Intent to re-launch it was fired.
- */
- public void onNewIntent(BrowserApp browserApp, SafeIntent intent) {}
-
- /**
- * Called when the tabs tray is opened.
- */
- public void onTabsTrayShown(BrowserApp browserApp, TabsPanel tabsPanel) {}
-
- /**
- * Called when the tabs tray is closed.
- */
- public void onTabsTrayHidden(BrowserApp browserApp, TabsPanel tabsPanel) {}
-
- /**
- * Called when an activity started using startActivityForResult() returns.
- *
- * Delegates should only use request and result codes declared in BrowserApp itself (as opposed
- * to declarations in the delegate), in order to avoid conflicts.
- */
- public void onActivityResult(BrowserApp browserApp, int requestCode, int resultCode, Intent data) {}
-}
-
diff --git a/mobile/android/base/java/org/mozilla/gecko/delegates/BrowserAppDelegateWithReference.java b/mobile/android/base/java/org/mozilla/gecko/delegates/BrowserAppDelegateWithReference.java
deleted file mode 100644
index c67b8a18a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/delegates/BrowserAppDelegateWithReference.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.mozilla.gecko.delegates;
-
-import android.os.Bundle;
-import android.support.annotation.CallSuper;
-
-import org.mozilla.gecko.BrowserApp;
-
-import java.lang.ref.WeakReference;
-
-/**
- * BrowserAppDelegate that stores a reference to the parent BrowserApp.
- */
-public abstract class BrowserAppDelegateWithReference extends BrowserAppDelegate {
- private WeakReference<BrowserApp> browserApp;
-
- @Override
- @CallSuper
- public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {
- this.browserApp = new WeakReference<>(browserApp);
- }
-
- /**
- * Obtain the referenced BrowserApp. May return <code>null</code> if the BrowserApp no longer
- * exists.
- */
- protected BrowserApp getBrowserApp() {
- return browserApp.get();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/delegates/OfflineTabStatusDelegate.java b/mobile/android/base/java/org/mozilla/gecko/delegates/OfflineTabStatusDelegate.java
deleted file mode 100644
index 5f3aa9c59..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/delegates/OfflineTabStatusDelegate.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.delegates;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.support.annotation.CallSuper;
-import android.support.design.widget.Snackbar;
-import android.support.v4.content.ContextCompat;
-
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.SnackbarBuilder;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.WeakHashMap;
-
-/**
- * Displays "Showing offline version" message when tabs are loaded from cache while offline.
- */
-public class OfflineTabStatusDelegate extends TabsTrayVisibilityAwareDelegate implements Tabs.OnTabsChangedListener {
- private WeakReference<Activity> activityReference;
- private WeakHashMap<Tab, Void> tabsQueuedForOfflineSnackbar = new WeakHashMap<>();
-
- @CallSuper
- @Override
- public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {
- super.onCreate(browserApp, savedInstanceState);
- activityReference = new WeakReference<Activity>(browserApp);
- }
-
- @Override
- public void onResume(BrowserApp browserApp) {
- Tabs.registerOnTabsChangedListener(this);
- }
-
- @Override
- public void onPause(BrowserApp browserApp) {
- Tabs.unregisterOnTabsChangedListener(this);
- }
-
- public void onTabChanged(final Tab tab, Tabs.TabEvents event, String data) {
- if (tab == null) {
- return;
- }
-
- // Ignore tabs loaded regularly.
- if (!tab.hasLoadedFromCache()) {
- return;
- }
-
- // Ignore tabs displaying about pages
- if (AboutPages.isAboutPage(tab.getURL())) {
- return;
- }
-
- // We only want to show these notifications for tabs that were loaded successfully.
- if (tab.getState() != Tab.STATE_SUCCESS) {
- return;
- }
-
- switch (event) {
- // We listen specifically for the STOP event (as opposed to PAGE_SHOW), because we need
- // to know if page load actually succeeded. When STOP is triggered, tab.getState()
- // will return definitive STATE_SUCCESS or STATE_ERROR. When PAGE_SHOW is triggered,
- // tab.getState() will return STATE_LOADING, which is ambiguous for our purposes.
- // We don't want to show these notifications for 404 pages, for example. See Bug 1304914.
- case STOP:
- // Show offline notification if tab is visible, or queue it for display later.
- if (!isTabsTrayVisible() && Tabs.getInstance().isSelectedTab(tab)) {
- showLoadedOfflineSnackbar(activityReference.get());
- } else {
- tabsQueuedForOfflineSnackbar.put(tab, null);
- }
- break;
- // Fallthrough; see Bug 1278980 for details on why this event is here.
- case OPENED_FROM_TABS_TRAY:
- // When tab is selected and offline notification was queued, display it if possible.
- // SELECTED event might also fire when we're on a TabStrip, so check first.
- case SELECTED:
- if (isTabsTrayVisible()) {
- break;
- }
- if (tabsQueuedForOfflineSnackbar.containsKey(tab)) {
- showLoadedOfflineSnackbar(activityReference.get());
- tabsQueuedForOfflineSnackbar.remove(tab);
- }
- break;
- }
- }
-
- /**
- * Displays the notification snackbar and logs a telemetry event.
- *
- * @param activity which will be used for displaying the snackbar.
- */
- private static void showLoadedOfflineSnackbar(final Activity activity) {
- if (activity == null) {
- return;
- }
-
- Telemetry.sendUIEvent(TelemetryContract.Event.NETERROR, TelemetryContract.Method.TOAST, "usecache");
-
- SnackbarBuilder.builder(activity)
- .message(R.string.tab_offline_version)
- .duration(Snackbar.LENGTH_INDEFINITE)
- .backgroundColor(ContextCompat.getColor(activity, R.color.link_blue))
- .buildAndShow();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/delegates/ScreenshotDelegate.java b/mobile/android/base/java/org/mozilla/gecko/delegates/ScreenshotDelegate.java
deleted file mode 100644
index f048372f7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/delegates/ScreenshotDelegate.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.delegates;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.support.design.widget.Snackbar;
-import android.util.Log;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.ScreenshotObserver;
-import org.mozilla.gecko.SnackbarBuilder;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserDB;
-
-import java.lang.ref.WeakReference;
-
-/**
- * Delegate for observing screenshots being taken.
- */
-public class ScreenshotDelegate extends BrowserAppDelegateWithReference implements ScreenshotObserver.OnScreenshotListener {
- private static final String LOGTAG = "GeckoScreenshotDelegate";
-
- private final ScreenshotObserver mScreenshotObserver = new ScreenshotObserver();
-
- @Override
- public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {
- super.onCreate(browserApp, savedInstanceState);
-
- mScreenshotObserver.setListener(browserApp, this);
- }
-
- @Override
- public void onScreenshotTaken(String screenshotPath, String title) {
- // Treat screenshots as a sharing method.
- Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.BUTTON, "screenshot");
-
- if (!AppConstants.SCREENSHOTS_IN_BOOKMARKS_ENABLED) {
- return;
- }
-
- final Tab selectedTab = Tabs.getInstance().getSelectedTab();
- if (selectedTab == null) {
- Log.w(LOGTAG, "Selected tab is null: could not page info to store screenshot.");
- return;
- }
-
- final Activity activity = getBrowserApp();
- if (activity == null) {
- return;
- }
-
- BrowserDB.from(activity).getUrlAnnotations().insertScreenshot(
- activity.getContentResolver(), selectedTab.getURL(), screenshotPath);
-
- SnackbarBuilder.builder(activity)
- .message(R.string.screenshot_added_to_bookmarks)
- .duration(Snackbar.LENGTH_SHORT)
- .buildAndShow();
- }
-
- @Override
- public void onResume(BrowserApp browserApp) {
- mScreenshotObserver.start();
- }
-
- @Override
- public void onPause(BrowserApp browserApp) {
- mScreenshotObserver.stop();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/delegates/TabsTrayVisibilityAwareDelegate.java b/mobile/android/base/java/org/mozilla/gecko/delegates/TabsTrayVisibilityAwareDelegate.java
deleted file mode 100644
index ebd3991ea..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/delegates/TabsTrayVisibilityAwareDelegate.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.delegates;
-
-import android.os.Bundle;
-import android.support.annotation.CallSuper;
-
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.tabs.TabsPanel;
-
-public abstract class TabsTrayVisibilityAwareDelegate extends BrowserAppDelegate {
- private boolean tabsTrayVisible;
-
- @Override
- @CallSuper
- public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {
- tabsTrayVisible = false;
- }
-
- @Override
- @CallSuper
- public void onTabsTrayShown(BrowserApp browserApp, TabsPanel tabsPanel) {
- tabsTrayVisible = true;
- }
-
- @Override
- @CallSuper
- public void onTabsTrayHidden(BrowserApp browserApp, TabsPanel tabsPanel) {
- tabsTrayVisible = false;
- }
-
- protected boolean isTabsTrayVisible() {
- return tabsTrayVisible;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/distribution/Distribution.java b/mobile/android/base/java/org/mozilla/gecko/distribution/Distribution.java
deleted file mode 100644
index a7b0fe32d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/distribution/Distribution.java
+++ /dev/null
@@ -1,1046 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.distribution;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.ProtocolException;
-import java.net.SocketException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.UnknownHostException;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.jar.JarEntry;
-import java.util.jar.JarInputStream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-import javax.net.ssl.SSLException;
-
-import ch.boye.httpclientandroidlib.protocol.HTTP;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.annotation.JNITarget;
-import org.mozilla.gecko.util.FileUtils;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.SystemClock;
-import android.support.annotation.WorkerThread;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-
-/**
- * Handles distribution file loading and fetching,
- * and the corresponding hand-offs to Gecko.
- */
-@RobocopTarget
-public class Distribution {
- private static final String LOGTAG = "GeckoDistribution";
-
- private static final int STATE_UNKNOWN = 0;
- private static final int STATE_NONE = 1;
- private static final int STATE_SET = 2;
-
- private static final String FETCH_PROTOCOL = "https";
- private static final String FETCH_HOSTNAME = "mobile.cdn.mozilla.net";
- private static final String FETCH_PATH = "/distributions/1/";
- private static final String FETCH_EXTENSION = ".jar";
-
- private static final String EXPECTED_CONTENT_TYPE = "application/java-archive";
-
- private static final String DISTRIBUTION_PATH = "distribution/";
-
- /**
- * Telemetry constants.
- */
- private static final String HISTOGRAM_REFERRER_INVALID = "FENNEC_DISTRIBUTION_REFERRER_INVALID";
- private static final String HISTOGRAM_DOWNLOAD_TIME_MS = "FENNEC_DISTRIBUTION_DOWNLOAD_TIME_MS";
- private static final String HISTOGRAM_CODE_CATEGORY = "FENNEC_DISTRIBUTION_CODE_CATEGORY";
-
- /**
- * Success/failure codes. Don't exceed the maximum listed in Histograms.json.
- */
- private static final int CODE_CATEGORY_STATUS_OUT_OF_RANGE = 0;
- // HTTP status 'codes' run from 1 to 5.
- private static final int CODE_CATEGORY_OFFLINE = 6;
- private static final int CODE_CATEGORY_FETCH_EXCEPTION = 7;
-
- // It's a post-fetch exception if we were able to download, but not
- // able to extract.
- private static final int CODE_CATEGORY_POST_FETCH_EXCEPTION = 8;
- private static final int CODE_CATEGORY_POST_FETCH_SECURITY_EXCEPTION = 9;
-
- // It's a malformed distribution if we could extract, but couldn't
- // process the contents.
- private static final int CODE_CATEGORY_MALFORMED_DISTRIBUTION = 10;
-
- // Specific fetch errors.
- private static final int CODE_CATEGORY_FETCH_SOCKET_ERROR = 11;
- private static final int CODE_CATEGORY_FETCH_SSL_ERROR = 12;
- private static final int CODE_CATEGORY_FETCH_NON_SUCCESS_RESPONSE = 13;
- private static final int CODE_CATEGORY_FETCH_INVALID_CONTENT_TYPE = 14;
-
- // Corresponds to the high value in Histograms.json.
- private static final long MAX_DOWNLOAD_TIME_MSEC = 40000; // 40 seconds.
-
- // If this is true, ready callbacks that arrive after our state is initially determined
- // will be queued for delayed running.
- // This should only be the case on first run, when we're in STATE_NONE.
- // Implicitly accessed from any non-UI threads via Distribution.doInit, but in practice only one
- // will actually perform initialization, and "non-UI thread" really means "background thread".
- private volatile boolean shouldDelayLateCallbacks = false;
-
- /**
- * These tasks can be queued to run when a distribution is available.
- *
- * If <code>distributionFound</code> is called, it will be the only call.
- * If <code>distributionNotFound</code> is called, it might be followed by
- * a call to <code>distributionArrivedLate</code>.
- *
- * When <code>distributionNotFound</code> is called,
- * {@link org.mozilla.gecko.distribution.Distribution#exists()} will return
- * false. In the other two callbacks, it will return true.
- */
- public interface ReadyCallback {
- @WorkerThread
- void distributionNotFound();
-
- @WorkerThread
- void distributionFound(Distribution distribution);
-
- @WorkerThread
- void distributionArrivedLate(Distribution distribution);
- }
-
- /**
- * Used as a drop-off point for ReferrerReceiver. Checked when we process
- * first-run distribution.
- *
- * This is `protected` so that test code can clear it between runs.
- */
- @RobocopTarget
- protected static volatile ReferrerDescriptor referrer;
-
- private static Distribution instance;
-
- private final Context context;
- private final String packagePath;
- private final String prefsBranch;
-
- volatile int state = STATE_UNKNOWN;
- private File distributionDir;
-
- private final Queue<ReadyCallback> onDistributionReady = new ConcurrentLinkedQueue<>();
-
- // Callbacks in this queue have been invoked once as distributionNotFound.
- // If they're invoked again, it'll be with distributionArrivedLate.
- private final Queue<ReadyCallback> onLateReady = new ConcurrentLinkedQueue<>();
-
- /**
- * This is a little bit of a bad singleton, because in principle a Distribution
- * can be created with arbitrary paths. So we only have one path to get here, and
- * it uses the default arguments. Watch out if you're creating your own instances!
- */
- public static synchronized Distribution getInstance(Context context) {
- if (instance == null) {
- instance = new Distribution(context);
- }
- return instance;
- }
-
- @RobocopTarget
- public static class DistributionDescriptor {
- public final boolean valid;
- public final String id;
- public final String version; // Example uses a float, but that's a crazy idea.
-
- // Default UI-visible description of the distribution.
- public final String about;
-
- // Each distribution file can include multiple localized versions of
- // the 'about' string. These are represented as, e.g., "about.en-US"
- // keys in the Global object.
- // Here we map locale to description.
- public final Map<String, String> localizedAbout;
-
- @SuppressWarnings("unchecked")
- public DistributionDescriptor(JSONObject obj) {
- this.id = obj.optString("id");
- this.version = obj.optString("version");
- this.about = obj.optString("about");
- Map<String, String> loc = new HashMap<String, String>();
- try {
- Iterator<String> keys = obj.keys();
- while (keys.hasNext()) {
- String key = keys.next();
- if (key.startsWith("about.")) {
- String locale = key.substring(6);
- if (!obj.isNull(locale)) {
- loc.put(locale, obj.getString(key));
- }
- }
- }
- } catch (JSONException ex) {
- Log.w(LOGTAG, "Unable to completely process distribution JSON.", ex);
- }
-
- this.localizedAbout = Collections.unmodifiableMap(loc);
- this.valid = (null != this.id) &&
- (null != this.version) &&
- (null != this.about);
- }
- }
-
- private static Distribution init(final Distribution distribution) {
- // Read/write preferences and files on the background thread.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- boolean distributionSet = distribution.doInit();
- if (distributionSet) {
- String preferencesJSON = "";
- try {
- final File descFile = distribution.getDistributionFile("preferences.json");
- preferencesJSON = FileUtils.readStringFromFile(descFile);
- } catch (IOException e) {
- Log.e(LOGTAG, "Error getting distribution descriptor file.", e);
- }
- GeckoAppShell.notifyObservers("Distribution:Set", preferencesJSON);
- }
- }
- });
-
- return distribution;
- }
-
- /**
- * Initializes distribution if it hasn't already been initialized. Sends
- * messages to Gecko as appropriate.
- *
- * @param packagePath where to look for the distribution directory.
- */
- @RobocopTarget
- public static Distribution init(final Context context, final String packagePath, final String prefsPath) {
- return init(new Distribution(context, packagePath, prefsPath));
- }
-
- /**
- * Use <code>Context.getPackageResourcePath</code> to find an implicit
- * package path. Reuses the existing Distribution if one exists.
- */
- @RobocopTarget
- public static Distribution init(final Context context) {
- return init(Distribution.getInstance(context));
- }
-
- /**
- * Returns parsed contents of bookmarks.json.
- * This method should only be called from a background thread.
- */
- public static JSONArray getBookmarks(final Context context) {
- Distribution dist = new Distribution(context);
- return dist.getBookmarks();
- }
-
- /**
- * @param packagePath where to look for the distribution directory.
- */
- public Distribution(final Context context, final String packagePath, final String prefsBranch) {
- this.context = context;
- this.packagePath = packagePath;
- this.prefsBranch = prefsBranch;
- }
-
- public Distribution(final Context context) {
- this(context, context.getPackageResourcePath(), null);
- }
-
- /**
- * This method is called by ReferrerReceiver when we receive a post-install
- * notification from Google Play.
- *
- * @param ref a parsed referrer value from the store-supplied intent.
- */
- public static void onReceivedReferrer(final Context context, final ReferrerDescriptor ref) {
- // Track the referrer object for distribution handling.
- referrer = ref;
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- final Distribution distribution = Distribution.getInstance(context);
-
- // This will bail if we aren't delayed, or we already have a distribution.
- distribution.processDelayedReferrer(ref);
-
- // On Android 5+ we might receive the referrer intent
- // and never actually launch the browser, which is the usual signal
- // for the distribution init process to complete.
- // Attempt to init here to handle that case.
- // Profile setup that relies on the distribution will occur
- // when the browser is eventually launched, via `addOnDistributionReadyCallback`.
- distribution.doInit();
- }
- });
- }
-
- /**
- * Handle a referrer intent that arrives after first use of the distribution.
- */
- private void processDelayedReferrer(final ReferrerDescriptor ref) {
- ThreadUtils.assertOnBackgroundThread();
- if (state != STATE_NONE) {
- return;
- }
-
- Log.i(LOGTAG, "Processing delayed referrer.");
-
- if (!checkIntentDistribution(ref)) {
- // Oh well. No sense keeping these tasks around.
- this.onLateReady.clear();
- return;
- }
-
- // Persist our new state.
- this.state = STATE_SET;
- getSharedPreferences().edit().putInt(getKeyName(), this.state).apply();
-
- // Just in case this isn't empty but doInit has finished.
- runReadyQueue();
-
- // Now process any tasks that already ran while we were in STATE_NONE
- // to tell them of our good news.
- runLateReadyQueue();
-
- // Make sure that changes to search defaults are applied immediately.
- GeckoAppShell.notifyObservers("Distribution:Changed", "");
- }
-
- /**
- * Helper to grab a file in the distribution directory.
- *
- * Returns null if there is no distribution directory or the file
- * doesn't exist. Ensures init first.
- */
- public File getDistributionFile(String name) {
- Log.d(LOGTAG, "Getting file from distribution.");
-
- if (this.state == STATE_UNKNOWN) {
- if (!this.doInit()) {
- return null;
- }
- }
-
- File dist = ensureDistributionDir();
- if (dist == null) {
- return null;
- }
-
- File descFile = new File(dist, name);
- if (!descFile.exists()) {
- Log.e(LOGTAG, "Distribution directory exists, but no file named " + name);
- return null;
- }
-
- return descFile;
- }
-
- public DistributionDescriptor getDescriptor() {
- File descFile = getDistributionFile("preferences.json");
- if (descFile == null) {
- // Logging and existence checks are handled in getDistributionFile.
- return null;
- }
-
- try {
- JSONObject all = FileUtils.readJSONObjectFromFile(descFile);
-
- if (!all.has("Global")) {
- Log.e(LOGTAG, "Distribution preferences.json has no Global entry!");
- return null;
- }
-
- return new DistributionDescriptor(all.getJSONObject("Global"));
-
- } catch (IOException e) {
- Log.e(LOGTAG, "Error getting distribution descriptor file.", e);
- Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
- return null;
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error parsing preferences.json", e);
- Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
- return null;
- }
- }
-
- /**
- * Get the Android preferences from the preferences.json file, if any exist.
- * @return The preferences in a JSONObject, or an empty JSONObject if no preferences are defined.
- */
- public JSONObject getAndroidPreferences() {
- final File descFile = getDistributionFile("preferences.json");
- if (descFile == null) {
- // Logging and existence checks are handled in getDistributionFile.
- return new JSONObject();
- }
-
- try {
- final JSONObject all = FileUtils.readJSONObjectFromFile(descFile);
-
- if (!all.has("AndroidPreferences")) {
- return new JSONObject();
- }
-
- return all.getJSONObject("AndroidPreferences");
-
- } catch (IOException e) {
- Log.e(LOGTAG, "Error getting distribution descriptor file.", e);
- Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
- return new JSONObject();
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error parsing preferences.json", e);
- Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
- return new JSONObject();
- }
- }
-
- public JSONArray getBookmarks() {
- File bookmarks = getDistributionFile("bookmarks.json");
- if (bookmarks == null) {
- // Logging and existence checks are handled in getDistributionFile.
- return null;
- }
-
- try {
- return new JSONArray(FileUtils.readStringFromFile(bookmarks));
- } catch (IOException e) {
- Log.e(LOGTAG, "Error getting bookmarks", e);
- Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
- return null;
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error parsing bookmarks.json", e);
- Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
- return null;
- }
- }
-
- /**
- * Don't call from the main thread.
- *
- * Postcondition: if this returns true, distributionDir will have been
- * set and populated.
- *
- * This method is *only* protected for use from testDistribution.
- *
- * @return true if we've set a distribution.
- */
- @RobocopTarget
- protected boolean doInit() {
- ThreadUtils.assertNotOnUiThread();
-
- // Bail if we've already tried to initialize the distribution, and
- // there wasn't one.
- final SharedPreferences settings = getSharedPreferences();
-
- final String keyName = getKeyName();
- this.state = settings.getInt(keyName, STATE_UNKNOWN);
-
- if (this.state == STATE_NONE) {
- runReadyQueue();
- return false;
- }
-
- // We've done the work once; don't do it again.
- if (this.state == STATE_SET) {
- // Note that we don't compute the distribution directory.
- // Call `ensureDistributionDir` if you need it.
- runReadyQueue();
- return true;
- }
-
- // We try to find the install intent, then the APK, then the system directory, and finally
- // an already copied distribution. Already copied might originate from the bouncer APK.
- final boolean distributionSet =
- checkIntentDistribution(referrer) ||
- copyAndCheckAPKDistribution() ||
- checkSystemDistribution() ||
- checkDataDistribution();
-
- // If this is our first run -- and thus we weren't already in STATE_NONE or STATE_SET above --
- // and we didn't find a distribution already, then we should hold on to callbacks in case we
- // get a late distribution.
- this.shouldDelayLateCallbacks = !distributionSet;
- this.state = distributionSet ? STATE_SET : STATE_NONE;
- settings.edit().putInt(keyName, this.state).apply();
-
- runReadyQueue();
- return distributionSet;
- }
-
- /**
- * If applicable, download and select the distribution specified in
- * the referrer intent.
- *
- * @return true if a referrer-supplied distribution was selected.
- */
- private boolean checkIntentDistribution(final ReferrerDescriptor referrer) {
- if (referrer == null) {
- return false;
- }
-
- URI uri = getReferredDistribution(referrer);
- if (uri == null) {
- return false;
- }
-
- long start = SystemClock.uptimeMillis();
- Log.v(LOGTAG, "Downloading referred distribution: " + uri);
-
- try {
- final HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();
-
- // If the Search Activity starts, and we handle the referrer intent, this'll return
- // null. Recover gracefully in this case.
- final GeckoAppShell.GeckoInterface geckoInterface = GeckoAppShell.getGeckoInterface();
- final String ua;
- if (geckoInterface == null) {
- // Fall back to GeckoApp's default implementation.
- ua = HardwareUtils.isTablet() ? AppConstants.USER_AGENT_FENNEC_TABLET :
- AppConstants.USER_AGENT_FENNEC_MOBILE;
- } else {
- ua = geckoInterface.getDefaultUAString();
- }
-
- connection.setRequestProperty(HTTP.USER_AGENT, ua);
- connection.setRequestProperty("Accept", EXPECTED_CONTENT_TYPE);
-
- try {
- final JarInputStream distro;
- try {
- distro = fetchDistribution(uri, connection);
- } catch (Exception e) {
- Log.e(LOGTAG, "Error fetching distribution from network.", e);
- recordFetchTelemetry(e);
- return false;
- }
-
- long end = SystemClock.uptimeMillis();
- final long duration = end - start;
- Log.d(LOGTAG, "Distro fetch took " + duration + "ms; result? " + (distro != null));
- Telemetry.addToHistogram(HISTOGRAM_DOWNLOAD_TIME_MS, clamp(MAX_DOWNLOAD_TIME_MSEC, duration));
-
- if (distro == null) {
- // Nothing to do.
- return false;
- }
-
- // Try to copy distribution files from the fetched stream.
- try {
- Log.d(LOGTAG, "Copying files from fetched zip.");
- if (copyFilesFromStream(distro)) {
- // We always copy to the data dir, and we only copy files from
- // a 'distribution' subdirectory. Now determine our actual distribution directory.
- return checkDataDistribution();
- }
- } catch (SecurityException e) {
- Log.e(LOGTAG, "Security exception copying files. Corrupt or malicious?", e);
- Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_POST_FETCH_SECURITY_EXCEPTION);
- } catch (Exception e) {
- Log.e(LOGTAG, "Error copying files from distribution.", e);
- Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_POST_FETCH_EXCEPTION);
- } finally {
- distro.close();
- }
- } finally {
- connection.disconnect();
- }
- } catch (IOException e) {
- Log.e(LOGTAG, "Error copying distribution files from network.", e);
- recordFetchTelemetry(e);
- }
-
- return false;
- }
-
- private static final int clamp(long v, long c) {
- return (int) Math.min(c, v);
- }
-
- /**
- * Fetch the provided URI, returning a {@link JarInputStream} if the response body
- * is appropriate.
- *
- * Protected to allow for mocking.
- *
- * @return the entity body as a stream, or null on failure.
- */
- @SuppressWarnings("static-method")
- @RobocopTarget
- protected JarInputStream fetchDistribution(URI uri, HttpURLConnection connection) throws IOException {
- final int status = connection.getResponseCode();
-
- Log.d(LOGTAG, "Distribution fetch: " + status);
- // We record HTTP statuses as 2xx, 3xx, 4xx, 5xx => 2, 3, 4, 5.
- final int value;
- if (status > 599 || status < 100) {
- Log.wtf(LOGTAG, "Unexpected HTTP status code: " + status);
- value = CODE_CATEGORY_STATUS_OUT_OF_RANGE;
- } else {
- value = status / 100;
- }
-
- Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, value);
-
- if (status != 200) {
- Log.w(LOGTAG, "Got status " + status + " fetching distribution.");
- Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_NON_SUCCESS_RESPONSE);
- return null;
- }
-
- final String contentType = connection.getContentType();
- if (contentType == null || !contentType.startsWith(EXPECTED_CONTENT_TYPE)) {
- Log.w(LOGTAG, "Malformed response: invalid Content-Type.");
- Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_INVALID_CONTENT_TYPE);
- return null;
- }
-
- return new JarInputStream(new BufferedInputStream(connection.getInputStream()), true);
- }
-
- private static void recordFetchTelemetry(final Exception exception) {
- if (exception == null) {
- // Should never happen.
- Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_EXCEPTION);
- return;
- }
-
- if (exception instanceof UnknownHostException) {
- // Unknown host => we're offline.
- Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_OFFLINE);
- return;
- }
-
- if (exception instanceof SSLException) {
- Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_SSL_ERROR);
- return;
- }
-
- if (exception instanceof ProtocolException ||
- exception instanceof SocketException) {
- Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_SOCKET_ERROR);
- return;
- }
-
- Telemetry.addToHistogram(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_FETCH_EXCEPTION);
- }
-
- /**
- * @return true if we copied files out of the APK. Sets distributionDir in that case.
- */
- private boolean copyAndCheckAPKDistribution() {
- try {
- // First, try copying distribution files out of the APK.
- if (copyFilesFromPackagedAssets()) {
- // We always copy to the data dir, and we only copy files from
- // a 'distribution' subdirectory. Now determine our actual distribution directory.
- return checkDataDistribution();
- }
- } catch (IOException e) {
- Log.e(LOGTAG, "Error copying distribution files from APK.", e);
- }
- return false;
- }
-
- /**
- * @return true if we found a data distribution (copied from APK or OTA). Sets distributionDir in that case.
- */
- private boolean checkDataDistribution() {
- return checkDirectories(getDataDistributionDirectories(context));
- }
-
- /**
- * @return true if we found a system distribution. Sets distributionDir in that case.
- */
- private boolean checkSystemDistribution() {
- return checkDirectories(getSystemDistributionDirectories(context));
- }
-
- /**
- * @return true if one of the specified distribution directories exists. Sets distributionDir in that case.
- */
- private boolean checkDirectories(String[] directories) {
- for (String path : directories) {
- File directory = new File(path);
- if (directory.exists()) {
- distributionDir = directory;
- return true;
- }
- }
- return false;
- }
-
- /**
- * Unpack distribution files from a downloaded jar stream.
- *
- * The caller is responsible for closing the provided stream.
- */
- private boolean copyFilesFromStream(JarInputStream jar) throws FileNotFoundException, IOException {
- final byte[] buffer = new byte[1024];
- boolean distributionSet = false;
- JarEntry entry;
- while ((entry = jar.getNextJarEntry()) != null) {
- final String name = entry.getName();
-
- if (entry.isDirectory()) {
- // We'll let getDataFile deal with creating the directory hierarchy.
- // Yes, we can do better, but it can wait.
- continue;
- }
-
- if (!name.startsWith(DISTRIBUTION_PATH)) {
- continue;
- }
-
- File outFile = getDataFile(name);
- if (outFile == null) {
- continue;
- }
-
- distributionSet = true;
-
- writeStream(jar, outFile, entry.getTime(), buffer);
- }
-
- return distributionSet;
- }
-
- /**
- * Copies the /assets/distribution folder out of the APK and into the app's data directory.
- * Returns true if distribution files were found and copied.
- */
- private boolean copyFilesFromPackagedAssets() throws IOException {
- final File applicationPackage = new File(packagePath);
- final ZipFile zip = new ZipFile(applicationPackage);
-
- final String assetsPrefix = "assets/";
- final String fullPrefix = assetsPrefix + DISTRIBUTION_PATH;
-
- boolean distributionSet = false;
- try {
- final byte[] buffer = new byte[1024];
-
- final Enumeration<? extends ZipEntry> zipEntries = zip.entries();
- while (zipEntries.hasMoreElements()) {
- final ZipEntry fileEntry = zipEntries.nextElement();
- final String name = fileEntry.getName();
-
- if (fileEntry.isDirectory()) {
- // We'll let getDataFile deal with creating the directory hierarchy.
- continue;
- }
-
- // Read from "assets/distribution/**".
- if (!name.startsWith(fullPrefix)) {
- continue;
- }
-
- // Write to "distribution/**".
- final String nameWithoutPrefix = name.substring(assetsPrefix.length());
- final File outFile = getDataFile(nameWithoutPrefix);
- if (outFile == null) {
- continue;
- }
-
- distributionSet = true;
-
- final InputStream fileStream = zip.getInputStream(fileEntry);
- try {
- writeStream(fileStream, outFile, fileEntry.getTime(), buffer);
- } finally {
- fileStream.close();
- }
- }
- } finally {
- zip.close();
- }
-
- return distributionSet;
- }
-
- private void writeStream(InputStream fileStream, File outFile, final long modifiedTime, byte[] buffer)
- throws FileNotFoundException, IOException {
- final OutputStream outStream = new FileOutputStream(outFile);
- try {
- int count;
- while ((count = fileStream.read(buffer)) > 0) {
- outStream.write(buffer, 0, count);
- }
-
- outFile.setLastModified(modifiedTime);
- } finally {
- outStream.close();
- }
- }
-
- /**
- * Return a File instance in the data directory, ensuring
- * that the parent exists.
- *
- * @return null if the parents could not be created.
- */
- private File getDataFile(final String name) {
- File outFile = new File(getDataDir(), name);
- File dir = outFile.getParentFile();
-
- if (!dir.exists()) {
- Log.d(LOGTAG, "Creating " + dir.getAbsolutePath());
- if (!dir.mkdirs()) {
- Log.e(LOGTAG, "Unable to create directories: " + dir.getAbsolutePath());
- return null;
- }
- }
-
- return outFile;
- }
-
- private URI getReferredDistribution(ReferrerDescriptor descriptor) {
- final String content = descriptor.content;
- if (content == null) {
- return null;
- }
-
- // We restrict here to avoid injection attacks. After all,
- // we're downloading a distribution payload based on intent input.
- if (!content.matches("^[a-zA-Z0-9]+$")) {
- Log.e(LOGTAG, "Invalid referrer content: " + content);
- Telemetry.addToHistogram(HISTOGRAM_REFERRER_INVALID, 1);
- return null;
- }
-
- try {
- return new URI(FETCH_PROTOCOL, FETCH_HOSTNAME, FETCH_PATH + content + FETCH_EXTENSION, null);
- } catch (URISyntaxException e) {
- // This should never occur.
- Log.wtf(LOGTAG, "Invalid URI with content " + content + "!");
- return null;
- }
- }
-
- /**
- * After calling this method, either <code>distributionDir</code>
- * will be set, or there is no distribution in use.
- *
- * Only call after init.
- */
- private File ensureDistributionDir() {
- if (this.distributionDir != null) {
- return this.distributionDir;
- }
-
- if (this.state != STATE_SET) {
- return null;
- }
-
- // After init, we know that either we've copied a distribution out of
- // the APK, or it exists in /system/.
- // Look in each location in turn.
- // (This could be optimized by caching the path in shared prefs.)
- if (checkDataDistribution() || checkSystemDistribution()) {
- return distributionDir;
- }
-
- return null;
- }
-
- private String getDataDir() {
- return context.getApplicationInfo().dataDir;
- }
-
- @JNITarget
- public static String[] getDistributionDirectories() {
- final Context context = GeckoAppShell.getApplicationContext();
-
- final String[] dataDirectories = getDataDistributionDirectories(context);
- final String[] systemDirectories = getSystemDistributionDirectories(context);
-
- final String[] directories = new String[dataDirectories.length + systemDirectories.length];
-
- System.arraycopy(dataDirectories, 0, directories, 0, dataDirectories.length);
- System.arraycopy(systemDirectories, 0, directories, dataDirectories.length, systemDirectories.length);
-
- return directories;
- }
-
- /**
- * Get a list of system distribution folder candidates.
- *
- * /system/<package>/distribution/<mcc>/<mnc> - For bundled distributions for specific network providers
- * /system/<package>/distribution/<mcc> - For bundled distributions for specific countries
- * /system/<package>/distribution/default - For bundled distributions with no matching mcc/mnc
- * /system/<package>/distribution - Default non-bundled system distribution
- */
- private static String[] getSystemDistributionDirectories(Context context) {
- final String baseDirectory = "/system/" + context.getPackageName() + "/distribution";
- return getDistributionDirectoriesFromBaseDirectory(context, baseDirectory);
- }
-
- /**
- * Get a list of data distribution folder candidates.
- *
- * <dataDir>/distribution/<mcc>/<mnc> - For bundled distributions for specific network providers
- * <dataDir>/distribution/<mcc> - For bundled distributions for specific countries
- * <dataDir>/distribution/default - For bundled distributions with no matching mcc/mnc
- * <dataDir>/distribution - Default non-bundled system distribution
- */
- private static String[] getDataDistributionDirectories(Context context) {
- final String baseDirectory = new File(context.getApplicationInfo().dataDir, DISTRIBUTION_PATH).getAbsolutePath();
- return getDistributionDirectoriesFromBaseDirectory(context, baseDirectory);
- }
-
- /**
- * Get a list of distribution folder candidates inside the specified base directory.
- */
- private static String[] getDistributionDirectoriesFromBaseDirectory(Context context, String baseDirectory) {
- final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- if (telephonyManager != null && telephonyManager.getSimState() == TelephonyManager.SIM_STATE_READY) {
- final String simOperator = telephonyManager.getSimOperator();
-
- if (simOperator != null && simOperator.length() >= 5) {
- final String mcc = simOperator.substring(0, 3);
- final String mnc = simOperator.substring(3);
-
- return new String[] {
- baseDirectory + "/" + mcc + "/" + mnc,
- baseDirectory + "/" + mcc,
- baseDirectory + "/default",
- baseDirectory
- };
- }
- }
-
- return new String[] {
- baseDirectory + "/default",
- baseDirectory
- };
- }
-
- /**
- * The provided <code>ReadyCallback</code> will be queued for execution after
- * the distribution is ready, or queued for immediate execution if the
- * distribution has already been processed.
- *
- * Each <code>ReadyCallback</code> will be executed on the background thread.
- */
- public void addOnDistributionReadyCallback(final ReadyCallback callback) {
- if (state == STATE_UNKNOWN) {
- // Queue for later.
- onDistributionReady.add(callback);
- } else {
- invokeCallbackDelayed(callback);
- }
- }
-
- /**
- * Run our delayed queue, after a delayed distribution arrives.
- */
- private void runLateReadyQueue() {
- ReadyCallback task;
- while ((task = onLateReady.poll()) != null) {
- invokeLateCallbackDelayed(task);
- }
- }
-
- /**
- * Execute tasks that wanted to run when we were done loading
- * the distribution.
- */
- private void runReadyQueue() {
- ReadyCallback task;
- while ((task = onDistributionReady.poll()) != null) {
- invokeCallbackDelayed(task);
- }
- }
-
- private void invokeLateCallbackDelayed(final ReadyCallback callback) {
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- // Sanity.
- if (state != STATE_SET) {
- Log.w(LOGTAG, "Refusing to invoke late distro callback in state " + state);
- return;
- }
- callback.distributionArrivedLate(Distribution.this);
- }
- });
- }
-
- private void invokeCallbackDelayed(final ReadyCallback callback) {
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @WorkerThread
- @Override
- public void run() {
- switch (state) {
- case STATE_SET:
- callback.distributionFound(Distribution.this);
- break;
- case STATE_NONE:
- callback.distributionNotFound();
- if (shouldDelayLateCallbacks) {
- onLateReady.add(callback);
- }
- break;
- default:
- throw new IllegalStateException("Expected STATE_NONE or STATE_SET, got " + state);
- }
- }
- });
- }
-
- /**
- * A safe way for callers to determine if this Distribution instance
- * represents a real live distribution.
- */
- public boolean exists() {
- return state == STATE_SET;
- }
-
- private String getKeyName() {
- return context.getPackageName() + ".distribution_state";
- }
-
- private SharedPreferences getSharedPreferences() {
- final SharedPreferences settings;
- if (prefsBranch == null) {
- settings = GeckoSharedPrefs.forApp(context);
- } else {
- settings = context.getSharedPreferences(prefsBranch, Activity.MODE_PRIVATE);
- }
- return settings;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/distribution/DistributionStoreCallback.java b/mobile/android/base/java/org/mozilla/gecko/distribution/DistributionStoreCallback.java
deleted file mode 100644
index 11ed4811f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/distribution/DistributionStoreCallback.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, you can obtain one at http://mozilla.org/MPL/2.0/.
- */
-
-package org.mozilla.gecko.distribution;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.util.Log;
-import org.mozilla.gecko.GeckoSharedPrefs;
-
-import java.lang.ref.WeakReference;
-
-/**
- * A distribution ready callback that will store the distribution ID to profile-specific shared preferences.
- */
-public class DistributionStoreCallback implements Distribution.ReadyCallback {
- private static final String LOGTAG = "Gecko" + DistributionStoreCallback.class.getSimpleName();
-
- public static final String PREF_DISTRIBUTION_ID = "distribution.id";
-
- private final WeakReference<Context> contextReference;
- private final String profileName;
-
- public DistributionStoreCallback(final Context context, final String profileName) {
- this.contextReference = new WeakReference<>(context);
- this.profileName = profileName;
- }
-
- public void distributionNotFound() { /* nothing to do here */ }
-
- @Override
- public void distributionFound(final Distribution distribution) {
- storeDistribution(distribution);
- }
-
- @Override
- public void distributionArrivedLate(final Distribution distribution) {
- storeDistribution(distribution);
- }
-
- private void storeDistribution(final Distribution distribution) {
- final Context context = contextReference.get();
- if (context == null) {
- Log.w(LOGTAG, "Context is no longer alive, could retrieve shared prefs to store distribution");
- return;
- }
-
- // While the distribution preferences are per install and not per profile, it's okay to use the
- // profile-specific prefs because:
- // 1) We don't really support mulitple profiles for end-users
- // 2) The TelemetryUploadService already accesses profile-specific shared prefs so this keeps things simple.
- final SharedPreferences sharedPrefs = GeckoSharedPrefs.forProfileName(context, profileName);
- final Distribution.DistributionDescriptor desc = distribution.getDescriptor();
- if (desc != null) {
- sharedPrefs.edit().putString(PREF_DISTRIBUTION_ID, desc.id).apply();
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/distribution/PartnerBookmarksProviderProxy.java b/mobile/android/base/java/org/mozilla/gecko/distribution/PartnerBookmarksProviderProxy.java
deleted file mode 100644
index 78a77221d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/distribution/PartnerBookmarksProviderProxy.java
+++ /dev/null
@@ -1,322 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.distribution;
-
-import android.content.ContentProvider;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.database.CursorWrapper;
-import android.net.Uri;
-import android.support.annotation.NonNull;
-import android.text.TextUtils;
-
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.db.BrowserContract;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * A proxy for the partner bookmarks provider. Bookmark and folder ids of the partner bookmarks providers
- * will be transformed so that they do not overlap with the ids from the local database.
- *
- * Bookmarks in folder:
- * content://{PACKAGE_ID}.partnerbookmarks/bookmarks/{folderId}
- * Icon of bookmark:
- * content://{PACKAGE_ID}.partnerbookmarks/icons/{bookmarkId}
- */
-public class PartnerBookmarksProviderProxy extends ContentProvider {
- /**
- * The contract between the partner bookmarks provider and applications. Contains the definition
- * for the supported URIs and columns.
- */
- public static class PartnerContract {
- public static final Uri CONTENT_URI = Uri.parse("content://com.android.partnerbookmarks/bookmarks");
-
- public static final int TYPE_BOOKMARK = 1;
- public static final int TYPE_FOLDER = 2;
-
- public static final int PARENT_ROOT_ID = 0;
-
- public static final String ID = "_id";
- public static final String TYPE = "type";
- public static final String URL = "url";
- public static final String TITLE = "title";
- public static final String FAVICON = "favicon";
- public static final String TOUCHICON = "touchicon";
- public static final String PARENT = "parent";
- }
-
- private static final String AUTHORITY_PREFIX = ".partnerbookmarks";
-
- private static final int URI_MATCH_BOOKMARKS = 1000;
- private static final int URI_MATCH_ICON = 1001;
- private static final int URI_MATCH_BOOKMARK = 1002;
-
- private static final String PREF_DELETED_PARTNER_BOOKMARKS = "distribution.partner.bookmark.deleted";
-
- /**
- * Cursor wrapper for filtering empty folders.
- */
- private static class FilteredCursor extends CursorWrapper {
- private HashSet<Integer> emptyFolderPositions;
- private int count;
-
- public FilteredCursor(PartnerBookmarksProviderProxy proxy, Cursor cursor) {
- super(cursor);
-
- emptyFolderPositions = new HashSet<>();
- count = cursor.getCount();
-
- for (int i = 0; i < cursor.getCount(); i++) {
- cursor.moveToPosition(i);
-
- final long id = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks._ID));
- final int type = cursor.getInt(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.TYPE));
-
- if (type == BrowserContract.Bookmarks.TYPE_FOLDER && proxy.isFolderEmpty(id)) {
- // We do not support deleting folders. So at least hide partner folders that are
- // empty because all bookmarks inside it are deleted/hidden.
- // Note that this will still show folders with empty folders in them. But multi-level
- // partner bookmarks are very unlikely.
-
- count--;
- emptyFolderPositions.add(i);
- }
- }
- }
-
- @Override
- public int getCount() {
- return count;
- }
-
- @Override
- public boolean moveToPosition(int position) {
- final Cursor cursor = getWrappedCursor();
- final int actualCount = cursor.getCount();
-
- // Find the next position pointing to a bookmark or a non-empty folder
- while (position < actualCount && emptyFolderPositions.contains(position)) {
- position++;
- }
-
- return position < actualCount && cursor.moveToPosition(position);
- }
- }
-
- private static String getAuthority(Context context) {
- return context.getPackageName() + AUTHORITY_PREFIX;
- }
-
- public static Uri getUriForBookmarks(Context context, long folderId) {
- return new Uri.Builder()
- .scheme("content")
- .authority(getAuthority(context))
- .appendPath("bookmarks")
- .appendPath(String.valueOf(folderId))
- .build();
- }
-
- public static Uri getUriForIcon(Context context, long bookmarkId) {
- return new Uri.Builder()
- .scheme("content")
- .authority(getAuthority(context))
- .appendPath("icons")
- .appendPath(String.valueOf(bookmarkId))
- .build();
- }
-
- public static Uri getUriForBookmark(Context context, long bookmarkId) {
- return new Uri.Builder()
- .scheme("content")
- .authority(getAuthority(context))
- .appendPath("bookmark")
- .appendPath(String.valueOf(bookmarkId))
- .build();
- }
-
- private final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
-
- @Override
- public boolean onCreate() {
- String authority = getAuthority(assertAndGetContext());
-
- uriMatcher.addURI(authority, "bookmarks/*", URI_MATCH_BOOKMARKS);
- uriMatcher.addURI(authority, "icons/*", URI_MATCH_ICON);
- uriMatcher.addURI(authority, "bookmark/*", URI_MATCH_BOOKMARK);
-
- return true;
- }
-
- @Override
- public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
- final Context context = assertAndGetContext();
- final int match = uriMatcher.match(uri);
-
- final ContentResolver contentResolver = context.getContentResolver();
-
- switch (match) {
- case URI_MATCH_BOOKMARKS:
- final long bookmarkId = ContentUris.parseId(uri);
- if (bookmarkId == -1) {
- throw new IllegalArgumentException("Bookmark id is not a number");
- }
- final Cursor cursor = getBookmarksInFolder(contentResolver, bookmarkId);
- cursor.setNotificationUri(context.getContentResolver(), uri);
- return new FilteredCursor(this, cursor);
-
- case URI_MATCH_ICON:
- return getIcon(contentResolver, ContentUris.parseId(uri));
-
- default:
- throw new UnsupportedOperationException("Unknown URI " + uri.toString());
- }
- }
-
- @Override
- public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
- final int match = uriMatcher.match(uri);
-
- switch (match) {
- case URI_MATCH_BOOKMARK:
- rememberRemovedBookmark(ContentUris.parseId(uri));
- notifyBookmarkChange();
- return 1;
-
- default:
- throw new UnsupportedOperationException("Unknown URI " + uri.toString());
- }
- }
-
- private void notifyBookmarkChange() {
- final Context context = assertAndGetContext();
-
- context.getContentResolver().notifyChange(
- new Uri.Builder()
- .scheme("content")
- .authority(getAuthority(context))
- .appendPath("bookmarks")
- .build(),
- null);
- }
-
- private synchronized void rememberRemovedBookmark(long bookmarkId) {
- Set<String> deletedIds = getRemovedBookmarkIds();
-
- deletedIds.add(String.valueOf(bookmarkId));
-
- GeckoSharedPrefs.forProfile(assertAndGetContext())
- .edit()
- .putStringSet(PREF_DELETED_PARTNER_BOOKMARKS, deletedIds)
- .apply();
- }
-
- private synchronized Set<String> getRemovedBookmarkIds() {
- SharedPreferences preferences = GeckoSharedPrefs.forProfile(assertAndGetContext());
- return preferences.getStringSet(PREF_DELETED_PARTNER_BOOKMARKS, new HashSet<String>());
- }
-
- private Cursor getBookmarksInFolder(ContentResolver contentResolver, long folderId) {
- // Use root folder id or transform negative id into actual (positive) folder id.
- final long actualFolderId = folderId == BrowserContract.Bookmarks.FIXED_ROOT_ID
- ? PartnerContract.PARENT_ROOT_ID
- : BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START - folderId;
-
- final String removedBookmarkIds = TextUtils.join(",", getRemovedBookmarkIds());
-
- return contentResolver.query(
- PartnerContract.CONTENT_URI,
- new String[] {
- // Transform ids into negative values starting with FAKE_PARTNER_BOOKMARKS_START.
- "(" + BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START + " - " + PartnerContract.ID + ") as " + BrowserContract.Bookmarks._ID,
- "(" + BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START + " - " + PartnerContract.ID + ") as " + BrowserContract.Combined.BOOKMARK_ID,
- PartnerContract.TITLE + " as " + BrowserContract.Bookmarks.TITLE,
- PartnerContract.URL + " as " + BrowserContract.Bookmarks.URL,
- // Transform parent ids to negative ids as well
- "(" + BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START + " - " + PartnerContract.PARENT + ") as " + BrowserContract.Bookmarks.PARENT,
- // Convert types (we use 0-1 and the partner provider 1-2)
- "(2 - " + PartnerContract.TYPE + ") as " + BrowserContract.Bookmarks.TYPE,
- // Use the ID of the entry as GUID
- PartnerContract.ID + " as " + BrowserContract.Bookmarks.GUID
- },
- PartnerContract.PARENT + " = ?"
- // We only want to read bookmarks or folders from the content provider
- + " AND " + BrowserContract.Bookmarks.TYPE + " IN (?,?)"
- // Only select entries with non empty title
- + " AND " + BrowserContract.Bookmarks.TITLE + " <> ''"
- // Filter all "deleted" ids
- + " AND " + BrowserContract.Combined.BOOKMARK_ID + " NOT IN (" + removedBookmarkIds + ")",
- new String[] {
- String.valueOf(actualFolderId),
- String.valueOf(PartnerContract.TYPE_BOOKMARK),
- String.valueOf(PartnerContract.TYPE_FOLDER)
- },
- // Same order we use in our content provider (without position)
- BrowserContract.Bookmarks.TYPE + " ASC, " + BrowserContract.Bookmarks._ID + " ASC");
- }
-
- private boolean isFolderEmpty(long folderId) {
- final Context context = assertAndGetContext();
- final Cursor cursor = getBookmarksInFolder(context.getContentResolver(), folderId);
-
- if (cursor == null) {
- return true;
- }
-
- try {
- return cursor.getCount() == 0;
- } finally {
- cursor.close();
- }
- }
-
- private Cursor getIcon(ContentResolver contentResolver, long bookmarkId) {
- final long actualId = BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START - bookmarkId;
-
- return contentResolver.query(
- PartnerContract.CONTENT_URI,
- new String[] {
- PartnerContract.TOUCHICON,
- PartnerContract.FAVICON
- },
- PartnerContract.ID + " = ?",
- new String[] {
- String.valueOf(actualId)
- },
- null);
- }
-
- private Context assertAndGetContext() {
- final Context context = super.getContext();
-
- if (context == null) {
- throw new AssertionError("Context is null");
- }
-
- return context;
- }
-
- @Override
- public String getType(@NonNull Uri uri) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Uri insert(@NonNull Uri uri, ContentValues values) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/distribution/PartnerBrowserCustomizationsClient.java b/mobile/android/base/java/org/mozilla/gecko/distribution/PartnerBrowserCustomizationsClient.java
deleted file mode 100644
index 2dad21a48..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/distribution/PartnerBrowserCustomizationsClient.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.distribution;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-
-/**
- * Client for accessing data from Android's "partner browser customizations" content provider.
- */
-public class PartnerBrowserCustomizationsClient {
- private static final Uri CONTENT_URI = Uri.parse("content://com.android.partnerbrowsercustomizations");
-
- private static final Uri HOMEPAGE_URI = CONTENT_URI.buildUpon().path("homepage").build();
-
- private static final String COLUMN_HOMEPAGE = "homepage";
-
- /**
- * Returns the partner homepage or null if it could not be read from the content provider.
- */
- public static String getHomepage(Context context) {
- Cursor cursor = context.getContentResolver().query(
- HOMEPAGE_URI, new String[] { COLUMN_HOMEPAGE }, null, null, null);
-
- if (cursor == null) {
- return null;
- }
-
- try {
- if (!cursor.moveToFirst()) {
- return null;
- }
-
- return cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_HOMEPAGE));
- } finally {
- cursor.close();
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/distribution/ReferrerDescriptor.java b/mobile/android/base/java/org/mozilla/gecko/distribution/ReferrerDescriptor.java
deleted file mode 100644
index 4a1be656b..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/distribution/ReferrerDescriptor.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.distribution;
-
-import org.mozilla.gecko.annotation.RobocopTarget;
-
-import android.net.Uri;
-
-import java.net.URLDecoder;
-import java.io.UnsupportedEncodingException;
-
-/**
- * Encapsulates access to values encoded in the "referrer" extra of an install intent.
- *
- * This object is immutable.
- *
- * Example input:
- *
- * "utm_source=campsource&utm_medium=campmed&utm_term=term%2Bhere&utm_content=content&utm_campaign=name"
- */
-@RobocopTarget
-public class ReferrerDescriptor {
- public final String source;
- public final String medium;
- public final String term;
- public final String content;
- public final String campaign;
-
- public ReferrerDescriptor(String referrer) {
- if (referrer == null) {
- source = null;
- medium = null;
- term = null;
- content = null;
- campaign = null;
- return;
- }
-
- try {
- referrer = URLDecoder.decode(referrer, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- // UTF-8 is always supported
- }
-
- final Uri u = new Uri.Builder()
- .scheme("http")
- .authority("local")
- .path("/")
- .encodedQuery(referrer).build();
-
- source = u.getQueryParameter("utm_source");
- medium = u.getQueryParameter("utm_medium");
- term = u.getQueryParameter("utm_term");
- content = u.getQueryParameter("utm_content");
- campaign = u.getQueryParameter("utm_campaign");
- }
-
- @Override
- public String toString() {
- return "{s: " + source + ", m: " + medium + ", t: " + term + ", c: " + content + ", c: " + campaign + "}";
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/distribution/ReferrerReceiver.java b/mobile/android/base/java/org/mozilla/gecko/distribution/ReferrerReceiver.java
deleted file mode 100644
index 3651d6068..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/distribution/ReferrerReceiver.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.distribution;
-
-import org.mozilla.gecko.AdjustConstants;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoAppShell;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.support.v4.content.LocalBroadcastManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-public class ReferrerReceiver extends BroadcastReceiver {
- private static final String LOGTAG = "GeckoReferrerReceiver";
-
- private static final String ACTION_INSTALL_REFERRER = "com.android.vending.INSTALL_REFERRER";
-
- // Sent when we're done.
- @RobocopTarget
- public static final String ACTION_REFERRER_RECEIVED = "org.mozilla.fennec.REFERRER_RECEIVED";
-
- /**
- * If the install intent has this source, it is a Mozilla specific or over
- * the air distribution referral. We'll track the campaign ID using
- * Mozilla's metrics systems.
- *
- * If the install intent has a source different than this one, it is a
- * referral from an advertising network. We may track these campaigns using
- * third-party tracking and metrics systems.
- */
- private static final String MOZILLA_UTM_SOURCE = "mozilla";
-
- /**
- * If the install intent has this campaign, we'll load the specified distribution.
- */
- private static final String DISTRIBUTION_UTM_CAMPAIGN = "distribution";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.v(LOGTAG, "Received intent " + intent);
- if (!ACTION_INSTALL_REFERRER.equals(intent.getAction())) {
- // This should never happen.
- return;
- }
-
- // Track the referrer object for distribution handling.
- ReferrerDescriptor referrer = new ReferrerDescriptor(intent.getStringExtra("referrer"));
-
- if (!TextUtils.equals(referrer.source, MOZILLA_UTM_SOURCE)) {
- // Allow the Adjust handler to process the intent.
- try {
- AdjustConstants.getAdjustHelper().onReceive(context, intent);
- } catch (Exception e) {
- Log.e(LOGTAG, "Got exception in Adjust's onReceive; ignoring referrer intent.", e);
- }
- return;
- }
-
- if (TextUtils.equals(referrer.campaign, DISTRIBUTION_UTM_CAMPAIGN)) {
- Distribution.onReceivedReferrer(context, referrer);
- // We want Adjust information for OTA distributions as well
- try {
- AdjustConstants.getAdjustHelper().onReceive(context, intent);
- } catch (Exception e) {
- Log.e(LOGTAG, "Got exception in Adjust's onReceive for distribution.", e);
- }
- } else {
- Log.d(LOGTAG, "Not downloading distribution: non-matching campaign.");
- // If this is a Mozilla campaign, pass the campaign along to Gecko.
- // It'll pretend to be a "playstore" distribution for BLP purposes.
- propagateMozillaCampaign(referrer);
- }
-
- // Broadcast a secondary, local intent to allow test code to respond.
- final Intent receivedIntent = new Intent(ACTION_REFERRER_RECEIVED);
- LocalBroadcastManager.getInstance(context).sendBroadcast(receivedIntent);
- }
-
-
- private void propagateMozillaCampaign(ReferrerDescriptor referrer) {
- if (referrer.campaign == null) {
- return;
- }
-
- try {
- final JSONObject data = new JSONObject();
- data.put("id", "playstore");
- data.put("version", referrer.campaign);
- String payload = data.toString();
-
- // Try to make sure the prefs are written as a group.
- GeckoAppShell.notifyObservers("Campaign:Set", payload);
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error propagating campaign identifier.", e);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/dlc/BaseAction.java b/mobile/android/base/java/org/mozilla/gecko/dlc/BaseAction.java
deleted file mode 100644
index 28d6b238d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/BaseAction.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.dlc;
-
-import android.content.Context;
-import android.support.annotation.IntDef;
-import android.util.Log;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.background.nativecode.NativeCrypto;
-import org.mozilla.gecko.dlc.catalog.DownloadContent;
-import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.IOUtils;
-import org.mozilla.gecko.util.ProxySelector;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URISyntaxException;
-
-public abstract class BaseAction {
- private static final String LOGTAG = "GeckoDLCBaseAction";
-
- /**
- * Exception indicating a recoverable error has happened. Download of the content will be retried later.
- */
- /* package-private */ static class RecoverableDownloadContentException extends Exception {
- private static final long serialVersionUID = -2246772819507370734L;
-
- @IntDef({MEMORY, DISK_IO, SERVER, NETWORK})
- public @interface ErrorType {}
- public static final int MEMORY = 1;
- public static final int DISK_IO = 2;
- public static final int SERVER = 3;
- public static final int NETWORK = 4;
-
- private int errorType;
-
- public RecoverableDownloadContentException(@ErrorType int errorType, String message) {
- super(message);
- this.errorType = errorType;
- }
-
- public RecoverableDownloadContentException(@ErrorType int errorType, Throwable cause) {
- super(cause);
- this.errorType = errorType;
- }
-
- @ErrorType
- public int getErrorType() {
- return errorType;
- }
-
- /**
- * Should this error be counted as failure? If this type of error will happen multiple times in a row then this
- * error will be treated as permanently and the operation will not be tried again until the content changes.
- */
- public boolean shouldBeCountedAsFailure() {
- if (NETWORK == errorType) {
- return false; // Always retry after network errors
- }
-
- return true;
- }
- }
-
- /**
- * If this exception is thrown the content will be marked as unrecoverable, permanently failed and we will not try
- * downloading it again - until a newer version of the content is available.
- */
- /* package-private */ static class UnrecoverableDownloadContentException extends Exception {
- private static final long serialVersionUID = 8956080754787367105L;
-
- public UnrecoverableDownloadContentException(String message) {
- super(message);
- }
-
- public UnrecoverableDownloadContentException(Throwable cause) {
- super(cause);
- }
- }
-
- public abstract void perform(Context context, DownloadContentCatalog catalog);
-
- protected File getDestinationFile(Context context, DownloadContent content)
- throws UnrecoverableDownloadContentException, RecoverableDownloadContentException {
- if (content.isFont()) {
- File destinationDirectory = new File(context.getApplicationInfo().dataDir, "fonts");
-
- if (!destinationDirectory.exists() && !destinationDirectory.mkdirs()) {
- throw new RecoverableDownloadContentException(RecoverableDownloadContentException.DISK_IO,
- "Destination directory does not exist and cannot be created");
- }
-
- return new File(destinationDirectory, content.getFilename());
- }
-
- // Unrecoverable: We downloaded a file and we don't know what to do with it (Should not happen)
- throw new UnrecoverableDownloadContentException("Can't determine destination for kind: " + content.getKind());
- }
-
- protected boolean verify(File file, String expectedChecksum)
- throws RecoverableDownloadContentException, UnrecoverableDownloadContentException {
- InputStream inputStream = null;
-
- try {
- inputStream = new BufferedInputStream(new FileInputStream(file));
-
- byte[] ctx = NativeCrypto.sha256init();
- if (ctx == null) {
- throw new RecoverableDownloadContentException(RecoverableDownloadContentException.MEMORY,
- "Could not create SHA-256 context");
- }
-
- byte[] buffer = new byte[4096];
- int read;
-
- while ((read = inputStream.read(buffer)) != -1) {
- NativeCrypto.sha256update(ctx, buffer, read);
- }
-
- String actualChecksum = Utils.byte2Hex(NativeCrypto.sha256finalize(ctx));
-
- if (!actualChecksum.equalsIgnoreCase(expectedChecksum)) {
- Log.w(LOGTAG, "Checksums do not match. Expected=" + expectedChecksum + ", Actual=" + actualChecksum);
- return false;
- }
-
- return true;
- } catch (IOException e) {
- // Recoverable: Just I/O discontinuation
- throw new RecoverableDownloadContentException(RecoverableDownloadContentException.DISK_IO, e);
- } finally {
- IOUtils.safeStreamClose(inputStream);
- }
- }
-
- protected HttpURLConnection buildHttpURLConnection(String url)
- throws UnrecoverableDownloadContentException, IOException {
- try {
- System.setProperty("http.keepAlive", "true");
-
- HttpURLConnection connection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(new URI(url));
- connection.setRequestProperty("User-Agent", HardwareUtils.isTablet() ?
- AppConstants.USER_AGENT_FENNEC_TABLET :
- AppConstants.USER_AGENT_FENNEC_MOBILE);
- connection.setRequestMethod("GET");
- connection.setInstanceFollowRedirects(true);
- return connection;
- } catch (MalformedURLException e) {
- throw new UnrecoverableDownloadContentException(e);
- } catch (URISyntaxException e) {
- throw new UnrecoverableDownloadContentException(e);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/dlc/CleanupAction.java b/mobile/android/base/java/org/mozilla/gecko/dlc/CleanupAction.java
deleted file mode 100644
index e44704c6c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/CleanupAction.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.dlc;
-
-import android.content.Context;
-
-import org.mozilla.gecko.dlc.catalog.DownloadContent;
-import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
-
-import java.io.File;
-
-/**
- * CleanupAction: Remove content that is no longer needed.
- */
-public class CleanupAction extends BaseAction {
- @Override
- public void perform(Context context, DownloadContentCatalog catalog) {
- for (DownloadContent content : catalog.getContentToDelete()) {
- if (!content.isAssetArchive()) {
- continue; // We do not know how to clean up this content. But this means we didn't
- // download it anyways.
- }
-
- try {
- File file = getDestinationFile(context, content);
-
- if (!file.exists()) {
- // File does not exist. As good as deleting.
- catalog.remove(content);
- return;
- }
-
- if (file.delete()) {
- // File has been deleted. Now remove it from the catalog.
- catalog.remove(content);
- }
- } catch (UnrecoverableDownloadContentException e) {
- // We can't recover. Pretend the content is removed. It probably never existed in
- // the first place.
- catalog.remove(content);
- } catch (RecoverableDownloadContentException e) {
- // Try again next time.
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadAction.java b/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadAction.java
deleted file mode 100644
index 8618d4699..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadAction.java
+++ /dev/null
@@ -1,325 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.dlc;
-
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.support.v4.net.ConnectivityManagerCompat;
-import android.util.Log;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.dlc.catalog.DownloadContent;
-import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.IOUtils;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.zip.GZIPInputStream;
-
-/**
- * Download content that has been scheduled during "study" or "verify".
- */
-public class DownloadAction extends BaseAction {
- private static final String LOGTAG = "DLCDownloadAction";
-
- private static final String CACHE_DIRECTORY = "downloadContent";
-
- private static final String CDN_BASE_URL = "https://fennec-catalog.cdn.mozilla.net/";
-
- public interface Callback {
- void onContentDownloaded(DownloadContent content);
- }
-
- private Callback callback;
-
- public DownloadAction(Callback callback) {
- this.callback = callback;
- }
-
- public void perform(Context context, DownloadContentCatalog catalog) {
- Log.d(LOGTAG, "Downloading content..");
-
- if (!isConnectedToNetwork(context)) {
- Log.d(LOGTAG, "No connected network available. Postponing download.");
- // TODO: Reschedule download (bug 1209498)
- return;
- }
-
- if (isActiveNetworkMetered(context)) {
- Log.d(LOGTAG, "Network is metered. Postponing download.");
- // TODO: Reschedule download (bug 1209498)
- return;
- }
-
- for (DownloadContent content : catalog.getScheduledDownloads()) {
- Log.d(LOGTAG, "Downloading: " + content);
-
- File temporaryFile = null;
-
- try {
- File destinationFile = getDestinationFile(context, content);
- if (destinationFile.exists() && verify(destinationFile, content.getChecksum())) {
- Log.d(LOGTAG, "Content already exists and is up-to-date.");
- catalog.markAsDownloaded(content);
- continue;
- }
-
- temporaryFile = createTemporaryFile(context, content);
-
- if (!hasEnoughDiskSpace(content, destinationFile, temporaryFile)) {
- Log.d(LOGTAG, "Not enough disk space to save content. Skipping download.");
- continue;
- }
-
- // TODO: Check space on disk before downloading content (bug 1220145)
- final String url = createDownloadURL(content);
-
- if (!temporaryFile.exists() || temporaryFile.length() < content.getSize()) {
- download(url, temporaryFile);
- }
-
- if (!verify(temporaryFile, content.getDownloadChecksum())) {
- Log.w(LOGTAG, "Wrong checksum after download, content=" + content.getId());
- temporaryFile.delete();
- continue;
- }
-
- if (!content.isAssetArchive()) {
- Log.e(LOGTAG, "Downloaded content is not of type 'asset-archive': " + content.getType());
- temporaryFile.delete();
- continue;
- }
-
- extract(temporaryFile, destinationFile, content.getChecksum());
-
- catalog.markAsDownloaded(content);
-
- Log.d(LOGTAG, "Successfully downloaded: " + content);
-
- if (callback != null) {
- callback.onContentDownloaded(content);
- }
-
- if (temporaryFile != null && temporaryFile.exists()) {
- temporaryFile.delete();
- }
- } catch (RecoverableDownloadContentException e) {
- Log.w(LOGTAG, "Downloading content failed (Recoverable): " + content, e);
-
- if (e.shouldBeCountedAsFailure()) {
- catalog.rememberFailure(content, e.getErrorType());
- }
-
- // TODO: Reschedule download (bug 1209498)
- } catch (UnrecoverableDownloadContentException e) {
- Log.w(LOGTAG, "Downloading content failed (Unrecoverable): " + content, e);
-
- catalog.markAsPermanentlyFailed(content);
-
- if (temporaryFile != null && temporaryFile.exists()) {
- temporaryFile.delete();
- }
- }
- }
-
- Log.v(LOGTAG, "Done");
- }
-
- protected void download(String source, File temporaryFile)
- throws RecoverableDownloadContentException, UnrecoverableDownloadContentException {
- InputStream inputStream = null;
- OutputStream outputStream = null;
-
- HttpURLConnection connection = null;
-
- try {
- connection = buildHttpURLConnection(source);
-
- final long offset = temporaryFile.exists() ? temporaryFile.length() : 0;
- if (offset > 0) {
- connection.setRequestProperty("Range", "bytes=" + offset + "-");
- }
-
- final int status = connection.getResponseCode();
- if (status != HttpURLConnection.HTTP_OK && status != HttpURLConnection.HTTP_PARTIAL) {
- // We are trying to be smart and only retry if this is an error that might resolve in the future.
- // TODO: This is guesstimating at best. We want to implement failure counters (Bug 1215106).
- if (status >= 500) {
- // Recoverable: Server errors 5xx
- throw new RecoverableDownloadContentException(RecoverableDownloadContentException.SERVER,
- "(Recoverable) Download failed. Status code: " + status);
- } else if (status >= 400) {
- // Unrecoverable: Client errors 4xx - Unlikely that this version of the client will ever succeed.
- throw new UnrecoverableDownloadContentException("(Unrecoverable) Download failed. Status code: " + status);
- } else {
- // HttpsUrlConnection: -1 (No valid response code)
- // Informational 1xx: They have no meaning to us.
- // Successful 2xx: We don't know how to handle anything but 200.
- // Redirection 3xx: HttpClient should have followed redirects if possible. We should not see those errors here.
- throw new UnrecoverableDownloadContentException("(Unrecoverable) Download failed. Status code: " + status);
- }
- }
-
- inputStream = new BufferedInputStream(connection.getInputStream());
- outputStream = openFile(temporaryFile, status == HttpURLConnection.HTTP_PARTIAL);
-
- IOUtils.copy(inputStream, outputStream);
-
- inputStream.close();
- outputStream.close();
- } catch (IOException e) {
- // Recoverable: Just I/O discontinuation
- throw new RecoverableDownloadContentException(RecoverableDownloadContentException.NETWORK, e);
- } finally {
- IOUtils.safeStreamClose(inputStream);
- IOUtils.safeStreamClose(outputStream);
-
- if (connection != null) {
- connection.disconnect();
- }
- }
- }
-
- protected OutputStream openFile(File file, boolean append) throws FileNotFoundException {
- return new BufferedOutputStream(new FileOutputStream(file, append));
- }
-
- protected void extract(File sourceFile, File destinationFile, String checksum)
- throws UnrecoverableDownloadContentException, RecoverableDownloadContentException {
- InputStream inputStream = null;
- OutputStream outputStream = null;
- File temporaryFile = null;
-
- try {
- File destinationDirectory = destinationFile.getParentFile();
- if (!destinationDirectory.exists() && !destinationDirectory.mkdirs()) {
- throw new IOException("Destination directory does not exist and cannot be created");
- }
-
- temporaryFile = new File(destinationDirectory, destinationFile.getName() + ".tmp");
-
- inputStream = new GZIPInputStream(new BufferedInputStream(new FileInputStream(sourceFile)));
- outputStream = new BufferedOutputStream(new FileOutputStream(temporaryFile));
-
- IOUtils.copy(inputStream, outputStream);
-
- inputStream.close();
- outputStream.close();
-
- if (!verify(temporaryFile, checksum)) {
- Log.w(LOGTAG, "Checksum of extracted file does not match.");
- return;
- }
-
- move(temporaryFile, destinationFile);
- } catch (IOException e) {
- // We could not extract to the destination: Keep temporary file and try again next time we run.
- throw new RecoverableDownloadContentException(RecoverableDownloadContentException.DISK_IO, e);
- } finally {
- IOUtils.safeStreamClose(inputStream);
- IOUtils.safeStreamClose(outputStream);
-
- if (temporaryFile != null && temporaryFile.exists()) {
- temporaryFile.delete();
- }
- }
- }
-
- protected boolean isConnectedToNetwork(Context context) {
- ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo networkInfo = manager.getActiveNetworkInfo();
-
- return networkInfo != null && networkInfo.isConnected();
- }
-
- protected boolean isActiveNetworkMetered(Context context) {
- return ConnectivityManagerCompat.isActiveNetworkMetered(
- (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE));
- }
-
- protected String createDownloadURL(DownloadContent content) {
- final String location = content.getLocation();
-
- return CDN_BASE_URL + content.getLocation();
- }
-
- protected File createTemporaryFile(Context context, DownloadContent content)
- throws RecoverableDownloadContentException {
- File cacheDirectory = new File(context.getCacheDir(), CACHE_DIRECTORY);
-
- if (!cacheDirectory.exists() && !cacheDirectory.mkdirs()) {
- // Recoverable: File system might not be mounted NOW and we didn't download anything yet anyways.
- throw new RecoverableDownloadContentException(RecoverableDownloadContentException.DISK_IO,
- "Could not create cache directory: " + cacheDirectory);
- }
-
- return new File(cacheDirectory, content.getDownloadChecksum() + "-" + content.getId());
- }
-
- protected void move(File temporaryFile, File destinationFile)
- throws RecoverableDownloadContentException, UnrecoverableDownloadContentException {
- if (!temporaryFile.renameTo(destinationFile)) {
- Log.d(LOGTAG, "Could not move temporary file to destination. Trying to copy..");
- copy(temporaryFile, destinationFile);
- temporaryFile.delete();
- }
- }
-
- protected void copy(File temporaryFile, File destinationFile)
- throws RecoverableDownloadContentException, UnrecoverableDownloadContentException {
- InputStream inputStream = null;
- OutputStream outputStream = null;
-
- try {
- File destinationDirectory = destinationFile.getParentFile();
- if (!destinationDirectory.exists() && !destinationDirectory.mkdirs()) {
- throw new IOException("Destination directory does not exist and cannot be created");
- }
-
- inputStream = new BufferedInputStream(new FileInputStream(temporaryFile));
- outputStream = new BufferedOutputStream(new FileOutputStream(destinationFile));
-
- IOUtils.copy(inputStream, outputStream);
-
- inputStream.close();
- outputStream.close();
- } catch (IOException e) {
- // We could not copy the temporary file to its destination: Keep the temporary file and
- // try again the next time we run.
- throw new RecoverableDownloadContentException(RecoverableDownloadContentException.DISK_IO, e);
- } finally {
- IOUtils.safeStreamClose(inputStream);
- IOUtils.safeStreamClose(outputStream);
- }
- }
-
- protected boolean hasEnoughDiskSpace(DownloadContent content, File destinationFile, File temporaryFile) {
- final File temporaryDirectory = temporaryFile.getParentFile();
- if (temporaryDirectory.getUsableSpace() < content.getSize()) {
- return false;
- }
-
- final File destinationDirectory = destinationFile.getParentFile();
- // We need some more space to extract the file (getSize() returns the uncompressed size)
- if (destinationDirectory.getUsableSpace() < content.getSize() * 2) {
- return false;
- }
-
- return true;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadContentService.java b/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadContentService.java
deleted file mode 100644
index 3729cf2e0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadContentService.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.dlc;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.dlc.catalog.DownloadContent;
-import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
-import org.mozilla.gecko.util.HardwareUtils;
-
-import android.app.IntentService;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-/**
- * Service to handle downloadable content that did not ship with the APK.
- */
-public class DownloadContentService extends IntentService {
- private static final String LOGTAG = "GeckoDLCService";
-
- /**
- * Study: Scan the catalog for "new" content available for download.
- */
- private static final String ACTION_STUDY_CATALOG = AppConstants.ANDROID_PACKAGE_NAME + ".DLC.STUDY";
-
- /**
- * Verify: Validate downloaded content. Does it still exist and does it have the correct checksum?
- */
- private static final String ACTION_VERIFY_CONTENT = AppConstants.ANDROID_PACKAGE_NAME + ".DLC.VERIFY";
-
- /**
- * Download content that has been scheduled during "study" or "verify".
- */
- private static final String ACTION_DOWNLOAD_CONTENT = AppConstants.ANDROID_PACKAGE_NAME + ".DLC.DOWNLOAD";
-
- /**
- * Sync: Synchronize catalog from a Kinto instance.
- */
- private static final String ACTION_SYNCHRONIZE_CATALOG = AppConstants.ANDROID_PACKAGE_NAME + ".DLC.SYNC";
-
- /**
- * CleanupAction: Remove content that is no longer needed (e.g. Removed from the catalog after a sync).
- */
- private static final String ACTION_CLEANUP_FILES = AppConstants.ANDROID_PACKAGE_NAME + ".DLC.CLEANUP";
-
- public static void startStudy(Context context) {
- Intent intent = new Intent(ACTION_STUDY_CATALOG);
- intent.setComponent(new ComponentName(context, DownloadContentService.class));
- context.startService(intent);
- }
-
- public static void startVerification(Context context) {
- Intent intent = new Intent(ACTION_VERIFY_CONTENT);
- intent.setComponent(new ComponentName(context, DownloadContentService.class));
- context.startService(intent);
- }
-
- public static void startDownloads(Context context) {
- Intent intent = new Intent(ACTION_DOWNLOAD_CONTENT);
- intent.setComponent(new ComponentName(context, DownloadContentService.class));
- context.startService(intent);
- }
-
- public static void startSync(Context context) {
- Intent intent = new Intent(ACTION_SYNCHRONIZE_CATALOG);
- intent.setComponent(new ComponentName(context, DownloadContentService.class));
- context.startService(intent);
- }
-
- public static void startCleanup(Context context) {
- Intent intent = new Intent(ACTION_CLEANUP_FILES);
- intent.setComponent(new ComponentName(context, DownloadContentService.class));
- context.startService(intent);
- }
-
- private DownloadContentCatalog catalog;
-
- public DownloadContentService() {
- super(LOGTAG);
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
-
- catalog = new DownloadContentCatalog(this);
- }
-
- protected void onHandleIntent(Intent intent) {
- if (!AppConstants.MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE) {
- Log.w(LOGTAG, "Download content is not enabled. Stop.");
- return;
- }
-
- if (!HardwareUtils.isSupportedSystem()) {
- // This service is running very early before checks in BrowserApp can prevent us from running.
- Log.w(LOGTAG, "System is not supported. Stop.");
- return;
- }
-
- if (intent == null) {
- return;
- }
-
- final BaseAction action;
-
- switch (intent.getAction()) {
- case ACTION_STUDY_CATALOG:
- action = new StudyAction();
- break;
-
- case ACTION_DOWNLOAD_CONTENT:
- action = new DownloadAction(new DownloadAction.Callback() {
- @Override
- public void onContentDownloaded(DownloadContent content) {
- if (content.isFont()) {
- GeckoAppShell.notifyObservers("Fonts:Reload", "");
- }
- }
- });
- break;
-
- case ACTION_VERIFY_CONTENT:
- action = new VerifyAction();
- break;
-
- case ACTION_SYNCHRONIZE_CATALOG:
- action = new SyncAction();
- break;
-
- default:
- Log.e(LOGTAG, "Unknown action: " + intent.getAction());
- return;
- }
-
- action.perform(this, catalog);
- catalog.persistChanges();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/dlc/StudyAction.java b/mobile/android/base/java/org/mozilla/gecko/dlc/StudyAction.java
deleted file mode 100644
index e15a17bbe..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/StudyAction.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.dlc;
-
-import android.content.Context;
-import android.os.Build;
-import android.text.TextUtils;
-import android.util.Log;
-
-import org.mozilla.gecko.dlc.catalog.DownloadContent;
-import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
-import org.mozilla.gecko.util.ContextUtils;
-
-/**
- * Study: Scan the catalog for "new" content available for download.
- */
-public class StudyAction extends BaseAction {
- private static final String LOGTAG = "DLCStudyAction";
-
- public void perform(Context context, DownloadContentCatalog catalog) {
- Log.d(LOGTAG, "Studying catalog..");
-
- for (DownloadContent content : catalog.getContentToStudy()) {
- if (!isMatching(context, content)) {
- // This content is not for this particular version of the application or system
- continue;
- }
-
- if (content.isAssetArchive() && content.isFont()) {
- catalog.scheduleDownload(content);
-
- Log.d(LOGTAG, "Scheduled download: " + content);
- }
- }
-
- if (catalog.hasScheduledDownloads()) {
- startDownloads(context);
- }
-
- Log.v(LOGTAG, "Done");
- }
-
- protected boolean isMatching(Context context, DownloadContent content) {
- final String androidApiPattern = content.getAndroidApiPattern();
- if (!TextUtils.isEmpty(androidApiPattern)) {
- final String apiVersion = String.valueOf(Build.VERSION.SDK_INT);
- if (apiVersion.matches(androidApiPattern)) {
- Log.d(LOGTAG, String.format("Android API (%s) does not match pattern: %s", apiVersion, androidApiPattern));
- return false;
- }
- }
-
- final String appIdPattern = content.getAppIdPattern();
- if (!TextUtils.isEmpty(appIdPattern)) {
- final String appId = context.getPackageName();
- if (!appId.matches(appIdPattern)) {
- Log.d(LOGTAG, String.format("App ID (%s) does not match pattern: %s", appId, appIdPattern));
- return false;
- }
- }
-
- final String appVersionPattern = content.getAppVersionPattern();
- if (!TextUtils.isEmpty(appVersionPattern)) {
- final String appVersion = ContextUtils.getCurrentPackageInfo(context).versionName;
- if (!appVersion.matches(appVersionPattern)) {
- Log.d(LOGTAG, String.format("App version (%s) does not match pattern: %s", appVersion, appVersionPattern));
- return false;
- }
- }
-
- // There are no patterns or all patterns have matched.
- return true;
- }
-
- protected void startDownloads(Context context) {
- DownloadContentService.startDownloads(context);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/dlc/SyncAction.java b/mobile/android/base/java/org/mozilla/gecko/dlc/SyncAction.java
deleted file mode 100644
index 104bdad18..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/SyncAction.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.dlc;
-
-import android.content.Context;
-import android.net.Uri;
-import android.util.Log;
-
-import com.keepsafe.switchboard.SwitchBoard;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.dlc.catalog.DownloadContent;
-import org.mozilla.gecko.dlc.catalog.DownloadContentBuilder;
-import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
-import org.mozilla.gecko.Experiments;
-import org.mozilla.gecko.util.IOUtils;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-
-/**
- * Sync: Synchronize catalog from a Kinto instance.
- */
-public class SyncAction extends BaseAction {
- private static final String LOGTAG = "DLCSyncAction";
-
- private static final String KINTO_KEY_ID = "id";
- private static final String KINTO_KEY_DELETED = "deleted";
- private static final String KINTO_KEY_DATA = "data";
- private static final String KINTO_KEY_ATTACHMENT = "attachment";
- private static final String KINTO_KEY_ORIGINAL = "original";
-
- private static final String KINTO_PARAMETER_SINCE = "_since";
- private static final String KINTO_PARAMETER_FIELDS = "_fields";
- private static final String KINTO_PARAMETER_SORT = "_sort";
-
- /**
- * Kinto endpoint with online version of downloadable content catalog
- *
- * Dev instance:
- * https://kinto-ota.dev.mozaws.net/v1/buckets/dlc/collections/catalog/records
- */
- private static final String CATALOG_ENDPOINT = "https://firefox.settings.services.mozilla.com/v1/buckets/fennec/collections/catalog/records";
-
- @Override
- public void perform(Context context, DownloadContentCatalog catalog) {
- Log.d(LOGTAG, "Synchronizing catalog.");
-
- if (!isSyncEnabledForClient(context)) {
- Log.d(LOGTAG, "Sync is not enabled for client. Skipping.");
- return;
- }
-
- boolean cleanupRequired = false;
- boolean studyRequired = false;
-
- try {
- long lastModified = catalog.getLastModified();
-
- // TODO: Consider using ETag here (Bug 1257459)
- JSONArray rawCatalog = fetchRawCatalog(lastModified);
-
- Log.d(LOGTAG, "Server returned " + rawCatalog.length() + " records (since " + lastModified + ")");
-
- for (int i = 0; i < rawCatalog.length(); i++) {
- JSONObject object = rawCatalog.getJSONObject(i);
- String id = object.getString(KINTO_KEY_ID);
-
- final boolean isDeleted = object.optBoolean(KINTO_KEY_DELETED, false);
-
- if (!isDeleted) {
- JSONObject attachment = object.getJSONObject(KINTO_KEY_ATTACHMENT);
- if (attachment.isNull(KINTO_KEY_ORIGINAL))
- throw new JSONException(String.format("Old Attachment Format"));
- }
-
- DownloadContent existingContent = catalog.getContentById(id);
-
- if (isDeleted) {
- cleanupRequired |= deleteContent(catalog, id);
- } else if (existingContent != null) {
- studyRequired |= updateContent(catalog, object, existingContent);
- } else {
- studyRequired |= createContent(catalog, object);
- }
- }
- } catch (UnrecoverableDownloadContentException e) {
- Log.e(LOGTAG, "UnrecoverableDownloadContentException", e);
- } catch (RecoverableDownloadContentException e) {
- Log.e(LOGTAG, "RecoverableDownloadContentException");
- } catch (JSONException e) {
- Log.e(LOGTAG, "JSONException", e);
- }
-
- if (studyRequired) {
- startStudyAction(context);
- }
-
- if (cleanupRequired) {
- startCleanupAction(context);
- }
-
- Log.v(LOGTAG, "Done");
- }
-
- protected void startStudyAction(Context context) {
- DownloadContentService.startStudy(context);
- }
-
- protected void startCleanupAction(Context context) {
- DownloadContentService.startCleanup(context);
- }
-
- protected JSONArray fetchRawCatalog(long lastModified)
- throws RecoverableDownloadContentException, UnrecoverableDownloadContentException {
- HttpURLConnection connection = null;
-
- try {
- Uri.Builder builder = Uri.parse(CATALOG_ENDPOINT).buildUpon();
-
- if (lastModified > 0) {
- builder.appendQueryParameter(KINTO_PARAMETER_SINCE, String.valueOf(lastModified));
- }
- // Only select the fields we are actually going to read.
- builder.appendQueryParameter(KINTO_PARAMETER_FIELDS,
- "attachment.location,attachment.original.filename,attachment.original.hash,attachment.hash,type,kind,attachment.original.size,match");
-
- // We want to process items in the order they have been modified. This is to ensure that
- // our last_modified values are correct if we processing is interrupted and not all items
- // have been processed.
- builder.appendQueryParameter(KINTO_PARAMETER_SORT, "last_modified");
-
- connection = buildHttpURLConnection(builder.build().toString());
-
- // TODO: Read 'Alert' header and EOL message if existing (Bug 1249248)
-
- // TODO: Read and use 'Backoff' header if available (Bug 1249251)
-
- // TODO: Add support for Next-Page header (Bug 1257495)
-
- final int responseCode = connection.getResponseCode();
-
- if (responseCode != HttpURLConnection.HTTP_OK) {
- if (responseCode >= 500) {
- // A Retry-After header will be added to error responses (>=500), telling the
- // client how many seconds it should wait before trying again.
-
- // TODO: Read and obey value in "Retry-After" header (Bug 1249249)
- throw new RecoverableDownloadContentException(RecoverableDownloadContentException.SERVER, "Server error (" + responseCode + ")");
- } else if (responseCode == 410) {
- // A 410 Gone error response can be returned if the client version is too old,
- // or the service had been replaced with a new and better service using a new
- // protocol version.
-
- // TODO: The server is gone. Stop synchronizing the catalog from this server (Bug 1249248).
- throw new UnrecoverableDownloadContentException("Server is gone (410)");
- } else if (responseCode >= 400) {
- // If the HTTP status is >=400 the response contains a JSON response.
- logErrorResponse(connection);
-
- // Unrecoverable: Client errors 4xx - Unlikely that this version of the client will ever succeed.
- throw new UnrecoverableDownloadContentException("(Unrecoverable) Catalog sync failed. Status code: " + responseCode);
- } else if (responseCode < 200) {
- // If the HTTP status is <200 the response contains a JSON response.
- logErrorResponse(connection);
-
- throw new RecoverableDownloadContentException(RecoverableDownloadContentException.SERVER, "Response code: " + responseCode);
- } else {
- // HttpsUrlConnection: -1 (No valid response code)
- // Successful 2xx: We don't know how to handle anything but 200.
- // Redirection 3xx: We should have followed redirects if possible. We should not see those errors here.
-
- throw new UnrecoverableDownloadContentException("(Unrecoverable) Download failed. Response code: " + responseCode);
- }
- }
-
- return fetchJSONResponse(connection).getJSONArray(KINTO_KEY_DATA);
- } catch (JSONException | IOException e) {
- throw new RecoverableDownloadContentException(RecoverableDownloadContentException.NETWORK, e);
- } finally {
- if (connection != null) {
- connection.disconnect();
- }
- }
- }
-
- private JSONObject fetchJSONResponse(HttpURLConnection connection) throws IOException, JSONException {
- InputStream inputStream = null;
-
- try {
- inputStream = new BufferedInputStream(connection.getInputStream());
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- IOUtils.copy(inputStream, outputStream);
- return new JSONObject(outputStream.toString("UTF-8"));
- } finally {
- IOUtils.safeStreamClose(inputStream);
- }
- }
-
- protected boolean updateContent(DownloadContentCatalog catalog, JSONObject object, DownloadContent existingContent)
- throws JSONException {
- DownloadContent content = existingContent.buildUpon()
- .updateFromKinto(object)
- .build();
-
- if (existingContent.getLastModified() >= content.getLastModified()) {
- Log.d(LOGTAG, "Item has not changed: " + content);
- return false;
- }
-
- catalog.update(content);
-
- return true;
- }
-
- protected boolean createContent(DownloadContentCatalog catalog, JSONObject object) throws JSONException {
- DownloadContent content = new DownloadContentBuilder()
- .updateFromKinto(object)
- .build();
-
- catalog.add(content);
-
- return true;
- }
-
- protected boolean deleteContent(DownloadContentCatalog catalog, String id) {
- DownloadContent content = catalog.getContentById(id);
- if (content == null) {
- return false;
- }
-
- catalog.markAsDeleted(content);
-
- return true;
- }
-
- protected boolean isSyncEnabledForClient(Context context) {
- // Sync action is behind a switchboard flag for staged rollout.
- return SwitchBoard.isInExperiment(context, Experiments.DOWNLOAD_CONTENT_CATALOG_SYNC);
- }
-
- private void logErrorResponse(HttpURLConnection connection) {
- try {
- JSONObject error = fetchJSONResponse(connection);
-
- Log.w(LOGTAG, "Server returned error response:");
- Log.w(LOGTAG, "- Code: " + error.getInt("code"));
- Log.w(LOGTAG, "- Errno: " + error.getInt("errno"));
- Log.w(LOGTAG, "- Error: " + error.optString("error", "-"));
- Log.w(LOGTAG, "- Message: " + error.optString("message", "-"));
- Log.w(LOGTAG, "- Info: " + error.optString("info", "-"));
- } catch (JSONException | IOException e) {
- Log.w(LOGTAG, "Could not fetch error response", e);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/dlc/VerifyAction.java b/mobile/android/base/java/org/mozilla/gecko/dlc/VerifyAction.java
deleted file mode 100644
index e96a62eae..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/VerifyAction.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.dlc;
-
-import android.content.Context;
-import android.util.Log;
-
-import org.mozilla.gecko.dlc.catalog.DownloadContent;
-import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
-
-import java.io.File;
-
-/**
- * Verify: Validate downloaded content. Does it still exist and does it have the correct checksum?
- */
-public class VerifyAction extends BaseAction {
- private static final String LOGTAG = "DLCVerifyAction";
-
- @Override
- public void perform(Context context, DownloadContentCatalog catalog) {
- Log.d(LOGTAG, "Verifying catalog..");
-
- for (DownloadContent content : catalog.getDownloadedContent()) {
- try {
- File destinationFile = getDestinationFile(context, content);
-
- if (!destinationFile.exists()) {
- Log.d(LOGTAG, "Downloaded content does not exist anymore: " + content);
-
- // This file does not exist even though it is marked as downloaded in the catalog. Scheduling a
- // download to fetch it again.
- catalog.scheduleDownload(content);
- continue;
- }
-
- if (!verify(destinationFile, content.getChecksum())) {
- catalog.scheduleDownload(content);
- Log.d(LOGTAG, "Wrong checksum. Scheduling download: " + content);
- continue;
- }
-
- Log.v(LOGTAG, "Content okay: " + content);
- } catch (UnrecoverableDownloadContentException e) {
- Log.w(LOGTAG, "Unrecoverable exception while verifying downloaded file", e);
- } catch (RecoverableDownloadContentException e) {
- // That's okay, we are just verifying already existing content. No log.
- }
- }
-
- if (catalog.hasScheduledDownloads()) {
- startDownloads(context);
- }
-
- Log.v(LOGTAG, "Done");
- }
-
- protected void startDownloads(Context context) {
- DownloadContentService.startDownloads(context);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContent.java b/mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContent.java
deleted file mode 100644
index 61f7992ca..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContent.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.dlc.catalog;
-
-import android.support.annotation.IntDef;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.StringDef;
-
-public class DownloadContent {
- @IntDef({STATE_NONE, STATE_SCHEDULED, STATE_DOWNLOADED, STATE_FAILED, STATE_UPDATED, STATE_DELETED})
- public @interface State {}
- public static final int STATE_NONE = 0;
- public static final int STATE_SCHEDULED = 1;
- public static final int STATE_DOWNLOADED = 2;
- public static final int STATE_FAILED = 3; // Permanently failed for this version of the content
- public static final int STATE_UPDATED = 4;
- public static final int STATE_DELETED = 5;
-
- @StringDef({TYPE_ASSET_ARCHIVE})
- public @interface Type {}
- public static final String TYPE_ASSET_ARCHIVE = "asset-archive";
-
- @StringDef({KIND_FONT, KIND_HYPHENATION_DICTIONARY})
- public @interface Kind {}
- public static final String KIND_FONT = "font";
- public static final String KIND_HYPHENATION_DICTIONARY = "hyphenation";
-
- private final String id;
- private final String location;
- private final String filename;
- private final String checksum;
- private final String downloadChecksum;
- private final long lastModified;
- private final String type;
- private final String kind;
- private final long size;
- private final String appVersionPattern;
- private final String androidApiPattern;
- private final String appIdPattern;
- private int state;
- private int failures;
- private int lastFailureType;
-
- /* package-private */ DownloadContent(@NonNull String id, @NonNull String location, @NonNull String filename,
- @NonNull String checksum, @NonNull String downloadChecksum, @NonNull long lastModified,
- @NonNull String type, @NonNull String kind, long size, int failures, int lastFailureType,
- @Nullable String appVersionPattern, @Nullable String androidApiPattern, @Nullable String appIdPattern) {
- this.id = id;
- this.location = location;
- this.filename = filename;
- this.checksum = checksum;
- this.downloadChecksum = downloadChecksum;
- this.lastModified = lastModified;
- this.type = type;
- this.kind = kind;
- this.size = size;
- this.state = STATE_NONE;
- this.failures = failures;
- this.lastFailureType = lastFailureType;
- this.appVersionPattern = appVersionPattern;
- this.androidApiPattern = androidApiPattern;
- this.appIdPattern = appIdPattern;
- }
-
- public String getId() {
- return id;
- }
-
- /* package-private */ void setState(@State int state) {
- this.state = state;
- }
-
- @State
- public int getState() {
- return state;
- }
-
- public boolean isStateIn(@State int... states) {
- for (int state : states) {
- if (this.state == state) {
- return true;
- }
- }
-
- return false;
- }
-
- @Kind
- public String getKind() {
- return kind;
- }
-
- @Type
- public String getType() {
- return type;
- }
-
- public String getLocation() {
- return location;
- }
-
- public long getLastModified() {
- return lastModified;
- }
-
- public String getFilename() {
- return filename;
- }
-
- public String getChecksum() {
- return checksum;
- }
-
- public String getDownloadChecksum() {
- return downloadChecksum;
- }
-
- public long getSize() {
- return size;
- }
-
- public boolean isFont() {
- return KIND_FONT.equals(kind);
- }
-
- public boolean isHyphenationDictionary() {
- return KIND_HYPHENATION_DICTIONARY.equals(kind);
- }
-
- /**
- *Checks whether the content to be downloaded is a known content.
- *Currently it checks whether the type is "Asset Archive" and is of kind
- *"Font" or "Hyphenation Dictionary".
- */
- public boolean isKnownContent() {
- return ((isFont() || isHyphenationDictionary()) && isAssetArchive());
- }
-
- public boolean isAssetArchive() {
- return TYPE_ASSET_ARCHIVE.equals(type);
- }
-
- /* package-private */ int getFailures() {
- return failures;
- }
-
- /* package-private */ int getLastFailureType() {
- return lastFailureType;
- }
-
- /* package-private */ void rememberFailure(int failureType) {
- if (lastFailureType != failureType) {
- lastFailureType = failureType;
- failures = 1;
- } else {
- failures++;
- }
- }
-
- /* package-private */ void resetFailures() {
- failures = 0;
- lastFailureType = 0;
- }
-
- public String getAppVersionPattern() {
- return appVersionPattern;
- }
-
- public String getAndroidApiPattern() {
- return androidApiPattern;
- }
-
- public String getAppIdPattern() {
- return appIdPattern;
- }
-
- public DownloadContentBuilder buildUpon() {
- return DownloadContentBuilder.buildUpon(this);
- }
-
-
- public String toString() {
- return String.format("[%s,%s] %s (%d bytes) %s", getType(), getKind(), getId(), getSize(), getChecksum());
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContentBootstrap.java b/mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContentBootstrap.java
deleted file mode 100644
index 40c804573..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContentBootstrap.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.dlc.catalog;
-
-import android.support.v4.util.ArrayMap;
-
-import org.mozilla.gecko.AppConstants;
-
-import java.util.Arrays;
-import java.util.List;
-
-/* package-private */ class DownloadContentBootstrap {
- public static ArrayMap<String, DownloadContent> createInitialDownloadContentList() {
- if (!AppConstants.MOZ_ANDROID_EXCLUDE_FONTS) {
- // We are packaging fonts. There's nothing we want to download;
- return new ArrayMap<>();
- }
-
- List<DownloadContent> initialList = Arrays.asList(
- new DownloadContentBuilder()
- .setId("c40929cf-7f4c-fa72-3dc9-12cadf56905d")
- .setLocation("fennec/catalog/f63e5f92-793c-4574-a2d7-fbc50242b8cb.gz")
- .setFilename("CharisSILCompact-B.ttf")
- .setChecksum("699d958b492eda0cc2823535f8567d0393090e3842f6df3c36dbe7239cb80b6d")
- .setDownloadChecksum("a9f9b34fed353169a88cc159b8f298cb285cce0b8b0f979c22a7d85de46f0532")
- .setSize(1676072)
- .setKind("font")
- .setType("asset-archive")
- .build(),
-
- new DownloadContentBuilder()
- .setId("6d265876-85ed-0917-fdc8-baf583ca2cba")
- .setLocation("fennec/catalog/19af6c88-09d9-4d6c-805e-cfebb8699a6c.gz")
- .setFilename("CharisSILCompact-BI.ttf")
- .setChecksum("82465e747b4f41471dbfd942842b2ee810749217d44b55dbc43623b89f9c7d9b")
- .setDownloadChecksum("2be26671039a5e2e4d0360a948b4fa42048171133076a3bb6173d93d4b9cd55b")
- .setSize(1667812)
- .setKind("font")
- .setType("asset-archive")
- .build(),
-
- new DownloadContentBuilder()
- .setId("8460dc6d-d129-fd1a-24b6-343dbf6531dd")
- .setLocation("fennec/catalog/f35a384a-90ea-41c6-a957-bb1845de97eb.gz")
- .setFilename("CharisSILCompact-I.ttf")
- .setChecksum("ab3ed6f2a4d4c2095b78227bd33155d7ccd05a879c107a291912640d4d64f767")
- .setDownloadChecksum("38a6469041c02624d43dfd41d2dd745e3e3211655e616188f65789a90952a1e9")
- .setSize(1693988)
- .setKind("font")
- .setType("asset-archive")
- .build(),
-
- new DownloadContentBuilder()
- .setId("c906275c-3747-fe27-426f-6187526a6f06")
- .setLocation("fennec/catalog/8c3bec92-d2df-4789-8c4a-0f523f026d96.gz")
- .setFilename("CharisSILCompact-R.ttf")
- .setChecksum("4ed509317f1bb441b185ea13bf1c9d19d1a0b396962efa3b5dc3190ad88f2067")
- .setDownloadChecksum("7c2ec1f550c2005b75383b878f737266b5f0b1c82679dd886c8bbe30c82e340e")
- .setSize(1727656)
- .setKind("font")
- .setType("asset-archive")
- .build(),
-
- new DownloadContentBuilder()
- .setId("ff5deecc-6ecc-d816-bb51-65face460119")
- .setLocation("fennec/catalog/ea115d71-e2ac-4609-853e-c978780776b1.gz")
- .setFilename("ClearSans-Bold.ttf")
- .setChecksum("385d0a293c1714770e198f7c762ab32f7905a0ed9d2993f69d640bd7232b4b70")
- .setDownloadChecksum("0d3c22bef90e7096f75b331bb7391de3aa43017e10d61041cd3085816db4919a")
- .setSize(140136)
- .setKind("font")
- .setType("asset-archive")
- .build(),
-
- new DownloadContentBuilder()
- .setId("a173d1db-373b-ce42-1335-6b3285cfdebd")
- .setLocation("fennec/catalog/0838e513-2d99-4e53-b58f-6b970f6548c6.gz")
- .setFilename("ClearSans-BoldItalic.ttf")
- .setChecksum("7bce66864e38eecd7c94b6657b9b38c35ebfacf8046bfb1247e08f07fe933198")
- .setDownloadChecksum("de0903164dde1ad3768d0bd6dec949871d6ab7be08f573d9d70f38c138a22e37")
- .setSize(156124)
- .setKind("font")
- .setType("asset-archive")
- .build(),
-
- new DownloadContentBuilder()
- .setId("e65c66df-0088-940d-ca5c-207c22118c0e")
- .setLocation("fennec/catalog/7550fa42-0947-478c-a5f0-5ea1bbb6ba27.gz")
- .setFilename("ClearSans-Italic.ttf")
- .setChecksum("87c13c5fbae832e4f85c3bd46fcbc175978234f39be5fe51c4937be4cbff3b68")
- .setDownloadChecksum("6e323db3115005dd0e96d2422db87a520f9ae426de28a342cd6cc87b55601d87")
- .setSize(155672)
- .setKind("font")
- .setType("asset-archive")
- .build(),
-
- new DownloadContentBuilder()
- .setId("25610abb-5dc8-fd75-40e7-990507f010c4")
- .setLocation("fennec/catalog/dd9bee7d-d784-476b-a3dd-69af8e516487.gz")
- .setFilename("ClearSans-Light.ttf")
- .setChecksum("e4885f6188e7a8587f5621c077c6c1f5e8d3739dffc8f4d055c2ba87568c750a")
- .setDownloadChecksum("19d4f7c67176e9e254c61420da9c7363d9fe5e6b4bb9d61afa4b3b574280714f")
- .setSize(145976)
- .setKind("font")
- .setType("asset-archive")
- .build(),
-
- new DownloadContentBuilder()
- .setId("ffe40339-a096-2262-c3f8-54af75c81fe6")
- .setLocation("fennec/catalog/bc5ada8c-8cfc-443d-93d7-dc5f98138a07.gz")
- .setFilename("ClearSans-Medium.ttf")
- .setChecksum("5d0e0115f3a3ed4be3eda6d7eabb899bb9a361292802e763d53c72e00f629da1")
- .setDownloadChecksum("edec86dab3ad2a97561cb41b584670262a48bed008c57bb587ee05ca47fb067f")
- .setSize(148892)
- .setKind("font")
- .setType("asset-archive")
- .build(),
-
- new DownloadContentBuilder()
- .setId("139a94be-ac69-0264-c9cc-8f2d071fd29d")
- .setLocation("fennec/catalog/0490c768-6178-49c2-af88-9f8769ff3167.gz")
- .setFilename("ClearSans-MediumItalic.ttf")
- .setChecksum("937dda88b26469306258527d38e42c95e27e7ebb9f05bd1d7c5d706a3c9541d7")
- .setDownloadChecksum("34edbd1b325dbffe7791fba8dd2d19852eb3c2fe00cff517ea2161ddc424ee22")
- .setSize(155228)
- .setKind("font")
- .setType("asset-archive")
- .build(),
-
- new DownloadContentBuilder()
- .setId("b887012a-01e1-7c94-fdcb-ca44d5b974a2")
- .setLocation("fennec/catalog/78205bf8-c668-41b1-b68f-afd54f98713b.gz")
- .setFilename("ClearSans-Regular.ttf")
- .setChecksum("9b91bbdb95ffa6663da24fdaa8ee06060cd0a4d2dceaf1ffbdda00e04915ee5b")
- .setDownloadChecksum("a72f1420b4da1ba9e6797adac34f08e72f94128a85e56542d5e6a8080af5f08a")
- .setSize(142572)
- .setKind("font")
- .setType("asset-archive")
- .build(),
-
- new DownloadContentBuilder()
- .setId("c8703652-d317-0356-0bf8-95441a5b2c9b")
- .setLocation("fennec/catalog/3570f44f-9440-4aa0-abd0-642eaf2a1aa0.gz")
- .setFilename("ClearSans-Thin.ttf")
- .setChecksum("07b0db85a3ad99afeb803f0f35631436a7b4c67ac66d0c7f77d26a47357c592a")
- .setDownloadChecksum("d9f23fd8687d6743f5c281c33539fb16f163304795039959b8caf159e6d62822")
- .setSize(147004)
- .setKind("font")
- .setType("asset-archive")
- .build());
-
- ArrayMap<String, DownloadContent> content = new ArrayMap<>();
- for (DownloadContent currentContent : initialList) {
- content.put(currentContent.getId(), currentContent);
- }
- return content;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContentBuilder.java b/mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContentBuilder.java
deleted file mode 100644
index 243e2d4eb..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContentBuilder.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.dlc.catalog;
-
-import android.text.TextUtils;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-public class DownloadContentBuilder {
- private static final String LOCAL_KEY_ID = "id";
- private static final String LOCAL_KEY_LOCATION = "location";
- private static final String LOCAL_KEY_FILENAME = "filename";
- private static final String LOCAL_KEY_CHECKSUM = "checksum";
- private static final String LOCAL_KEY_DOWNLOAD_CHECKSUM = "download_checksum";
- private static final String LOCAL_KEY_LAST_MODIFIED = "last_modified";
- private static final String LOCAL_KEY_TYPE = "type";
- private static final String LOCAL_KEY_KIND = "kind";
- private static final String LOCAL_KEY_SIZE = "size";
- private static final String LOCAL_KEY_STATE = "state";
- private static final String LOCAL_KEY_FAILURES = "failures";
- private static final String LOCAL_KEY_LAST_FAILURE_TYPE = "last_failure_type";
- private static final String LOCAL_KEY_PATTERN_APP_ID = "pattern_app_id";
- private static final String LOCAL_KEY_PATTERN_ANDROID_API = "pattern_android_api";
- private static final String LOCAL_KEY_PATTERN_APP_VERSION = "pattern_app_version";
-
- private static final String KINTO_KEY_ID = "id";
- private static final String KINTO_KEY_ATTACHMENT = "attachment";
- private static final String KINTO_KEY_ORIGINAL = "original";
- private static final String KINTO_KEY_LOCATION = "location";
- private static final String KINTO_KEY_FILENAME = "filename";
- private static final String KINTO_KEY_HASH = "hash";
- private static final String KINTO_KEY_LAST_MODIFIED = "last_modified";
- private static final String KINTO_KEY_TYPE = "type";
- private static final String KINTO_KEY_KIND = "kind";
- private static final String KINTO_KEY_SIZE = "size";
- private static final String KINTO_KEY_MATCH = "match";
- private static final String KINTO_KEY_APP_ID = "appId";
- private static final String KINTO_KEY_ANDROID_API = "androidApi";
- private static final String KINTO_KEY_APP_VERSION = "appVersion";
-
- private String id;
- private String location;
- private String filename;
- private String checksum;
- private String downloadChecksum;
- private long lastModified;
- private String type;
- private String kind;
- private long size;
- private int state;
- private int failures;
- private int lastFailureType;
- private String appVersionPattern;
- private String androidApiPattern;
- private String appIdPattern;
-
- public static DownloadContentBuilder buildUpon(DownloadContent content) {
- DownloadContentBuilder builder = new DownloadContentBuilder();
-
- builder.id = content.getId();
- builder.location = content.getLocation();
- builder.filename = content.getFilename();
- builder.checksum = content.getChecksum();
- builder.downloadChecksum = content.getDownloadChecksum();
- builder.lastModified = content.getLastModified();
- builder.type = content.getType();
- builder.kind = content.getKind();
- builder.size = content.getSize();
- builder.state = content.getState();
- builder.failures = content.getFailures();
- builder.lastFailureType = content.getLastFailureType();
-
- return builder;
- }
-
- public static DownloadContent fromJSON(JSONObject object) throws JSONException {
- return new DownloadContentBuilder()
- .setId(object.getString(LOCAL_KEY_ID))
- .setLocation(object.getString(LOCAL_KEY_LOCATION))
- .setFilename(object.getString(LOCAL_KEY_FILENAME))
- .setChecksum(object.getString(LOCAL_KEY_CHECKSUM))
- .setDownloadChecksum(object.getString(LOCAL_KEY_DOWNLOAD_CHECKSUM))
- .setLastModified(object.getLong(LOCAL_KEY_LAST_MODIFIED))
- .setType(object.getString(LOCAL_KEY_TYPE))
- .setKind(object.getString(LOCAL_KEY_KIND))
- .setSize(object.getLong(LOCAL_KEY_SIZE))
- .setState(object.getInt(LOCAL_KEY_STATE))
- .setFailures(object.optInt(LOCAL_KEY_FAILURES), object.optInt(LOCAL_KEY_LAST_FAILURE_TYPE))
- .setAppVersionPattern(object.optString(LOCAL_KEY_PATTERN_APP_VERSION))
- .setAppIdPattern(object.optString(LOCAL_KEY_PATTERN_APP_ID))
- .setAndroidApiPattern(object.optString(LOCAL_KEY_PATTERN_ANDROID_API))
- .build();
- }
-
- public static JSONObject toJSON(DownloadContent content) throws JSONException {
- final JSONObject object = new JSONObject();
- object.put(LOCAL_KEY_ID, content.getId());
- object.put(LOCAL_KEY_LOCATION, content.getLocation());
- object.put(LOCAL_KEY_FILENAME, content.getFilename());
- object.put(LOCAL_KEY_CHECKSUM, content.getChecksum());
- object.put(LOCAL_KEY_DOWNLOAD_CHECKSUM, content.getDownloadChecksum());
- object.put(LOCAL_KEY_LAST_MODIFIED, content.getLastModified());
- object.put(LOCAL_KEY_TYPE, content.getType());
- object.put(LOCAL_KEY_KIND, content.getKind());
- object.put(LOCAL_KEY_SIZE, content.getSize());
- object.put(LOCAL_KEY_STATE, content.getState());
- object.put(LOCAL_KEY_PATTERN_APP_VERSION, content.getAppVersionPattern());
- object.put(LOCAL_KEY_PATTERN_APP_ID, content.getAppIdPattern());
- object.put(LOCAL_KEY_PATTERN_ANDROID_API, content.getAndroidApiPattern());
-
- final int failures = content.getFailures();
- if (failures > 0) {
- object.put(LOCAL_KEY_FAILURES, failures);
- object.put(LOCAL_KEY_LAST_FAILURE_TYPE, content.getLastFailureType());
- }
-
- return object;
- }
-
- public DownloadContent build() {
- DownloadContent content = new DownloadContent(id, location, filename, checksum,
- downloadChecksum, lastModified, type, kind, size, failures, lastFailureType,
- appVersionPattern, androidApiPattern, appIdPattern);
- content.setState(state);
-
- return content;
- }
-
- public DownloadContentBuilder setId(String id) {
- this.id = id;
- return this;
- }
-
- public DownloadContentBuilder setLocation(String location) {
- this.location = location;
- return this;
- }
-
- public DownloadContentBuilder setFilename(String filename) {
- this.filename = filename;
- return this;
- }
-
- public DownloadContentBuilder setChecksum(String checksum) {
- this.checksum = checksum;
- return this;
- }
-
- public DownloadContentBuilder setDownloadChecksum(String downloadChecksum) {
- this.downloadChecksum = downloadChecksum;
- return this;
- }
-
- public DownloadContentBuilder setLastModified(long lastModified) {
- this.lastModified = lastModified;
- return this;
- }
-
- public DownloadContentBuilder setType(String type) {
- this.type = type;
- return this;
- }
-
- public DownloadContentBuilder setKind(String kind) {
- this.kind = kind;
- return this;
- }
-
- public DownloadContentBuilder setSize(long size) {
- this.size = size;
- return this;
- }
-
- public DownloadContentBuilder setState(int state) {
- this.state = state;
- return this;
- }
-
- /* package-private */ DownloadContentBuilder setFailures(int failures, int lastFailureType) {
- this.failures = failures;
- this.lastFailureType = lastFailureType;
-
- return this;
- }
-
- public DownloadContentBuilder setAppVersionPattern(String appVersionPattern) {
- this.appVersionPattern = appVersionPattern;
- return this;
- }
-
- public DownloadContentBuilder setAndroidApiPattern(String androidApiPattern) {
- this.androidApiPattern = androidApiPattern;
- return this;
- }
-
- public DownloadContentBuilder setAppIdPattern(String appIdPattern) {
- this.appIdPattern = appIdPattern;
- return this;
- }
-
- public DownloadContentBuilder updateFromKinto(JSONObject object) throws JSONException {
- final String objectId = object.getString(KINTO_KEY_ID);
-
- if (TextUtils.isEmpty(id)) {
- // New object without an id yet
- id = objectId;
- } else if (!id.equals(objectId)) {
- throw new JSONException(String.format("Record ids do not match: Expected=%s, Actual=%s", id, objectId));
- }
-
- setType(object.getString(KINTO_KEY_TYPE));
- setKind(object.getString(KINTO_KEY_KIND));
- setLastModified(object.getLong(KINTO_KEY_LAST_MODIFIED));
-
- JSONObject attachment = object.getJSONObject(KINTO_KEY_ATTACHMENT);
- JSONObject original = attachment.getJSONObject(KINTO_KEY_ORIGINAL);
-
- setFilename(original.getString(KINTO_KEY_FILENAME));
- setChecksum(original.getString(KINTO_KEY_HASH));
- setSize(original.getLong(KINTO_KEY_SIZE));
-
- setLocation(attachment.getString(KINTO_KEY_LOCATION));
- setDownloadChecksum(attachment.getString(KINTO_KEY_HASH));
-
- JSONObject match = object.optJSONObject(KINTO_KEY_MATCH);
- if (match != null) {
- setAndroidApiPattern(match.optString(KINTO_KEY_ANDROID_API));
- setAppIdPattern(match.optString(KINTO_KEY_APP_ID));
- setAppVersionPattern(match.optString(KINTO_KEY_APP_VERSION));
- }
-
- return this;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContentCatalog.java b/mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContentCatalog.java
deleted file mode 100644
index 43ba4e82e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/catalog/DownloadContentCatalog.java
+++ /dev/null
@@ -1,303 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.dlc.catalog;
-
-import android.content.Context;
-import android.support.annotation.Nullable;
-import android.support.v4.util.ArrayMap;
-import android.support.v4.util.AtomicFile;
-import android.util.Log;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Catalog of downloadable content (DLC).
- *
- * Changing elements returned by the catalog should be guarded by the catalog instance to guarantee visibility when
- * persisting changes.
- */
-public class DownloadContentCatalog {
- private static final String LOGTAG = "GeckoDLCCatalog";
- private static final String FILE_NAME = "download_content_catalog";
-
- private static final String JSON_KEY_CONTENT = "content";
-
- private static final int MAX_FAILURES_UNTIL_PERMANENTLY_FAILED = 10;
-
- private final AtomicFile file; // Guarded by 'file'
-
- private ArrayMap<String, DownloadContent> content; // Guarded by 'this'
- private boolean hasLoadedCatalog; // Guarded by 'this
- private boolean hasCatalogChanged; // Guarded by 'this'
-
- public DownloadContentCatalog(Context context) {
- this(new AtomicFile(new File(context.getApplicationInfo().dataDir, FILE_NAME)));
-
- startLoadFromDisk();
- }
-
- // For injecting mocked AtomicFile objects during test
- protected DownloadContentCatalog(AtomicFile file) {
- this.content = new ArrayMap<>();
- this.file = file;
- }
-
- public List<DownloadContent> getContentToStudy() {
- return filterByState(DownloadContent.STATE_NONE, DownloadContent.STATE_UPDATED);
- }
-
- public List<DownloadContent> getContentToDelete() {
- return filterByState(DownloadContent.STATE_DELETED);
- }
-
- public List<DownloadContent> getDownloadedContent() {
- return filterByState(DownloadContent.STATE_DOWNLOADED);
- }
-
- public List<DownloadContent> getScheduledDownloads() {
- return filterByState(DownloadContent.STATE_SCHEDULED);
- }
-
- private synchronized List<DownloadContent> filterByState(@DownloadContent.State int... filterStates) {
- awaitLoadingCatalogLocked();
-
- List<DownloadContent> filteredContent = new ArrayList<>();
-
- for (DownloadContent currentContent : content.values()) {
- if (currentContent.isStateIn(filterStates)) {
- filteredContent.add(currentContent);
- }
- }
-
- return filteredContent;
- }
-
- public boolean hasScheduledDownloads() {
- return !filterByState(DownloadContent.STATE_SCHEDULED).isEmpty();
- }
-
- public synchronized void add(DownloadContent newContent) {
- awaitLoadingCatalogLocked();
-
- content.put(newContent.getId(), newContent);
- hasCatalogChanged = true;
- }
-
- public synchronized void update(DownloadContent changedContent) {
- awaitLoadingCatalogLocked();
-
- if (!content.containsKey(changedContent.getId())) {
- Log.w(LOGTAG, "Did not find content with matching id (" + changedContent.getId() + ") to update");
- return;
- }
-
- changedContent.setState(DownloadContent.STATE_UPDATED);
- changedContent.resetFailures();
-
- content.put(changedContent.getId(), changedContent);
- hasCatalogChanged = true;
- }
-
- public synchronized void remove(DownloadContent removedContent) {
- awaitLoadingCatalogLocked();
-
- if (!content.containsKey(removedContent.getId())) {
- Log.w(LOGTAG, "Did not find content with matching id (" + removedContent.getId() + ") to remove");
- return;
- }
-
- content.remove(removedContent.getId());
- }
-
- @Nullable
- public synchronized DownloadContent getContentById(String id) {
- return content.get(id);
- }
-
- public synchronized long getLastModified() {
- awaitLoadingCatalogLocked();
-
- long lastModified = 0;
-
- for (DownloadContent currentContent : content.values()) {
- if (currentContent.getLastModified() > lastModified) {
- lastModified = currentContent.getLastModified();
- }
- }
-
- return lastModified;
- }
-
- public synchronized void scheduleDownload(DownloadContent content) {
- content.setState(DownloadContent.STATE_SCHEDULED);
- hasCatalogChanged = true;
- }
-
- public synchronized void markAsDownloaded(DownloadContent content) {
- content.setState(DownloadContent.STATE_DOWNLOADED);
- content.resetFailures();
- hasCatalogChanged = true;
- }
-
- public synchronized void markAsPermanentlyFailed(DownloadContent content) {
- content.setState(DownloadContent.STATE_FAILED);
- hasCatalogChanged = true;
- }
-
- public synchronized void markAsDeleted(DownloadContent content) {
- content.setState(DownloadContent.STATE_DELETED);
- hasCatalogChanged = true;
- }
-
- public synchronized void rememberFailure(DownloadContent content, int failureType) {
- if (content.getFailures() >= MAX_FAILURES_UNTIL_PERMANENTLY_FAILED) {
- Log.d(LOGTAG, "Maximum number of failures reached. Marking content has permanently failed.");
-
- markAsPermanentlyFailed(content);
- } else {
- content.rememberFailure(failureType);
- hasCatalogChanged = true;
- }
- }
-
- public void persistChanges() {
- new Thread(LOGTAG + "-Persist") {
- public void run() {
- writeToDisk();
- }
- }.start();
- }
-
- private void startLoadFromDisk() {
- new Thread(LOGTAG + "-Load") {
- public void run() {
- loadFromDisk();
- }
- }.start();
- }
-
- private void awaitLoadingCatalogLocked() {
- while (!hasLoadedCatalog) {
- try {
- Log.v(LOGTAG, "Waiting for catalog to be loaded");
-
- wait();
- } catch (InterruptedException e) {
- // Ignore
- }
- }
- }
-
- protected synchronized boolean hasCatalogChanged() {
- return hasCatalogChanged;
- }
-
- protected synchronized void loadFromDisk() {
- Log.d(LOGTAG, "Loading from disk");
-
- if (hasLoadedCatalog) {
- return;
- }
-
- ArrayMap<String, DownloadContent> loadedContent = new ArrayMap<>();
-
- try {
- JSONObject catalog;
-
- synchronized (file) {
- catalog = new JSONObject(new String(file.readFully(), "UTF-8"));
- }
-
- JSONArray array = catalog.getJSONArray(JSON_KEY_CONTENT);
- for (int i = 0; i < array.length(); i++) {
- DownloadContent currentContent = DownloadContentBuilder.fromJSON(array.getJSONObject(i));
- loadedContent.put(currentContent.getId(), currentContent);
- }
- } catch (FileNotFoundException e) {
- Log.d(LOGTAG, "Catalog file does not exist: Bootstrapping initial catalog");
- loadedContent = DownloadContentBootstrap.createInitialDownloadContentList();
- } catch (JSONException e) {
- Log.w(LOGTAG, "Unable to parse catalog JSON. Re-creating catalog.", e);
- // Catalog seems to be broken. Re-create catalog:
- loadedContent = DownloadContentBootstrap.createInitialDownloadContentList();
- hasCatalogChanged = true; // Indicate that we want to persist the new catalog
- } catch (NullPointerException e) {
- // Bad content can produce an NPE in JSON code -- bug 1300139
- Log.w(LOGTAG, "Unable to parse catalog JSON. Re-creating catalog.", e);
- // Catalog seems to be broken. Re-create catalog:
- loadedContent = DownloadContentBootstrap.createInitialDownloadContentList();
- hasCatalogChanged = true; // Indicate that we want to persist the new catalog
- } catch (UnsupportedEncodingException e) {
- AssertionError error = new AssertionError("Should not happen: This device does not speak UTF-8");
- error.initCause(e);
- throw error;
- } catch (IOException e) {
- Log.d(LOGTAG, "Can't read catalog due to IOException", e);
- }
-
- onCatalogLoaded(loadedContent);
-
- notifyAll();
-
- Log.d(LOGTAG, "Loaded " + content.size() + " elements");
- }
-
- protected void onCatalogLoaded(ArrayMap<String, DownloadContent> content) {
- this.content = content;
- this.hasLoadedCatalog = true;
- }
-
- protected synchronized void writeToDisk() {
- if (!hasCatalogChanged) {
- Log.v(LOGTAG, "Not persisting: Catalog has not changed");
- return;
- }
-
- Log.d(LOGTAG, "Writing to disk");
-
- FileOutputStream outputStream = null;
-
- synchronized (file) {
- try {
- outputStream = file.startWrite();
-
- JSONArray array = new JSONArray();
- for (DownloadContent currentContent : content.values()) {
- array.put(DownloadContentBuilder.toJSON(currentContent));
- }
-
- JSONObject catalog = new JSONObject();
- catalog.put(JSON_KEY_CONTENT, array);
-
- outputStream.write(catalog.toString().getBytes("UTF-8"));
-
- file.finishWrite(outputStream);
-
- hasCatalogChanged = false;
- } catch (UnsupportedEncodingException e) {
- AssertionError error = new AssertionError("Should not happen: This device does not speak UTF-8");
- error.initCause(e);
- throw error;
- } catch (IOException | JSONException e) {
- Log.e(LOGTAG, "IOException during writing catalog", e);
-
- if (outputStream != null) {
- file.failWrite(outputStream);
- }
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/ContentNotificationsDelegate.java b/mobile/android/base/java/org/mozilla/gecko/feeds/ContentNotificationsDelegate.java
deleted file mode 100644
index d317a21ee..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/ContentNotificationsDelegate.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v4.app.NotificationManagerCompat;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.delegates.BrowserAppDelegate;
-import org.mozilla.gecko.mozglue.SafeIntent;
-
-import java.util.List;
-
-/**
- * BrowserAppDelegate implementation that takes care of handling intents from content notifications.
- */
-public class ContentNotificationsDelegate extends BrowserAppDelegate {
- // The application is opened from a content notification
- public static final String ACTION_CONTENT_NOTIFICATION = AppConstants.ANDROID_PACKAGE_NAME + ".action.CONTENT_NOTIFICATION";
-
- public static final String EXTRA_READ_BUTTON = "read_button";
- public static final String EXTRA_URLS = "urls";
-
- private static final String TELEMETRY_EXTRA_CONTENT_UPDATE = "content_update";
- private static final String TELEMETRY_EXTRA_READ_NOW_BUTTON = TELEMETRY_EXTRA_CONTENT_UPDATE + "_read_now";
-
- @Override
- public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {
- if (savedInstanceState != null) {
- // This activity is getting restored: We do not want to handle the URLs in the Intent again. The browser
- // will take care of restoring the tabs we already created.
- return;
- }
-
-
- final Intent unsafeIntent = browserApp.getIntent();
-
- // Nothing to do.
- if (unsafeIntent == null) {
- return;
- }
-
- final SafeIntent intent = new SafeIntent(unsafeIntent);
-
- if (ACTION_CONTENT_NOTIFICATION.equals(intent.getAction())) {
- openURLsFromIntent(browserApp, intent);
- }
- }
-
- @Override
- public void onNewIntent(BrowserApp browserApp, @NonNull final SafeIntent intent) {
- if (ACTION_CONTENT_NOTIFICATION.equals(intent.getAction())) {
- openURLsFromIntent(browserApp, intent);
- }
- }
-
- private void openURLsFromIntent(BrowserApp browserApp, @NonNull final SafeIntent intent) {
- final List<String> urls = intent.getStringArrayListExtra(EXTRA_URLS);
- if (urls != null) {
- browserApp.openUrls(urls);
- }
-
- Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(browserApp));
-
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, TELEMETRY_EXTRA_CONTENT_UPDATE);
-
- if (intent.getBooleanExtra(EXTRA_READ_BUTTON, false)) {
- // "READ NOW" button in notification was clicked
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.NOTIFICATION, TELEMETRY_EXTRA_READ_NOW_BUTTON);
-
- // Android's "auto cancel" won't remove the notification when an action button is pressed. So we do it ourselves here.
- NotificationManagerCompat.from(browserApp).cancel(R.id.websiteContentNotification);
- } else {
- // Notification was clicked
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.NOTIFICATION, TELEMETRY_EXTRA_CONTENT_UPDATE);
- }
-
- Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(browserApp));
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/FeedAlarmReceiver.java b/mobile/android/base/java/org/mozilla/gecko/feeds/FeedAlarmReceiver.java
deleted file mode 100644
index d943b4f81..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/FeedAlarmReceiver.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds;
-
-import android.content.Context;
-import android.content.Intent;
-import android.support.v4.content.WakefulBroadcastReceiver;
-import android.util.Log;
-
-/**
- * Broadcast receiver that will receive broadcasts from the AlarmManager and start the FeedService
- * with the given action.
- */
-public class FeedAlarmReceiver extends WakefulBroadcastReceiver {
- private static final String LOGTAG = "FeedCheckAction";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
-
- Log.d(LOGTAG, "Received alarm with action: " + action);
-
- final Intent serviceIntent = new Intent(context, FeedService.class);
- serviceIntent.setAction(action);
-
- startWakefulService(context, serviceIntent);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/FeedFetcher.java b/mobile/android/base/java/org/mozilla/gecko/feeds/FeedFetcher.java
deleted file mode 100644
index 76c1b7e30..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/FeedFetcher.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import org.mozilla.gecko.feeds.parser.Feed;
-import org.mozilla.gecko.feeds.parser.SimpleFeedParser;
-import org.mozilla.gecko.util.IOUtils;
-
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.URL;
-
-import ch.boye.httpclientandroidlib.util.TextUtils;
-
-/**
- * Helper class for fetching and parsing a feed.
- */
-public class FeedFetcher {
- private static final int CONNECT_TIMEOUT = 15000;
- private static final int READ_TIMEOUT = 15000;
-
- public static class FeedResponse {
- public final Feed feed;
- public final String etag;
- public final String lastModified;
-
- public FeedResponse(Feed feed, String etag, String lastModified) {
- this.feed = feed;
- this.etag = etag;
- this.lastModified = lastModified;
- }
- }
-
- /**
- * Fetch and parse a feed from the given URL. Will return null if fetching or parsing failed.
- */
- public static FeedResponse fetchAndParseFeed(String url) {
- return fetchAndParseFeedIfModified(url, null, null);
- }
-
- /**
- * Fetch and parse a feed from the given URL using the given ETag and "Last modified" value.
- *
- * Will return null if fetching or parsing failed. Will also return null if the feed has not
- * changed (ETag / Last-Modified-Since).
- *
- * @param eTag The ETag from the last fetch or null if no ETag is available (will always fetch feed)
- * @param lastModified The "Last modified" header from the last time the feed has been fetch or
- * null if no value is available (will always fetch feed)
- * @return A FeedResponse or null if no feed could be fetched (error or no new version available)
- */
- @Nullable
- public static FeedResponse fetchAndParseFeedIfModified(@NonNull String url, @Nullable String eTag, @Nullable String lastModified) {
- HttpURLConnection connection = null;
- InputStream stream = null;
-
- try {
- connection = (HttpURLConnection) new URL(url).openConnection();
- connection.setInstanceFollowRedirects(true);
- connection.setConnectTimeout(CONNECT_TIMEOUT);
- connection.setReadTimeout(READ_TIMEOUT);
-
- if (!TextUtils.isEmpty(eTag)) {
- connection.setRequestProperty("If-None-Match", eTag);
- }
-
- if (!TextUtils.isEmpty(lastModified)) {
- connection.setRequestProperty("If-Modified-Since", lastModified);
- }
-
- final int statusCode = connection.getResponseCode();
-
- if (statusCode != HttpURLConnection.HTTP_OK) {
- return null;
- }
-
- String responseEtag = connection.getHeaderField("ETag");
- if (!TextUtils.isEmpty(responseEtag) && responseEtag.startsWith("W/")) {
- // Weak ETag, get actual ETag value
- responseEtag = responseEtag.substring(2);
- }
-
- final String updatedLastModified = connection.getHeaderField("Last-Modified");
-
- stream = new BufferedInputStream(connection.getInputStream());
-
- final SimpleFeedParser parser = new SimpleFeedParser();
- final Feed feed = parser.parse(stream);
-
- return new FeedResponse(feed, responseEtag, updatedLastModified);
- } catch (IOException e) {
- return null;
- } catch (SimpleFeedParser.ParserException e) {
- return null;
- } finally {
- if (connection != null) {
- connection.disconnect();
- }
- IOUtils.safeStreamClose(stream);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/FeedService.java b/mobile/android/base/java/org/mozilla/gecko/feeds/FeedService.java
deleted file mode 100644
index 374486215..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/FeedService.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds;
-
-import android.app.IntentService;
-import android.content.Context;
-import android.content.Intent;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.support.annotation.Nullable;
-import android.support.v4.net.ConnectivityManagerCompat;
-import android.util.Log;
-
-import com.keepsafe.switchboard.SwitchBoard;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.feeds.action.FeedAction;
-import org.mozilla.gecko.feeds.action.CheckForUpdatesAction;
-import org.mozilla.gecko.feeds.action.EnrollSubscriptionsAction;
-import org.mozilla.gecko.feeds.action.SetupAlarmsAction;
-import org.mozilla.gecko.feeds.action.SubscribeToFeedAction;
-import org.mozilla.gecko.feeds.action.WithdrawSubscriptionsAction;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.Experiments;
-
-/**
- * Background service for subscribing to and checking website feeds to notify the user about updates.
- */
-public class FeedService extends IntentService {
- private static final String LOGTAG = "GeckoFeedService";
-
- public static final String ACTION_SETUP = AppConstants.ANDROID_PACKAGE_NAME + ".FEEDS.SETUP";
- public static final String ACTION_SUBSCRIBE = AppConstants.ANDROID_PACKAGE_NAME + ".FEEDS.SUBSCRIBE";
- public static final String ACTION_CHECK = AppConstants.ANDROID_PACKAGE_NAME + ".FEEDS.CHECK";
- public static final String ACTION_ENROLL = AppConstants.ANDROID_PACKAGE_NAME + ".FEEDS.ENROLL";
- public static final String ACTION_WITHDRAW = AppConstants.ANDROID_PACKAGE_NAME + ".FEEDS.WITHDRAW";
-
- public static void setup(Context context) {
- Intent intent = new Intent(context, FeedService.class);
- intent.setAction(ACTION_SETUP);
- context.startService(intent);
- }
-
- public static void subscribe(Context context, String feedUrl) {
- Intent intent = new Intent(context, FeedService.class);
- intent.setAction(ACTION_SUBSCRIBE);
- intent.putExtra(SubscribeToFeedAction.EXTRA_FEED_URL, feedUrl);
- context.startService(intent);
- }
-
- public FeedService() {
- super(LOGTAG);
- }
-
- private BrowserDB browserDB;
-
- @Override
- public void onCreate() {
- super.onCreate();
-
- browserDB = BrowserDB.from(this);
- }
-
- @Override
- protected void onHandleIntent(Intent intent) {
- try {
- if (intent == null) {
- return;
- }
-
- Log.d(LOGTAG, "Service started with action: " + intent.getAction());
-
- if (!isInExperiment(this)) {
- Log.d(LOGTAG, "Not in content notifications experiment. Skipping.");
- return;
- }
-
- FeedAction action = createActionForIntent(intent);
- if (action == null) {
- Log.d(LOGTAG, "No action to process");
- return;
- }
-
- if (action.requiresPreferenceEnabled() && !isPreferenceEnabled()) {
- Log.d(LOGTAG, "Preference is disabled. Skipping.");
- return;
- }
-
- if (action.requiresNetwork() && !isConnectedToUnmeteredNetwork()) {
- // For now just skip if we are not connected or the network is metered. We do not want
- // to use precious mobile traffic.
- Log.d(LOGTAG, "Not connected to a network or network is metered. Skipping.");
- return;
- }
-
- action.perform(browserDB, intent);
- } finally {
- FeedAlarmReceiver.completeWakefulIntent(intent);
- }
-
- Log.d(LOGTAG, "Done.");
- }
-
- @Nullable
- private FeedAction createActionForIntent(Intent intent) {
- final Context context = getApplicationContext();
-
- switch (intent.getAction()) {
- case ACTION_SETUP:
- return new SetupAlarmsAction(context);
-
- case ACTION_SUBSCRIBE:
- return new SubscribeToFeedAction(context);
-
- case ACTION_CHECK:
- return new CheckForUpdatesAction(context);
-
- case ACTION_ENROLL:
- return new EnrollSubscriptionsAction(context);
-
- case ACTION_WITHDRAW:
- return new WithdrawSubscriptionsAction(context);
-
- default:
- throw new AssertionError("Unknown action: " + intent.getAction());
- }
- }
-
- private boolean isConnectedToUnmeteredNetwork() {
- ConnectivityManager manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo networkInfo = manager.getActiveNetworkInfo();
- if (networkInfo == null || !networkInfo.isConnected()) {
- return false;
- }
-
- return !ConnectivityManagerCompat.isActiveNetworkMetered(manager);
- }
-
- public static boolean isInExperiment(Context context) {
- return SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_12HRS) ||
- SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_5PM) ||
- SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_8AM);
- }
-
- public static String getEnabledExperiment(Context context) {
- String experiment = null;
-
- if (SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_12HRS)) {
- experiment = Experiments.CONTENT_NOTIFICATIONS_12HRS;
- } else if (SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_8AM)) {
- experiment = Experiments.CONTENT_NOTIFICATIONS_8AM;
- } else if (SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_5PM)) {
- experiment = Experiments.CONTENT_NOTIFICATIONS_5PM;
- }
-
- return experiment;
- }
-
- private boolean isPreferenceEnabled() {
- return GeckoSharedPrefs.forApp(this).getBoolean(GeckoPreferences.PREFS_NOTIFICATIONS_CONTENT, true);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/action/CheckForUpdatesAction.java b/mobile/android/base/java/org/mozilla/gecko/feeds/action/CheckForUpdatesAction.java
deleted file mode 100644
index 09a2b12b6..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/action/CheckForUpdatesAction.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.action;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationManagerCompat;
-import android.support.v4.content.ContextCompat;
-import android.text.format.DateFormat;
-
-import org.json.JSONException;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.UrlAnnotations;
-import org.mozilla.gecko.feeds.ContentNotificationsDelegate;
-import org.mozilla.gecko.feeds.FeedFetcher;
-import org.mozilla.gecko.feeds.FeedService;
-import org.mozilla.gecko.feeds.parser.Feed;
-import org.mozilla.gecko.feeds.subscriptions.FeedSubscription;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.util.StringUtils;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-/**
- * CheckForUpdatesAction: Check if feeds we subscribed to have new content available.
- */
-public class CheckForUpdatesAction extends FeedAction {
- /**
- * This extra will be added to Intents fired by the notification.
- */
- public static final String EXTRA_CONTENT_NOTIFICATION = "content-notification";
-
- private final Context context;
-
- public CheckForUpdatesAction(Context context) {
- this.context = context;
- }
-
- @Override
- public void perform(BrowserDB browserDB, Intent intent) {
- final UrlAnnotations urlAnnotations = browserDB.getUrlAnnotations();
- final ContentResolver resolver = context.getContentResolver();
- final List<Feed> updatedFeeds = new ArrayList<>();
-
- log("Checking feeds for updates..");
-
- Cursor cursor = urlAnnotations.getFeedSubscriptions(resolver);
- if (cursor == null) {
- return;
- }
-
- try {
- while (cursor.moveToNext()) {
- FeedSubscription subscription = FeedSubscription.fromCursor(cursor);
-
- FeedFetcher.FeedResponse response = checkFeedForUpdates(subscription);
- if (response != null) {
- final Feed feed = response.feed;
-
- if (!hasBeenVisited(browserDB, feed.getLastItem().getURL())) {
- // Only notify about this update if the last item hasn't been visited yet.
- updatedFeeds.add(feed);
- } else {
- Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context));
- Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL,
- TelemetryContract.Method.SERVICE,
- "content_update");
- Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context));
- }
-
- urlAnnotations.updateFeedSubscription(resolver, subscription);
- }
- }
- } catch (JSONException e) {
- log("Could not deserialize subscription", e);
- } finally {
- cursor.close();
- }
-
- showNotification(updatedFeeds);
- }
-
- private FeedFetcher.FeedResponse checkFeedForUpdates(FeedSubscription subscription) {
- log("Checking feed: " + subscription.getFeedTitle());
-
- FeedFetcher.FeedResponse response = fetchFeed(subscription);
- if (response == null) {
- return null;
- }
-
- if (subscription.hasBeenUpdated(response)) {
- log("* Feed has changed. New item: " + response.feed.getLastItem().getTitle());
-
- subscription.update(response);
-
- return response;
-
- }
-
- return null;
- }
-
- /**
- * Returns true if this URL has been visited before.
- *
- * We do an exact match. So this can fail if the feed uses a different URL and redirects to
- * content. But it's better than no checks at all.
- */
- private boolean hasBeenVisited(final BrowserDB browserDB, final String url) {
- final Cursor cursor = browserDB.getHistoryForURL(context.getContentResolver(), url);
- if (cursor == null) {
- return false;
- }
-
- try {
- if (cursor.moveToFirst()) {
- return cursor.getInt(cursor.getColumnIndex(BrowserContract.History.VISITS)) > 0;
- }
- } finally {
- cursor.close();
- }
-
- return false;
- }
-
- private void showNotification(List<Feed> updatedFeeds) {
- final int feedCount = updatedFeeds.size();
- if (feedCount == 0) {
- return;
- }
-
- if (feedCount == 1) {
- showNotificationForSingleUpdate(updatedFeeds.get(0));
- } else {
- showNotificationForMultipleUpdates(updatedFeeds);
- }
-
- Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context));
- Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.NOTIFICATION, "content_update");
- Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context));
- }
-
- private void showNotificationForSingleUpdate(Feed feed) {
- final String date = DateFormat.getMediumDateFormat(context).format(new Date(feed.getLastItem().getTimestamp()));
-
- final NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle()
- .bigText(feed.getLastItem().getTitle())
- .setBigContentTitle(feed.getTitle())
- .setSummaryText(context.getString(R.string.content_notification_updated_on, date));
-
- final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, createOpenIntent(feed), PendingIntent.FLAG_UPDATE_CURRENT);
-
- final Notification notification = new NotificationCompat.Builder(context)
- .setSmallIcon(R.drawable.ic_status_logo)
- .setContentTitle(feed.getTitle())
- .setContentText(feed.getLastItem().getTitle())
- .setStyle(style)
- .setColor(ContextCompat.getColor(context, R.color.fennec_ui_orange))
- .setContentIntent(pendingIntent)
- .setAutoCancel(true)
- .addAction(createOpenAction(feed))
- .addAction(createNotificationSettingsAction())
- .build();
-
- NotificationManagerCompat.from(context).notify(R.id.websiteContentNotification, notification);
- }
-
- private void showNotificationForMultipleUpdates(List<Feed> feeds) {
- final NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
- for (Feed feed : feeds) {
- inboxStyle.addLine(StringUtils.stripScheme(feed.getLastItem().getURL(), StringUtils.UrlFlags.STRIP_HTTPS));
- }
- inboxStyle.setSummaryText(context.getString(R.string.content_notification_summary));
-
- final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, createOpenIntent(feeds), PendingIntent.FLAG_UPDATE_CURRENT);
-
- Notification notification = new NotificationCompat.Builder(context)
- .setSmallIcon(R.drawable.ic_status_logo)
- .setContentTitle(context.getString(R.string.content_notification_title_plural, feeds.size()))
- .setContentText(context.getString(R.string.content_notification_summary))
- .setStyle(inboxStyle)
- .setColor(ContextCompat.getColor(context, R.color.fennec_ui_orange))
- .setContentIntent(pendingIntent)
- .setAutoCancel(true)
- .addAction(createOpenAction(feeds))
- .setNumber(feeds.size())
- .addAction(createNotificationSettingsAction())
- .build();
-
- NotificationManagerCompat.from(context).notify(R.id.websiteContentNotification, notification);
- }
-
- private Intent createOpenIntent(Feed feed) {
- final List<Feed> feeds = new ArrayList<>();
- feeds.add(feed);
-
- return createOpenIntent(feeds);
- }
-
- private Intent createOpenIntent(List<Feed> feeds) {
- final ArrayList<String> urls = new ArrayList<>();
- for (Feed feed : feeds) {
- urls.add(feed.getLastItem().getURL());
- }
-
- final Intent intent = new Intent(context, BrowserApp.class);
- intent.setAction(ContentNotificationsDelegate.ACTION_CONTENT_NOTIFICATION);
- intent.putStringArrayListExtra(ContentNotificationsDelegate.EXTRA_URLS, urls);
-
- return intent;
- }
-
- private NotificationCompat.Action createOpenAction(Feed feed) {
- final List<Feed> feeds = new ArrayList<>();
- feeds.add(feed);
-
- return createOpenAction(feeds);
- }
-
- private NotificationCompat.Action createOpenAction(List<Feed> feeds) {
- Intent intent = createOpenIntent(feeds);
- intent.putExtra(ContentNotificationsDelegate.EXTRA_READ_BUTTON, true);
-
- PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
-
- return new NotificationCompat.Action(
- R.drawable.open_in_browser,
- context.getString(R.string.content_notification_action_read_now),
- pendingIntent);
- }
-
- private NotificationCompat.Action createNotificationSettingsAction() {
- final Intent intent = new Intent(GeckoApp.ACTION_LAUNCH_SETTINGS);
- intent.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
- intent.putExtra(EXTRA_CONTENT_NOTIFICATION, true);
-
- GeckoPreferences.setResourceToOpen(intent, "preferences_notifications");
-
- PendingIntent settingsIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
-
- return new NotificationCompat.Action(
- R.drawable.settings_notifications,
- context.getString(R.string.content_notification_action_settings),
- settingsIntent);
- }
-
- private FeedFetcher.FeedResponse fetchFeed(FeedSubscription subscription) {
- return FeedFetcher.fetchAndParseFeedIfModified(
- subscription.getFeedUrl(),
- subscription.getETag(),
- subscription.getLastModified()
- );
- }
-
- @Override
- public boolean requiresNetwork() {
- return true;
- }
-
- @Override
- public boolean requiresPreferenceEnabled() {
- return true;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/action/EnrollSubscriptionsAction.java b/mobile/android/base/java/org/mozilla/gecko/feeds/action/EnrollSubscriptionsAction.java
deleted file mode 100644
index b778938fd..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/action/EnrollSubscriptionsAction.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.action;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.text.TextUtils;
-
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.UrlAnnotations;
-import org.mozilla.gecko.feeds.FeedService;
-import org.mozilla.gecko.feeds.knownsites.KnownSiteBlogger;
-import org.mozilla.gecko.feeds.knownsites.KnownSite;
-import org.mozilla.gecko.feeds.knownsites.KnownSiteMedium;
-import org.mozilla.gecko.feeds.knownsites.KnownSiteTumblr;
-import org.mozilla.gecko.feeds.knownsites.KnownSiteWordpress;
-
-/**
- * EnrollSubscriptionsAction: Search for bookmarks of known sites we can subscribe to.
- */
-public class EnrollSubscriptionsAction extends FeedAction {
- private static final String LOGTAG = "FeedEnrollAction";
-
- private static final KnownSite[] knownSites = {
- new KnownSiteMedium(),
- new KnownSiteBlogger(),
- new KnownSiteWordpress(),
- new KnownSiteTumblr(),
- };
-
- private Context context;
-
- public EnrollSubscriptionsAction(Context context) {
- this.context = context;
- }
-
- @Override
- public void perform(BrowserDB db, Intent intent) {
- log("Searching for bookmarks to enroll in updates");
-
- final ContentResolver contentResolver = context.getContentResolver();
-
- for (KnownSite knownSite : knownSites) {
- searchFor(db, contentResolver, knownSite);
- }
- }
-
- @Override
- public boolean requiresNetwork() {
- return false;
- }
-
- @Override
- public boolean requiresPreferenceEnabled() {
- return true;
- }
-
- private void searchFor(BrowserDB db, ContentResolver contentResolver, KnownSite knownSite) {
- final UrlAnnotations urlAnnotations = db.getUrlAnnotations();
-
- final Cursor cursor = db.getBookmarksForPartialUrl(contentResolver, knownSite.getURLSearchString());
- if (cursor == null) {
- log("Nothing found (" + knownSite.getClass().getSimpleName() + ")");
- return;
- }
-
- try {
- log("Found " + cursor.getCount() + " websites");
-
- while (cursor.moveToNext()) {
-
- final String url = cursor.getString(cursor.getColumnIndex(BrowserContract.Bookmarks.URL));
-
- log(" URL: " + url);
-
- String feedUrl = knownSite.getFeedFromURL(url);
- if (TextUtils.isEmpty(feedUrl)) {
- log("Could not determine feed for URL: " + url);
- return;
- }
-
- if (!urlAnnotations.hasFeedUrlForWebsite(contentResolver, url)) {
- urlAnnotations.insertFeedUrl(contentResolver, url, feedUrl);
- }
-
- if (!urlAnnotations.hasFeedSubscription(contentResolver, feedUrl)) {
- FeedService.subscribe(context, feedUrl);
- }
- }
- } finally {
- cursor.close();
- }
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/action/FeedAction.java b/mobile/android/base/java/org/mozilla/gecko/feeds/action/FeedAction.java
deleted file mode 100644
index acfaa8b4d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/action/FeedAction.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.action;
-
-import android.content.Intent;
-import android.util.Log;
-
-import org.mozilla.gecko.db.BrowserDB;
-
-/**
- * Interface for actions run by FeedService.
- */
-public abstract class FeedAction {
- public static final boolean DEBUG_LOG = false;
-
- /**
- * Perform this action.
- *
- * @param browserDB database instance to perform the action.
- * @param intent used to start the service.
- */
- public abstract void perform(BrowserDB browserDB, Intent intent);
-
- /**
- * Does this action require an active network connection?
- */
- public abstract boolean requiresNetwork();
-
- /**
- * Should this action only run if the preference is enabled?
- */
- public abstract boolean requiresPreferenceEnabled();
-
- /**
- * This method will swallow all log messages to avoid logging potential personal information.
- *
- * For debugging purposes set {@code DEBUG_LOG} to true.
- */
- public void log(String message) {
- if (DEBUG_LOG) {
- Log.d("Gecko" + getClass().getSimpleName(), message);
- }
- }
-
- /**
- * This method will swallow all log messages to avoid logging potential personal information.
- *
- * For debugging purposes set {@code DEBUG_LOG} to true.
- */
- public void log(String message, Throwable throwable) {
- if (DEBUG_LOG) {
- Log.d("Gecko" + getClass().getSimpleName(), message, throwable);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/action/SetupAlarmsAction.java b/mobile/android/base/java/org/mozilla/gecko/feeds/action/SetupAlarmsAction.java
deleted file mode 100644
index f5bf39997..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/action/SetupAlarmsAction.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.action;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.os.SystemClock;
-
-import com.keepsafe.switchboard.SwitchBoard;
-
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.feeds.FeedAlarmReceiver;
-import org.mozilla.gecko.feeds.FeedService;
-import org.mozilla.gecko.Experiments;
-
-import java.text.DateFormat;
-import java.util.Calendar;
-
-/**
- * SetupAlarmsAction: Set up alarms to run various actions every now and then.
- */
-public class SetupAlarmsAction extends FeedAction {
- private static final String LOGTAG = "FeedSetupAction";
-
- private Context context;
-
- public SetupAlarmsAction(Context context) {
- this.context = context;
- }
-
- @Override
- public void perform(BrowserDB browserDB, Intent intent) {
- final AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-
- cancelPreviousAlarms(alarmManager);
- scheduleAlarms(alarmManager);
- }
-
- @Override
- public boolean requiresNetwork() {
- return false;
- }
-
- @Override
- public boolean requiresPreferenceEnabled() {
- return false;
- }
-
- private void cancelPreviousAlarms(AlarmManager alarmManager) {
- final PendingIntent withdrawIntent = getWithdrawPendingIntent();
- alarmManager.cancel(withdrawIntent);
-
- final PendingIntent enrollIntent = getEnrollPendingIntent();
- alarmManager.cancel(enrollIntent);
-
- final PendingIntent checkIntent = getCheckPendingIntent();
- alarmManager.cancel(checkIntent);
-
- log("Cancelled previous alarms");
- }
-
- private void scheduleAlarms(AlarmManager alarmManager) {
- alarmManager.setInexactRepeating(
- AlarmManager.ELAPSED_REALTIME,
- SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_FIFTEEN_MINUTES,
- AlarmManager.INTERVAL_DAY,
- getWithdrawPendingIntent());
-
- alarmManager.setInexactRepeating(
- AlarmManager.ELAPSED_REALTIME,
- SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HALF_HOUR,
- AlarmManager.INTERVAL_DAY,
- getEnrollPendingIntent()
- );
-
- if (SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_12HRS)) {
- scheduleUpdateCheckEvery12Hours(alarmManager);
- }
-
- if (SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_8AM)) {
- scheduleUpdateAtFullHour(alarmManager, 8);
- }
-
- if (SwitchBoard.isInExperiment(context, Experiments.CONTENT_NOTIFICATIONS_5PM)) {
- scheduleUpdateAtFullHour(alarmManager, 17);
- }
-
-
- log("Scheduled alarms");
- }
-
- private void scheduleUpdateCheckEvery12Hours(AlarmManager alarmManager) {
- alarmManager.setInexactRepeating(
- AlarmManager.ELAPSED_REALTIME,
- SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HOUR,
- AlarmManager.INTERVAL_HALF_DAY,
- getCheckPendingIntent()
- );
- }
-
- private void scheduleUpdateAtFullHour(AlarmManager alarmManager, int hourOfDay) {
- final Calendar calendar = Calendar.getInstance();
-
- if (calendar.get(Calendar.HOUR_OF_DAY) >= hourOfDay) {
- // This time has already passed today. Try again tomorrow.
- calendar.add(Calendar.DAY_OF_MONTH, 1);
- }
-
- calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
- calendar.set(Calendar.MINUTE, 0);
- calendar.set(Calendar.SECOND, 0);
- calendar.set(Calendar.MILLISECOND, 0);
-
- alarmManager.setInexactRepeating(
- AlarmManager.RTC,
- calendar.getTimeInMillis(),
- AlarmManager.INTERVAL_DAY,
- getCheckPendingIntent()
- );
-
- log("Scheduled update alarm at " + DateFormat.getDateTimeInstance().format(calendar.getTime()));
- }
-
- private PendingIntent getWithdrawPendingIntent() {
- Intent intent = new Intent(context, FeedAlarmReceiver.class);
- intent.setAction(FeedService.ACTION_WITHDRAW);
- return PendingIntent.getBroadcast(context, 0, intent, 0);
- }
-
- private PendingIntent getEnrollPendingIntent() {
- Intent intent = new Intent(context, FeedAlarmReceiver.class);
- intent.setAction(FeedService.ACTION_ENROLL);
- return PendingIntent.getBroadcast(context, 0, intent, 0);
- }
-
- private PendingIntent getCheckPendingIntent() {
- Intent intent = new Intent(context, FeedAlarmReceiver.class);
- intent.setAction(FeedService.ACTION_CHECK);
- return PendingIntent.getBroadcast(context, 0, intent, 0);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/action/SubscribeToFeedAction.java b/mobile/android/base/java/org/mozilla/gecko/feeds/action/SubscribeToFeedAction.java
deleted file mode 100644
index fbfce1af2..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/action/SubscribeToFeedAction.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.action;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.UrlAnnotations;
-import org.mozilla.gecko.feeds.FeedFetcher;
-import org.mozilla.gecko.feeds.FeedService;
-import org.mozilla.gecko.feeds.subscriptions.FeedSubscription;
-
-/**
- * SubscribeToFeedAction: Try to fetch a feed and create a subscription if successful.
- */
-public class SubscribeToFeedAction extends FeedAction {
- private static final String LOGTAG = "FeedSubscribeAction";
-
- public static final String EXTRA_FEED_URL = "feed_url";
-
- private Context context;
-
- public SubscribeToFeedAction(Context context) {
- this.context = context;
- }
-
- @Override
- public void perform(BrowserDB browserDB, Intent intent) {
- final UrlAnnotations urlAnnotations = browserDB.getUrlAnnotations();
-
- final Bundle extras = intent.getExtras();
- final String feedUrl = extras.getString(EXTRA_FEED_URL);
-
- if (urlAnnotations.hasFeedSubscription(context.getContentResolver(), feedUrl)) {
- log("Already subscribed to " + feedUrl + ". Skipping.");
- return;
- }
-
- log("Subscribing to feed: " + feedUrl);
-
- subscribe(urlAnnotations, feedUrl);
- }
-
- @Override
- public boolean requiresNetwork() {
- return true;
- }
-
- @Override
- public boolean requiresPreferenceEnabled() {
- return true;
- }
-
- private void subscribe(UrlAnnotations urlAnnotations, String feedUrl) {
- FeedFetcher.FeedResponse response = FeedFetcher.fetchAndParseFeed(feedUrl);
- if (response == null) {
- log(String.format("Could not fetch feed (%s). Not subscribing for now.", feedUrl));
- return;
- }
-
- log("Subscribing to feed: " + response.feed.getTitle());
- log(" Last item: " + response.feed.getLastItem().getTitle());
-
- final FeedSubscription subscription = FeedSubscription.create(feedUrl, response);
-
- urlAnnotations.insertFeedSubscription(context.getContentResolver(), subscription);
-
- Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context));
- Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.SERVICE, "content_update");
- Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context));
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/action/WithdrawSubscriptionsAction.java b/mobile/android/base/java/org/mozilla/gecko/feeds/action/WithdrawSubscriptionsAction.java
deleted file mode 100644
index 6f955c185..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/action/WithdrawSubscriptionsAction.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.action;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-
-import org.json.JSONException;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.UrlAnnotations;
-import org.mozilla.gecko.feeds.FeedService;
-import org.mozilla.gecko.feeds.subscriptions.FeedSubscription;
-
-/**
- * WithdrawSubscriptionsAction: Look for feeds to unsubscribe from.
- */
-public class WithdrawSubscriptionsAction extends FeedAction {
- private static final String LOGTAG = "FeedWithdrawAction";
-
- private Context context;
-
- public WithdrawSubscriptionsAction(Context context) {
- this.context = context;
- }
-
- @Override
- public void perform(BrowserDB browserDB, Intent intent) {
- log("Searching for subscriptions to remove..");
-
- final UrlAnnotations urlAnnotations = browserDB.getUrlAnnotations();
- final ContentResolver resolver = context.getContentResolver();
-
- removeFeedsOfUnknownUrls(browserDB, urlAnnotations, resolver);
- removeSubscriptionsOfRemovedFeeds(urlAnnotations, resolver);
- }
-
- /**
- * Search for website URLs with a feed assigned. Remove entry if website URL is not known anymore:
- * For now this means the website is not bookmarked.
- */
- private void removeFeedsOfUnknownUrls(BrowserDB browserDB, UrlAnnotations urlAnnotations, ContentResolver resolver) {
- Cursor cursor = urlAnnotations.getWebsitesWithFeedUrl(resolver);
- if (cursor == null) {
- return;
- }
-
- try {
- while (cursor.moveToNext()) {
- final String url = cursor.getString(cursor.getColumnIndex(BrowserContract.UrlAnnotations.URL));
-
- if (!browserDB.isBookmark(resolver, url)) {
- log("Removing feed for unknown URL: " + url);
-
- urlAnnotations.deleteFeedUrl(resolver, url);
- }
- }
- } finally {
- cursor.close();
- }
- }
-
- /**
- * Remove subscriptions of feed URLs that are not assigned to a website URL (anymore).
- */
- private void removeSubscriptionsOfRemovedFeeds(UrlAnnotations urlAnnotations, ContentResolver resolver) {
- Cursor cursor = urlAnnotations.getFeedSubscriptions(resolver);
- if (cursor == null) {
- return;
- }
-
- try {
- while (cursor.moveToNext()) {
- final FeedSubscription subscription = FeedSubscription.fromCursor(cursor);
-
- if (!urlAnnotations.hasWebsiteForFeedUrl(resolver, subscription.getFeedUrl())) {
- log("Removing subscription for feed: " + subscription.getFeedUrl());
-
- urlAnnotations.deleteFeedSubscription(resolver, subscription);
-
- Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context));
- Telemetry.sendUIEvent(TelemetryContract.Event.UNSAVE, TelemetryContract.Method.SERVICE, "content_update");
- Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(context));
- }
- }
- } catch (JSONException e) {
- log("Could not deserialize subscription", e);
- } finally {
- cursor.close();
- }
- }
-
- @Override
- public boolean requiresNetwork() {
- return false;
- }
-
- @Override
- public boolean requiresPreferenceEnabled() {
- return true;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSite.java b/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSite.java
deleted file mode 100644
index febfbb0c7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSite.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.knownsites;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-/**
- * A site we know and for which we can guess the feed URL from an arbitrary URL.
- */
-public interface KnownSite {
- /**
- * Get a search string to find URLs of this site in our database. This search string is usually
- * a partial domain / URL.
- *
- * For example we could return "medium.com" to find all URLs that contain this string. This could
- * obviously find URLs that are not actually medium.com sites. This is acceptable as long as
- * getFeedFromURL() can handle these inputs and either returns a feed for valid URLs or null for
- * other matches that are not related to this site.
- */
- @NonNull String getURLSearchString();
-
- /**
- * Get the Feed URL for this URL. For a known site we can "guess" the feed URL from an URL
- * pointing to any page. The input URL will be a result from the database found with the value
- * returned by getURLSearchString().
- *
- * Example:
- * - Input: https://medium.com/@antlam/ux-thoughts-for-2016-1fc1d6e515e8
- * - Output: https://medium.com/feed/@antlam
- *
- * @return the url representing a feed, or null if a feed could not be determined.
- */
- @Nullable String getFeedFromURL(String url);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteBlogger.java b/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteBlogger.java
deleted file mode 100644
index 6bb3629bf..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteBlogger.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.knownsites;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Blogger.com
- */
-public class KnownSiteBlogger implements KnownSite {
- @Override
- public String getURLSearchString() {
- return ".blogspot.com";
- }
-
- @Override
- public String getFeedFromURL(String url) {
- Pattern pattern = Pattern.compile("https?://(www\\.)?(.*?)\\.blogspot\\.com(/.*)?");
- Matcher matcher = pattern.matcher(url);
- if (matcher.matches()) {
- return String.format("https://%s.blogspot.com/feeds/posts/default", matcher.group(2));
- }
- return null;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteMedium.java b/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteMedium.java
deleted file mode 100644
index a96e83fcd..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteMedium.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.knownsites;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Medium.com
- */
-public class KnownSiteMedium implements KnownSite {
- @Override
- public String getURLSearchString() {
- return "://medium.com/";
- }
-
- @Override
- public String getFeedFromURL(String url) {
- Pattern pattern = Pattern.compile("https?://medium.com/([^/]+)(/.*)?");
- Matcher matcher = pattern.matcher(url);
- if (matcher.matches()) {
- return String.format("https://medium.com/feed/%s", matcher.group(1));
- }
- return null;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteTumblr.java b/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteTumblr.java
deleted file mode 100644
index c9f480013..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteTumblr.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.knownsites;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Tumblr.com
- */
-public class KnownSiteTumblr implements KnownSite {
- @Override
- public String getURLSearchString() {
- return ".tumblr.com";
- }
-
- @Override
- public String getFeedFromURL(String url) {
- final Pattern pattern = Pattern.compile("https?://(.*?).tumblr.com(/.*)?");
- final Matcher matcher = pattern.matcher(url);
- if (matcher.matches()) {
- final String username = matcher.group(1);
- if (username.equals("www")) {
- return null;
- }
- return "http://" + username + ".tumblr.com/rss";
- }
- return null;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteWordpress.java b/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteWordpress.java
deleted file mode 100644
index a74b41a74..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/knownsites/KnownSiteWordpress.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.mozilla.gecko.feeds.knownsites;
-
-import android.support.annotation.NonNull;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Wordpress.com
- */
-public class KnownSiteWordpress implements KnownSite {
- @Override
- public String getURLSearchString() {
- return ".wordpress.com";
- }
-
- @Override
- public String getFeedFromURL(String url) {
- Pattern pattern = Pattern.compile("https?://(.*?).wordpress.com(/.*)?");
- Matcher matcher = pattern.matcher(url);
- if (matcher.matches()) {
- return "https://" + matcher.group(1) + ".wordpress.com/feed/";
- }
- return null;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/parser/Feed.java b/mobile/android/base/java/org/mozilla/gecko/feeds/parser/Feed.java
deleted file mode 100644
index aefc72aa7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/parser/Feed.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.parser;
-
-import ch.boye.httpclientandroidlib.util.TextUtils;
-
-public class Feed {
- private String title;
- private String websiteURL;
- private String feedURL;
- private Item lastItem;
-
- public static Feed create(String title, String websiteURL, String feedURL, Item lastItem) {
- Feed feed = new Feed();
-
- feed.setTitle(title);
- feed.setWebsiteURL(websiteURL);
- feed.setFeedURL(feedURL);
- feed.setLastItem(lastItem);
-
- return feed;
- }
-
- /* package-private */ Feed() {}
-
- /* package-private */ void setTitle(String title) {
- this.title = title;
- }
-
- /* package-private */ void setWebsiteURL(String websiteURL) {
- this.websiteURL = websiteURL;
- }
-
- /* package-private */ void setFeedURL(String feedURL) {
- this.feedURL = feedURL;
- }
-
- /* package-private */ void setLastItem(Item lastItem) {
- this.lastItem = lastItem;
- }
-
- /**
- * Is this feed object sufficiently complete so that we can use it?
- */
- /* package-private */ boolean isSufficientlyComplete() {
- return !TextUtils.isEmpty(title) &&
- lastItem != null &&
- !TextUtils.isEmpty(lastItem.getURL()) &&
- !TextUtils.isEmpty(lastItem.getTitle());
- }
-
- public String getTitle() {
- return title;
- }
-
- public String getWebsiteURL() {
- return websiteURL;
- }
-
- public String getFeedURL() {
- return feedURL;
- }
-
- public Item getLastItem() {
- return lastItem;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/parser/Item.java b/mobile/android/base/java/org/mozilla/gecko/feeds/parser/Item.java
deleted file mode 100644
index 8d8f6d44e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/parser/Item.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.parser;
-
-public class Item {
- private String title;
- private String url;
- private long timestamp;
-
- public static Item create(String title, String url, long timestamp) {
- Item item = new Item();
-
- item.setTitle(title);
- item.setURL(url);
- item.setTimestamp(timestamp);
-
- return item;
- }
-
- /* package-private */ void setTitle(String title) {
- this.title = title;
- }
-
- /* package-private */ void setURL(String url) {
- this.url = url;
- }
-
- /* package-private */ void setTimestamp(long timestamp) {
- this.timestamp = timestamp;
- }
-
- public String getTitle() {
- return title;
- }
-
- public String getURL() {
- return url;
- }
-
- /**
- * @return the number of milliseconds since Jan. 1, 1970, midnight GMT.
- */
- public long getTimestamp() {
- return timestamp;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/parser/SimpleFeedParser.java b/mobile/android/base/java/org/mozilla/gecko/feeds/parser/SimpleFeedParser.java
deleted file mode 100644
index afb1b7cb2..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/parser/SimpleFeedParser.java
+++ /dev/null
@@ -1,367 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.parser;
-
-import android.util.Log;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlPullParserFactory;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-
-import ch.boye.httpclientandroidlib.util.TextUtils;
-
-/**
- * A super simple feed parser written for implementing "content notifications". This XML Pull Parser
- * can read ATOM and RSS feeds and returns an object describing the feed and the latest entry.
- */
-public class SimpleFeedParser {
- /**
- * Generic exception that's thrown by the parser whenever a stream cannot be parsed.
- */
- public static class ParserException extends Exception {
- private static final long serialVersionUID = -6119538440219805603L;
-
- public ParserException(Throwable cause) {
- super(cause);
- }
-
- public ParserException(String message) {
- super(message);
- }
- }
-
- private static final String LOGTAG = "Gecko/FeedParser";
-
- private static final String TAG_RSS = "rss";
- private static final String TAG_FEED = "feed";
- private static final String TAG_RDF = "RDF";
- private static final String TAG_TITLE = "title";
- private static final String TAG_ITEM = "item";
- private static final String TAG_LINK = "link";
- private static final String TAG_ENTRY = "entry";
- private static final String TAG_PUBDATE = "pubDate";
- private static final String TAG_UPDATED = "updated";
- private static final String TAG_DATE = "date";
- private static final String TAG_SOURCE = "source";
- private static final String TAG_IMAGE = "image";
- private static final String TAG_CONTENT = "content";
-
- private class ParserState {
- public Feed feed;
- public Item currentItem;
- public boolean isRSS;
- public boolean isATOM;
- public boolean inSource;
- public boolean inImage;
- public boolean inContent;
- }
-
- public Feed parse(InputStream in) throws ParserException, IOException {
- final ParserState state = new ParserState();
-
- try {
- final XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
- factory.setNamespaceAware(true);
-
- XmlPullParser parser = factory.newPullParser();
- parser.setInput(in, null);
-
- int eventType = parser.getEventType();
-
- while (eventType != XmlPullParser.END_DOCUMENT) {
- switch (eventType) {
- case XmlPullParser.START_DOCUMENT:
- handleStartDocument(state);
- break;
-
- case XmlPullParser.START_TAG:
- handleStartTag(parser, state);
- break;
-
- case XmlPullParser.END_TAG:
- handleEndTag(parser, state);
- break;
- }
-
- eventType = parser.next();
- }
- } catch (XmlPullParserException e) {
- throw new ParserException(e);
- }
-
- if (!state.feed.isSufficientlyComplete()) {
- throw new ParserException("Feed is not sufficiently complete");
- }
-
- return state.feed;
- }
-
- private void handleStartDocument(ParserState state) {
- state.feed = new Feed();
- }
-
- private void handleStartTag(XmlPullParser parser, ParserState state) throws IOException, XmlPullParserException {
- switch (parser.getName()) {
- case TAG_RSS:
- state.isRSS = true;
- break;
-
- case TAG_FEED:
- state.isATOM = true;
- break;
-
- case TAG_RDF:
- // This is a RSS 1.0 feed
- state.isRSS = true;
- break;
-
- case TAG_ITEM:
- case TAG_ENTRY:
- state.currentItem = new Item();
- break;
-
- case TAG_TITLE:
- handleTitleStartTag(parser, state);
- break;
-
- case TAG_LINK:
- handleLinkStartTag(parser, state);
- break;
-
- case TAG_PUBDATE:
- handlePubDateStartTag(parser, state);
- break;
-
- case TAG_UPDATED:
- handleUpdatedStartTag(parser, state);
- break;
-
- case TAG_DATE:
- handleDateStartTag(parser, state);
- break;
-
- case TAG_SOURCE:
- state.inSource = true;
- break;
-
- case TAG_IMAGE:
- state.inImage = true;
- break;
-
- case TAG_CONTENT:
- state.inContent = true;
- break;
- }
- }
-
- private void handleEndTag(XmlPullParser parser, ParserState state) {
- switch (parser.getName()) {
- case TAG_ITEM:
- case TAG_ENTRY:
- handleItemOrEntryREndTag(state);
- break;
-
- case TAG_SOURCE:
- state.inSource = false;
- break;
-
- case TAG_IMAGE:
- state.inImage = false;
- break;
-
- case TAG_CONTENT:
- state.inContent = false;
- break;
- }
- }
-
- private void handleTitleStartTag(XmlPullParser parser, ParserState state) throws IOException, XmlPullParserException {
- if (state.inSource || state.inImage || state.inContent) {
- // We do not care about titles in <source>, <image> or <media> tags.
- return;
- }
-
- String title = getTextUntilEndTag(parser, TAG_TITLE);
-
- title = title.replaceAll("[\r\n]", " ");
- title = title.replaceAll(" +", " ");
-
- if (state.currentItem != null) {
- state.currentItem.setTitle(title);
- } else {
- state.feed.setTitle(title);
- }
- }
-
- private void handleLinkStartTag(XmlPullParser parser, ParserState state) throws IOException, XmlPullParserException {
- if (state.inSource || state.inImage) {
- // We do not care about links in <source> or <image> tags.
- return;
- }
-
- Map<String, String> attributes = fetchAttributes(parser);
-
- if (attributes.size() > 0) {
- String rel = attributes.get("rel");
-
- if (state.currentItem == null && "self".equals(rel)) {
- state.feed.setFeedURL(attributes.get("href"));
- return;
- }
-
- if (rel == null || "alternate".equals(rel)) {
- String type = attributes.get("type");
- if (type == null || type.equals("text/html")) {
- String link = attributes.get("href");
- if (TextUtils.isEmpty(link)) {
- return;
- }
-
- if (state.currentItem != null) {
- state.currentItem.setURL(link);
- } else {
- state.feed.setWebsiteURL(link);
- }
-
- return;
- }
- }
- }
-
- if (state.isRSS) {
- String link = getTextUntilEndTag(parser, TAG_LINK);
- if (TextUtils.isEmpty(link)) {
- return;
- }
-
- if (state.currentItem != null) {
- state.currentItem.setURL(link);
- } else {
- state.feed.setWebsiteURL(link);
- }
- }
- }
-
- private void handleItemOrEntryREndTag(ParserState state) {
- if (state.feed.getLastItem() == null || state.feed.getLastItem().getTimestamp() < state.currentItem.getTimestamp()) {
- // Only set this item as "last item" if we do not have an item yet or this item is newer.
- state.feed.setLastItem(state.currentItem);
- }
-
- state.currentItem = null;
- }
-
- private void handlePubDateStartTag(XmlPullParser parser, ParserState state) throws IOException, XmlPullParserException {
- if (state.currentItem == null) {
- return;
- }
-
- String pubDate = getTextUntilEndTag(parser, TAG_PUBDATE);
- if (TextUtils.isEmpty(pubDate)) {
- return;
- }
-
- // RFC-822
- SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
-
- updateCurrentItemTimestamp(state, pubDate, format);
- }
-
- private void handleUpdatedStartTag(XmlPullParser parser, ParserState state) throws IOException, XmlPullParserException {
- if (state.inSource) {
- // We do not care about stuff in <source> tags.
- return;
- }
-
- if (state.currentItem == null) {
- // We are only interested in <updated> values of feed items.
- return;
- }
-
- String updated = getTextUntilEndTag(parser, TAG_UPDATED);
- if (TextUtils.isEmpty(updated)) {
- return;
- }
-
- SimpleDateFormat[] formats = new SimpleDateFormat[] {
- new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US),
- new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US)
- };
-
- // Fix timezones SimpleDateFormat can't parse:
- // 2016-01-26T18:56:54Z -> 2016-01-26T18:56:54+0000 (Timezone: Z -> +0000)
- updated = updated.replaceFirst("Z$", "+0000");
- // 2016-01-26T18:56:54+01:00 -> 2016-01-26T18:56:54+0100 (Timezone: +01:00 -> +0100)
- updated = updated.replaceFirst("([0-9]{2})([\\+\\-])([0-9]{2}):([0-9]{2})$", "$1$2$3$4");
-
- updateCurrentItemTimestamp(state, updated, formats);
- }
-
- private void handleDateStartTag(XmlPullParser parser, ParserState state) throws IOException, XmlPullParserException {
- if (state.currentItem == null) {
- // We are only interested in <updated> values of feed items.
- return;
- }
-
- String text = getTextUntilEndTag(parser, TAG_DATE);
- if (TextUtils.isEmpty(text)) {
- return;
- }
-
- // Fix timezones SimpleDateFormat can't parse:
- // 2016-01-26T18:56:54+00:00 -> 2016-01-26T18:56:54+0000
- text = text.replaceFirst("([0-9]{2})([\\+\\-])([0-9]{2}):([0-9]{2})$", "$1$2$3$4");
-
- SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
-
- updateCurrentItemTimestamp(state, text, format);
- }
-
- private void updateCurrentItemTimestamp(ParserState state, String text, SimpleDateFormat... formats) {
- for (SimpleDateFormat format : formats) {
- try {
- Date date = format.parse(text);
- state.currentItem.setTimestamp(date.getTime());
- return;
- } catch (ParseException e) {
- Log.w(LOGTAG, "Could not parse 'updated': " + text);
- }
- }
- }
-
- private Map<String, String> fetchAttributes(XmlPullParser parser) {
- Map<String, String> attributes = new HashMap<>();
-
- for (int i = 0; i < parser.getAttributeCount(); i++) {
- attributes.put(parser.getAttributeName(i), parser.getAttributeValue(i));
- }
-
- return attributes;
- }
-
- private String getTextUntilEndTag(XmlPullParser parser, String tag) throws IOException, XmlPullParserException {
- StringBuilder builder = new StringBuilder();
-
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- if (parser.getEventType() == XmlPullParser.TEXT) {
- builder.append(parser.getText());
- } else if (parser.getEventType() == XmlPullParser.END_TAG && tag.equals(parser.getName())) {
- break;
- }
- }
-
- return builder.toString().trim();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/feeds/subscriptions/FeedSubscription.java b/mobile/android/base/java/org/mozilla/gecko/feeds/subscriptions/FeedSubscription.java
deleted file mode 100644
index 7ce7f193f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/feeds/subscriptions/FeedSubscription.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.feeds.subscriptions;
-
-import android.database.Cursor;
-import android.text.TextUtils;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.feeds.FeedFetcher;
-import org.mozilla.gecko.feeds.parser.Item;
-
-/**
- * An object describing a subscription and containing some meta data about the last time we fetched
- * the feed.
- */
-public class FeedSubscription {
- private static final String JSON_KEY_FEED_TITLE = "feed_title";
- private static final String JSON_KEY_LAST_ITEM_TITLE = "last_item_title";
- private static final String JSON_KEY_LAST_ITEM_URL = "last_item_url";
- private static final String JSON_KEY_LAST_ITEM_TIMESTAMP = "last_item_timestamp";
- private static final String JSON_KEY_ETAG = "etag";
- private static final String JSON_KEY_LAST_MODIFIED = "last_modified";
-
- private String feedUrl;
- private String feedTitle;
- private String lastItemTitle;
- private String lastItemUrl;
- private long lastItemTimestamp;
- private String etag;
- private String lastModified;
-
- public static FeedSubscription create(String feedUrl, FeedFetcher.FeedResponse response) {
- FeedSubscription subscription = new FeedSubscription();
- subscription.feedUrl = feedUrl;
-
- subscription.update(response);
-
- return subscription;
- }
-
- public static FeedSubscription fromCursor(Cursor cursor) throws JSONException {
- final FeedSubscription subscription = new FeedSubscription();
- subscription.feedUrl = cursor.getString(cursor.getColumnIndex(BrowserContract.UrlAnnotations.URL));
-
- final String value = cursor.getString(cursor.getColumnIndex(BrowserContract.UrlAnnotations.VALUE));
- subscription.fromJSON(new JSONObject(value));
-
- return subscription;
- }
-
- private void fromJSON(JSONObject object) throws JSONException {
- feedTitle = object.getString(JSON_KEY_FEED_TITLE);
- lastItemTitle = object.getString(JSON_KEY_LAST_ITEM_TITLE);
- lastItemUrl = object.getString(JSON_KEY_LAST_ITEM_URL);
- lastItemTimestamp = object.getLong(JSON_KEY_LAST_ITEM_TIMESTAMP);
- etag = object.optString(JSON_KEY_ETAG);
- lastModified = object.optString(JSON_KEY_LAST_MODIFIED);
- }
-
- public void update(FeedFetcher.FeedResponse response) {
- feedTitle = response.feed.getTitle();
- lastItemTitle = response.feed.getLastItem().getTitle();
- lastItemUrl = response.feed.getLastItem().getURL();
- lastItemTimestamp = response.feed.getLastItem().getTimestamp();
- etag = response.etag;
- lastModified = response.lastModified;
- }
-
- /**
- * Guesstimate if this response is a newer representation of the feed.
- */
- public boolean hasBeenUpdated(FeedFetcher.FeedResponse response) {
- final Item responseItem = response.feed.getLastItem();
-
- if (responseItem.getTimestamp() > lastItemTimestamp) {
- // The timestamp is from a newer date so we expect that this item is a new item. But this
- // could also mean that the timestamp of an already existing item has been updated. We
- // accept that and assume that the content will have changed too in this case.
- return true;
- }
-
- if (responseItem.getTimestamp() == lastItemTimestamp && responseItem.getTimestamp() != 0) {
- // We have a timestamp that is not zero and this item has still the timestamp: It's very
- // likely that we are looking at the same item. We assume this is not new content.
- return false;
- }
-
- if (!responseItem.getURL().equals(lastItemUrl)) {
- // The URL changed: It is very likely that this is a new item. At least it has been updated
- // in a way that we just treat it as new content here.
- return true;
- }
-
- return false;
- }
-
- public String getFeedUrl() {
- return feedUrl;
- }
-
- public String getFeedTitle() {
- return feedTitle;
- }
-
- public String getETag() {
- return etag;
- }
-
- public String getLastModified() {
- return lastModified;
- }
-
- public JSONObject toJSON() throws JSONException {
- JSONObject object = new JSONObject();
-
- object.put(JSON_KEY_FEED_TITLE, feedTitle);
- object.put(JSON_KEY_LAST_ITEM_TITLE, lastItemTitle);
- object.put(JSON_KEY_LAST_ITEM_URL, lastItemUrl);
- object.put(JSON_KEY_LAST_ITEM_TIMESTAMP, lastItemTimestamp);
- object.put(JSON_KEY_ETAG, etag);
- object.put(JSON_KEY_LAST_MODIFIED, lastModified);
-
- return object;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/firstrun/DataPanel.java b/mobile/android/base/java/org/mozilla/gecko/firstrun/DataPanel.java
deleted file mode 100644
index d5940d758..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/firstrun/DataPanel.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.firstrun;
-
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.PrefsHelper;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-
-public class DataPanel extends FirstrunPanel {
- private boolean isEnabled = false;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
- final View root = super.onCreateView(inflater, container, savedInstance);
- final ImageView clickableImage = (ImageView) root.findViewById(R.id.firstrun_image);
- clickableImage.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- // Set new state.
- isEnabled = !isEnabled;
- int newResource = isEnabled ? R.drawable.firstrun_data_on : R.drawable.firstrun_data_off;
- ((ImageView) view).setImageResource(newResource);
- if (isEnabled) {
- // Always block images.
- PrefsHelper.setPref("browser.image_blocking", 0);
- } else {
- // Default: always load images.
- PrefsHelper.setPref("browser.image_blocking", 1);
- }
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-datasaving-" + isEnabled);
- }
- });
-
- return root;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunAnimationContainer.java b/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunAnimationContainer.java
deleted file mode 100644
index 93dd0c254..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunAnimationContainer.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.firstrun;
-
-import android.content.Context;
-import android.support.v4.app.FragmentManager;
-import android.util.AttributeSet;
-
-import android.view.View;
-import android.widget.LinearLayout;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.Experiments;
-
-/**
- * A container for the pager and the entire first run experience.
- * This is used for animation purposes.
- */
-public class FirstrunAnimationContainer extends LinearLayout {
- public static final String PREF_FIRSTRUN_ENABLED = "startpane_enabled";
-
- public static interface OnFinishListener {
- public void onFinish();
- }
-
- private FirstrunPager pager;
- private boolean visible;
- private OnFinishListener onFinishListener;
-
- public FirstrunAnimationContainer(Context context) {
- this(context, null);
- }
- public FirstrunAnimationContainer(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public void load(Context appContext, FragmentManager fm) {
- visible = true;
- pager = (FirstrunPager) findViewById(R.id.firstrun_pager);
- pager.load(appContext, fm, new OnFinishListener() {
- @Override
- public void onFinish() {
- hide();
- }
- });
- }
-
- public boolean isVisible() {
- return visible;
- }
-
- public void hide() {
- visible = false;
- if (onFinishListener != null) {
- onFinishListener.onFinish();
- }
- animateHide();
-
- // Stop all versions of firstrun A/B sessions.
- Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING3_B);
- Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING3_C);
- }
-
- private void animateHide() {
- final Animator alphaAnimator = ObjectAnimator.ofFloat(this, "alpha", 0);
- alphaAnimator.setDuration(150);
- alphaAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- FirstrunAnimationContainer.this.setVisibility(View.GONE);
- }
- });
-
- alphaAnimator.start();
- }
-
- public boolean showBrowserHint() {
- final int currentPage = pager.getCurrentItem();
- FirstrunPanel currentPanel = (FirstrunPanel) ((FirstrunPager.ViewPagerAdapter) pager.getAdapter()).getItem(currentPage);
- pager.cleanup();
- return currentPanel.shouldShowBrowserHint();
- }
-
- public void registerOnFinishListener(OnFinishListener listener) {
- this.onFinishListener = listener;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPager.java b/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPager.java
deleted file mode 100644
index c2838ee3e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPager.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.firstrun;
-
-import android.content.Context;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentPagerAdapter;
-import android.support.v4.view.ViewPager;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.home.HomePager.Decor;
-import org.mozilla.gecko.home.TabMenuStrip;
-import org.mozilla.gecko.restrictions.Restrictions;
-
-import java.util.List;
-
-/**
- * ViewPager containing for our first run pages.
- *
- * @see FirstrunPanel for the first run pages that are used in this pager.
- */
-public class FirstrunPager extends ViewPager {
-
- private Context context;
- protected FirstrunPanel.PagerNavigation pagerNavigation;
- private Decor mDecor;
-
- public FirstrunPager(Context context) {
- this(context, null);
- }
-
- public FirstrunPager(Context context, AttributeSet attrs) {
- super(context, attrs);
- this.context = context;
- }
-
- @Override
- public void addView(View child, int index, ViewGroup.LayoutParams params) {
- if (child instanceof Decor) {
- ((ViewPager.LayoutParams) params).isDecor = true;
- mDecor = (Decor) child;
- mDecor.setOnTitleClickListener(new TabMenuStrip.OnTitleClickListener() {
- @Override
- public void onTitleClicked(int index) {
- setCurrentItem(index, true);
- }
- });
- }
-
- super.addView(child, index, params);
- }
-
- public void load(Context appContext, FragmentManager fm, final FirstrunAnimationContainer.OnFinishListener onFinishListener) {
- final List<FirstrunPagerConfig.FirstrunPanelConfig> panels;
-
- if (Restrictions.isRestrictedProfile(context)) {
- panels = FirstrunPagerConfig.getRestricted();
- } else {
- panels = FirstrunPagerConfig.getDefault(appContext);
- }
-
- setAdapter(new ViewPagerAdapter(fm, panels));
- this.pagerNavigation = new FirstrunPanel.PagerNavigation() {
- @Override
- public void next() {
- final int currentPage = FirstrunPager.this.getCurrentItem();
- if (currentPage < FirstrunPager.this.getAdapter().getCount() - 1) {
- FirstrunPager.this.setCurrentItem(currentPage + 1);
- }
- }
-
- @Override
- public void finish() {
- if (onFinishListener != null) {
- onFinishListener.onFinish();
- }
- }
- };
- addOnPageChangeListener(new OnPageChangeListener() {
- @Override
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- mDecor.onPageScrolled(position, positionOffset, positionOffsetPixels);
- }
-
- @Override
- public void onPageSelected(int i) {
- mDecor.onPageSelected(i);
- Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.PANEL, "onboarding." + i);
- }
-
- @Override
- public void onPageScrollStateChanged(int i) {}
- });
-
- animateLoad();
-
- // Record telemetry for first onboarding panel, for baseline.
- Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.PANEL, "onboarding.0");
- }
-
- public void cleanup() {
- setAdapter(null);
- }
-
- private void animateLoad() {
- setTranslationY(500);
- setAlpha(0);
-
- final Animator translateAnimator = ObjectAnimator.ofFloat(this, "translationY", 0);
- translateAnimator.setDuration(400);
-
- final Animator alphaAnimator = ObjectAnimator.ofFloat(this, "alpha", 1);
- alphaAnimator.setStartDelay(200);
- alphaAnimator.setDuration(600);
-
- final AnimatorSet set = new AnimatorSet();
- set.playTogether(alphaAnimator, translateAnimator);
- set.setStartDelay(400);
-
- set.start();
- }
-
- protected class ViewPagerAdapter extends FragmentPagerAdapter {
- private final List<FirstrunPagerConfig.FirstrunPanelConfig> panels;
- private final Fragment[] fragments;
-
- public ViewPagerAdapter(FragmentManager fm, List<FirstrunPagerConfig.FirstrunPanelConfig> panels) {
- super(fm);
- this.panels = panels;
- this.fragments = new Fragment[panels.size()];
- for (FirstrunPagerConfig.FirstrunPanelConfig panel : panels) {
- mDecor.onAddPagerView(context.getString(panel.getTitleRes()));
- }
-
- if (panels.size() > 0) {
- mDecor.onPageSelected(0);
- }
- }
-
- @Override
- public Fragment getItem(int i) {
- Fragment fragment = this.fragments[i];
- if (fragment == null) {
- FirstrunPagerConfig.FirstrunPanelConfig panelConfig = panels.get(i);
- fragment = Fragment.instantiate(context, panelConfig.getClassname(), panelConfig.getArgs());
- ((FirstrunPanel) fragment).setPagerNavigation(pagerNavigation);
- fragments[i] = fragment;
- }
- return fragment;
- }
-
- @Override
- public int getCount() {
- return panels.size();
- }
-
- @Override
- public CharSequence getPageTitle(int i) {
- // Unused now that we use TabMenuStrip.
- return context.getString(panels.get(i).getTitleRes()).toUpperCase();
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPagerConfig.java b/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPagerConfig.java
deleted file mode 100644
index 3f901d07b..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPagerConfig.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.firstrun;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.util.Log;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.Experiments;
-
-import java.util.LinkedList;
-import java.util.List;
-
-public class FirstrunPagerConfig {
- public static final String LOGTAG = "FirstrunPagerConfig";
-
- public static final String KEY_IMAGE = "imageRes";
- public static final String KEY_TEXT = "textRes";
- public static final String KEY_SUBTEXT = "subtextRes";
-
- public static List<FirstrunPanelConfig> getDefault(Context context) {
- final List<FirstrunPanelConfig> panels = new LinkedList<>();
-
- if (Experiments.isInExperimentLocal(context, Experiments.ONBOARDING3_B)) {
- panels.add(SimplePanelConfigs.urlbarPanelConfig);
- panels.add(SimplePanelConfigs.bookmarksPanelConfig);
- panels.add(SimplePanelConfigs.dataPanelConfig);
- panels.add(SimplePanelConfigs.syncPanelConfig);
- panels.add(SimplePanelConfigs.signInPanelConfig);
- Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING3_B);
- GeckoSharedPrefs.forProfile(context).edit().putString(Experiments.PREF_ONBOARDING_VERSION, Experiments.ONBOARDING3_B).apply();
- } else if (Experiments.isInExperimentLocal(context, Experiments.ONBOARDING3_C)) {
- panels.add(SimplePanelConfigs.tabqueuePanelConfig);
- panels.add(SimplePanelConfigs.readerviewPanelConfig);
- panels.add(SimplePanelConfigs.accountPanelConfig);
- Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.ONBOARDING3_C);
- GeckoSharedPrefs.forProfile(context).edit().putString(Experiments.PREF_ONBOARDING_VERSION, Experiments.ONBOARDING3_C).apply();
- } else {
- Log.e(LOGTAG, "Not in an experiment!");
- panels.add(SimplePanelConfigs.signInPanelConfig);
- }
- return panels;
- }
-
- public static List<FirstrunPanelConfig> getRestricted() {
- final List<FirstrunPanelConfig> panels = new LinkedList<>();
- panels.add(new FirstrunPanelConfig(RestrictedWelcomePanel.class.getName(), RestrictedWelcomePanel.TITLE_RES));
- return panels;
- }
-
- public static class FirstrunPanelConfig {
-
- private String classname;
- private int titleRes;
- private Bundle args;
-
- public FirstrunPanelConfig(String resource, int titleRes) {
- this(resource, titleRes, -1, -1, -1, true);
- }
-
- public FirstrunPanelConfig(String classname, int titleRes, int imageRes, int textRes, int subtextRes) {
- this(classname, titleRes, imageRes, textRes, subtextRes, false);
- }
-
- private FirstrunPanelConfig(String classname, int titleRes, int imageRes, int textRes, int subtextRes, boolean isCustom) {
- this.classname = classname;
- this.titleRes = titleRes;
-
- if (!isCustom) {
- this.args = new Bundle();
- this.args.putInt(KEY_IMAGE, imageRes);
- this.args.putInt(KEY_TEXT, textRes);
- this.args.putInt(KEY_SUBTEXT, subtextRes);
- }
- }
-
- public String getClassname() {
- return this.classname;
- }
-
- public int getTitleRes() {
- return this.titleRes;
- }
-
- public Bundle getArgs() {
- return args;
- }
- }
-
- private static class SimplePanelConfigs {
- public static final FirstrunPanelConfig urlbarPanelConfig = new FirstrunPanelConfig(FirstrunPanel.class.getName(), R.string.firstrun_panel_title_welcome, R.drawable.firstrun_urlbar, R.string.firstrun_urlbar_message, R.string.firstrun_urlbar_subtext);
- public static final FirstrunPanelConfig bookmarksPanelConfig = new FirstrunPanelConfig(FirstrunPanel.class.getName(), R.string.firstrun_bookmarks_title, R.drawable.firstrun_bookmarks, R.string.firstrun_bookmarks_message, R.string.firstrun_bookmarks_subtext);
- public static final FirstrunPanelConfig dataPanelConfig = new FirstrunPanelConfig(DataPanel.class.getName(), R.string.firstrun_data_title, R.drawable.firstrun_data_off, R.string.firstrun_data_message, R.string.firstrun_data_subtext);
- public static final FirstrunPanelConfig syncPanelConfig = new FirstrunPanelConfig(FirstrunPanel.class.getName(), R.string.firstrun_sync_title, R.drawable.firstrun_sync, R.string.firstrun_sync_message, R.string.firstrun_sync_subtext);
- public static final FirstrunPanelConfig signInPanelConfig = new FirstrunPanelConfig(SyncPanel.class.getName(), R.string.pref_sync, R.drawable.firstrun_signin, R.string.firstrun_signin_message, R.string.firstrun_welcome_button_browser);
-
- public static final FirstrunPanelConfig tabqueuePanelConfig = new FirstrunPanelConfig(TabQueuePanel.class.getName(), R.string.firstrun_tabqueue_title, R.drawable.firstrun_tabqueue_off, R.string.firstrun_tabqueue_message_off, R.string.firstrun_tabqueue_subtext_off);
- public static final FirstrunPanelConfig readerviewPanelConfig = new FirstrunPanelConfig(FirstrunPanel.class.getName(), R.string.firstrun_readerview_title, R.drawable.firstrun_readerview, R.string.firstrun_readerview_message, R.string.firstrun_readerview_subtext);
- public static final FirstrunPanelConfig accountPanelConfig = new FirstrunPanelConfig(SyncPanel.class.getName(), R.string.firstrun_account_title, R.drawable.firstrun_account, R.string.firstrun_account_message, R.string.firstrun_button_notnow);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPanel.java b/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPanel.java
deleted file mode 100644
index 4b27dbc73..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/firstrun/FirstrunPanel.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.firstrun;
-
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-
-/**
- * Base class for our first run pages. We call these FirstrunPanel for consistency
- * with HomePager/HomePanel.
- *
- * @see FirstrunPager for the containing pager.
- */
-public class FirstrunPanel extends Fragment {
-
- public static final int TITLE_RES = -1;
- protected boolean showBrowserHint = true;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
- final ViewGroup root = (ViewGroup) inflater.inflate(R.layout.firstrun_basepanel_checkable_fragment, container, false);
- Bundle args = getArguments();
- if (args != null) {
- final int imageRes = args.getInt(FirstrunPagerConfig.KEY_IMAGE);
- final int textRes = args.getInt(FirstrunPagerConfig.KEY_TEXT);
- final int subtextRes = args.getInt(FirstrunPagerConfig.KEY_SUBTEXT);
-
- ((ImageView) root.findViewById(R.id.firstrun_image)).setImageResource(imageRes);
- ((TextView) root.findViewById(R.id.firstrun_text)).setText(textRes);
- ((TextView) root.findViewById(R.id.firstrun_subtext)).setText(subtextRes);
- }
-
- root.findViewById(R.id.firstrun_link).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-next");
- pagerNavigation.next();
- }
- });
-
- return root;
- }
-
- public interface PagerNavigation {
- void next();
- void finish();
- }
- protected PagerNavigation pagerNavigation;
-
- public void setPagerNavigation(PagerNavigation listener) {
- this.pagerNavigation = listener;
- }
-
- protected void next() {
- if (pagerNavigation != null) {
- pagerNavigation.next();
- }
- }
-
- protected void close() {
- if (pagerNavigation != null) {
- pagerNavigation.finish();
- }
- }
-
- protected boolean shouldShowBrowserHint() {
- return showBrowserHint;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/firstrun/RestrictedWelcomePanel.java b/mobile/android/base/java/org/mozilla/gecko/firstrun/RestrictedWelcomePanel.java
deleted file mode 100644
index efc91d20f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/firstrun/RestrictedWelcomePanel.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.firstrun;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.home.HomePager;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.EnumSet;
-
-public class RestrictedWelcomePanel extends FirstrunPanel {
- public static final int TITLE_RES = R.string.firstrun_panel_title_welcome;
-
- private static final String LEARN_MORE_URL = "https://support.mozilla.org/kb/controlledaccess";
-
- private HomePager.OnUrlOpenListener onUrlOpenListener;
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
-
- try {
- onUrlOpenListener = (HomePager.OnUrlOpenListener) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException(activity.toString() + " must implement HomePager.OnUrlOpenListener");
- }
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
- final ViewGroup root = (ViewGroup) inflater.inflate(R.layout.restricted_firstrun_welcome_fragment, container, false);
-
- root.findViewById(R.id.welcome_browse).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- close();
- }
- });
-
- root.findViewById(R.id.learn_more_link).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- onUrlOpenListener.onUrlOpen(LEARN_MORE_URL, EnumSet.of(HomePager.OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
-
- close();
- }
- });
-
- return root;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/firstrun/SyncPanel.java b/mobile/android/base/java/org/mozilla/gecko/firstrun/SyncPanel.java
deleted file mode 100644
index 2f489c84e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/firstrun/SyncPanel.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.firstrun;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.activities.FxAccountWebFlowActivity;
-
-public class SyncPanel extends FirstrunPanel {
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
- final ViewGroup root = (ViewGroup) inflater.inflate(R.layout.firstrun_sync_fragment, container, false);
- final Bundle args = getArguments();
- if (args != null) {
- final int imageRes = args.getInt(FirstrunPagerConfig.KEY_IMAGE);
- final int textRes = args.getInt(FirstrunPagerConfig.KEY_TEXT);
- final int subtextRes = args.getInt(FirstrunPagerConfig.KEY_SUBTEXT);
-
- ((ImageView) root.findViewById(R.id.firstrun_image)).setImageResource(imageRes);
- ((TextView) root.findViewById(R.id.firstrun_text)).setText(textRes);
- ((TextView) root.findViewById(R.id.welcome_browse)).setText(subtextRes);
- }
-
- root.findViewById(R.id.welcome_account).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-sync");
- showBrowserHint = false;
-
- final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
- intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_FIRSTRUN);
- intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
- startActivity(intent);
-
- close();
- }
- });
-
- root.findViewById(R.id.welcome_browse).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-browser");
- close();
- }
- });
-
- return root;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/firstrun/TabQueuePanel.java b/mobile/android/base/java/org/mozilla/gecko/firstrun/TabQueuePanel.java
deleted file mode 100644
index 3c2ed8312..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/firstrun/TabQueuePanel.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.firstrun;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.graphics.Typeface;
-import android.os.Bundle;
-import android.support.v4.content.ContextCompat;
-import android.support.v7.widget.SwitchCompat;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CompoundButton;
-import android.widget.ImageView;
-import android.widget.TextView;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.tabqueue.TabQueueHelper;
-import org.mozilla.gecko.tabqueue.TabQueuePrompt;
-
-public class TabQueuePanel extends FirstrunPanel {
- private static final int REQUEST_CODE_TAB_QUEUE = 1;
- private SwitchCompat toggleSwitch;
- private ImageView imageView;
- private TextView messageTextView;
- private TextView subtextTextView;
- private Context context;
-
- @Override
- public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle savedInstance) {
- context = getContext();
- final View root = super.onCreateView(inflater, container, savedInstance);
-
- imageView = (ImageView) root.findViewById(R.id.firstrun_image);
- messageTextView = (TextView) root.findViewById(R.id.firstrun_text);
- subtextTextView = (TextView) root.findViewById(R.id.firstrun_subtext);
-
- toggleSwitch = (SwitchCompat) root.findViewById(R.id.firstrun_switch);
- toggleSwitch.setVisibility(View.VISIBLE);
- toggleSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.DIALOG, "firstrun_tabqueue-permissions");
- if (b && !TabQueueHelper.canDrawOverlays(context)) {
- Intent promptIntent = new Intent(context, TabQueuePrompt.class);
- startActivityForResult(promptIntent, REQUEST_CODE_TAB_QUEUE);
- return;
- }
-
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-tabqueue-" + b);
-
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
- final SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(GeckoPreferences.PREFS_TAB_QUEUE, b).apply();
-
- // Set image, text, and typeface changes.
- imageView.setImageResource(b ? R.drawable.firstrun_tabqueue_on : R.drawable.firstrun_tabqueue_off);
- messageTextView.setText(b ? R.string.firstrun_tabqueue_message_on : R.string.firstrun_tabqueue_message_off);
- messageTextView.setTypeface(b ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT);
- subtextTextView.setText(b ? R.string.firstrun_tabqueue_subtext_on : R.string.firstrun_tabqueue_subtext_off);
- subtextTextView.setTypeface(b ? Typeface.defaultFromStyle(Typeface.ITALIC) : Typeface.DEFAULT);
- subtextTextView.setTextColor(b ? ContextCompat.getColor(context, R.color.fennec_ui_orange) : ContextCompat.getColor(context, R.color.placeholder_grey));
- }
- });
-
- return root;
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- switch (requestCode) {
- case REQUEST_CODE_TAB_QUEUE:
- final boolean accepted = TabQueueHelper.processTabQueuePromptResponse(resultCode, context);
- if (accepted) {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.DIALOG, "firstrun_tabqueue-permissions-yes");
- toggleSwitch.setChecked(true);
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "firstrun-tabqueue-true");
- }
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.DIALOG, "firstrun_tabqueue-permissions-" + (accepted ? "accepted" : "rejected"));
- break;
- }
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/gcm/GcmInstanceIDListenerService.java b/mobile/android/base/java/org/mozilla/gecko/gcm/GcmInstanceIDListenerService.java
deleted file mode 100644
index 0616cd229..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/gcm/GcmInstanceIDListenerService.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.gcm;
-
-import android.util.Log;
-
-import com.google.android.gms.iid.InstanceIDListenerService;
-
-import org.mozilla.gecko.push.PushService;
-import org.mozilla.gecko.util.ThreadUtils;
-
-/**
- * This service is notified by the on-device Google Play Services library if an
- * in-use token needs to be updated. We simply pass through to AndroidPushService.
- */
-public class GcmInstanceIDListenerService extends InstanceIDListenerService {
- /**
- * Called if InstanceID token is updated. This may occur if the security of
- * the previous token had been compromised. This call is initiated by the
- * InstanceID provider.
- */
- @Override
- public void onTokenRefresh() {
- Log.d("GeckoPushGCM", "Token refresh request received. Processing on background thread.");
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- PushService.getInstance(GcmInstanceIDListenerService.this).onRefresh();
- }
- });
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/gcm/GcmMessageListenerService.java b/mobile/android/base/java/org/mozilla/gecko/gcm/GcmMessageListenerService.java
deleted file mode 100644
index 7962d7dc3..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/gcm/GcmMessageListenerService.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.gcm;
-
-import android.os.Bundle;
-import android.util.Log;
-
-import com.google.android.gms.gcm.GcmListenerService;
-
-import org.mozilla.gecko.push.PushService;
-import org.mozilla.gecko.util.ThreadUtils;
-
-/**
- * This service actually handles messages directed from the on-device Google
- * Play Services package. We simply route them to the AndroidPushService.
- */
-public class GcmMessageListenerService extends GcmListenerService {
- /**
- * Called when message is received.
- *
- * @param from SenderID of the sender.
- * @param bundle Data bundle containing message data as key/value pairs.
- */
- @Override
- public void onMessageReceived(final String from, final Bundle bundle) {
- Log.d("GeckoPushGCM", "Message received. Processing on background thread.");
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- PushService.getInstance(GcmMessageListenerService.this).onMessageReceived(
- GcmMessageListenerService.this, bundle);
- }
- });
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/gcm/GcmTokenClient.java b/mobile/android/base/java/org/mozilla/gecko/gcm/GcmTokenClient.java
deleted file mode 100644
index 024905eb0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/gcm/GcmTokenClient.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.gcm;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.support.annotation.NonNull;
-import android.util.Log;
-
-import com.google.android.gms.common.ConnectionResult;
-import com.google.android.gms.common.GoogleApiAvailability;
-import com.google.android.gms.gcm.GoogleCloudMessaging;
-import com.google.android.gms.iid.InstanceID;
-
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.push.Fetched;
-
-import java.io.IOException;
-
-/**
- * Fetch and cache GCM tokens.
- * <p/>
- * GCM tokens are stable and long lived. Google Play Services will periodically request that
- * they are rotated, however: see
- * <a href="https://developers.google.com/instance-id/guides/android-implementation">https://developers.google.com/instance-id/guides/android-implementation</a>.
- * <p/>
- * The GCM token is cached in the App-wide shared preferences. There's no particular harm in
- * requesting new tokens, so if the user clears the App data, that's fine -- we'll get a fresh
- * token and Push will react accordingly.
- */
-public class GcmTokenClient {
- private static final String LOG_TAG = "GeckoPushGCM";
-
- private static final String KEY_GCM_TOKEN = "gcm_token";
- private static final String KEY_GCM_TOKEN_TIMESTAMP = "gcm_token_timestamp";
-
- private final Context context;
-
- public GcmTokenClient(Context context) {
- this.context = context;
- }
-
- /**
- * Check the device to make sure it has the Google Play Services APK.
- * @param context Android context.
- */
- protected void ensurePlayServices(Context context) throws NeedsGooglePlayServicesException {
- final GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
- int resultCode = apiAvailability.isGooglePlayServicesAvailable(context);
- if (resultCode != ConnectionResult.SUCCESS) {
- Log.w(LOG_TAG, "This device does not support GCM! isGooglePlayServicesAvailable returned: " + resultCode);
- Log.w(LOG_TAG, "isGooglePlayServicesAvailable message: " + apiAvailability.getErrorString(resultCode));
- throw new NeedsGooglePlayServicesException(resultCode);
- }
- }
-
- /**
- * Get a GCM token (possibly cached).
- *
- * @param senderID to request token for.
- * @param debug whether to log debug details.
- * @return token and timestamp.
- * @throws NeedsGooglePlayServicesException if user action is needed to use Google Play Services.
- * @throws IOException if the token fetch failed.
- */
- public @NonNull Fetched getToken(@NonNull String senderID, boolean debug) throws NeedsGooglePlayServicesException, IOException {
- ensurePlayServices(this.context);
-
- final SharedPreferences sharedPrefs = GeckoSharedPrefs.forApp(context);
- String token = sharedPrefs.getString(KEY_GCM_TOKEN, null);
- long timestamp = sharedPrefs.getLong(KEY_GCM_TOKEN_TIMESTAMP, 0L);
- if (token != null && timestamp > 0L) {
- if (debug) {
- Log.i(LOG_TAG, "Cached GCM token exists: " + token);
- } else {
- Log.i(LOG_TAG, "Cached GCM token exists.");
- }
- return new Fetched(token, timestamp);
- }
-
- Log.i(LOG_TAG, "Cached GCM token does not exist; requesting new token with sender ID: " + senderID);
-
- final InstanceID instanceID = InstanceID.getInstance(context);
- token = instanceID.getToken(senderID, GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
- timestamp = System.currentTimeMillis();
-
- if (debug) {
- Log.i(LOG_TAG, "Got fresh GCM token; caching: " + token);
- } else {
- Log.i(LOG_TAG, "Got fresh GCM token; caching.");
- }
- sharedPrefs
- .edit()
- .putString(KEY_GCM_TOKEN, token)
- .putLong(KEY_GCM_TOKEN_TIMESTAMP, timestamp)
- .apply();
-
- return new Fetched(token, timestamp);
- }
-
- /**
- * Remove any cached GCM token.
- */
- public void invalidateToken() {
- final SharedPreferences sharedPrefs = GeckoSharedPrefs.forApp(context);
- sharedPrefs
- .edit()
- .remove(KEY_GCM_TOKEN)
- .remove(KEY_GCM_TOKEN_TIMESTAMP)
- .apply();
- }
-
- public class NeedsGooglePlayServicesException extends Exception {
- private static final long serialVersionUID = 4132853166L;
-
- private final int resultCode;
-
- NeedsGooglePlayServicesException(int resultCode) {
- super();
- this.resultCode = resultCode;
- }
-
- public void showErrorNotification() {
- final GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
- apiAvailability.showErrorNotification(context, resultCode);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/health/HealthRecorder.java b/mobile/android/base/java/org/mozilla/gecko/health/HealthRecorder.java
deleted file mode 100644
index a9f5b72f3..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/health/HealthRecorder.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.health;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-
-import org.json.JSONObject;
-
-/**
- * HealthRecorder is an interface into the Firefox Health Report storage system.
- */
-public interface HealthRecorder {
- /**
- * Returns whether the Health Recorder is actively recording events.
- */
- public boolean isEnabled();
-
- public void setCurrentSession(SessionInformation session);
- public void checkForOrphanSessions();
-
- public void recordGeckoStartupTime(long duration);
- public void recordJavaStartupTime(long duration);
- public void recordSearch(final String engineID, final String location);
- public void recordSessionEnd(String reason, SharedPreferences.Editor editor);
- public void recordSessionEnd(String reason, SharedPreferences.Editor editor, final int environment);
-
- public void onAppLocaleChanged(String to);
- public void onAddonChanged(String id, JSONObject json);
- public void onAddonUninstalling(String id);
- public void onEnvironmentChanged();
- public void onEnvironmentChanged(final boolean startNewSession, final String sessionEndReason);
-
- public void close(final Context context);
-
- public void processDelayed();
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/health/SessionInformation.java b/mobile/android/base/java/org/mozilla/gecko/health/SessionInformation.java
deleted file mode 100644
index ad65918e1..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/health/SessionInformation.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.health;
-
-import android.content.SharedPreferences;
-import android.util.Log;
-
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-public class SessionInformation {
- private static final String LOG_TAG = "GeckoSessInfo";
-
- public static final String PREFS_SESSION_START = "sessionStart";
-
- public final long wallStartTime; // System wall clock.
- public final long realStartTime; // Realtime clock.
-
- private final boolean wasOOM;
- private final boolean wasStopped;
-
- private volatile long timedGeckoStartup = -1;
- private volatile long timedJavaStartup = -1;
-
- // Current sessions don't (right now) care about wasOOM/wasStopped.
- // Eventually we might want to lift that logic out of GeckoApp.
- public SessionInformation(long wallTime, long realTime) {
- this(wallTime, realTime, false, false);
- }
-
- // Previous sessions do...
- public SessionInformation(long wallTime, long realTime, boolean wasOOM, boolean wasStopped) {
- this.wallStartTime = wallTime;
- this.realStartTime = realTime;
- this.wasOOM = wasOOM;
- this.wasStopped = wasStopped;
- }
-
- /**
- * Initialize a new SessionInformation instance from the supplied prefs object.
- *
- * This includes retrieving OOM/crash data, as well as timings.
- *
- * If no wallStartTime was found, that implies that the previous
- * session was correctly recorded, and an object with a zero
- * wallStartTime is returned.
- */
- public static SessionInformation fromSharedPrefs(SharedPreferences prefs) {
- boolean wasOOM = prefs.getBoolean(GeckoAppShell.PREFS_OOM_EXCEPTION, false);
- boolean wasStopped = prefs.getBoolean(GeckoApp.PREFS_WAS_STOPPED, true);
- long wallStartTime = prefs.getLong(PREFS_SESSION_START, 0L);
- long realStartTime = 0L;
- Log.d(LOG_TAG, "Building SessionInformation from prefs: " +
- wallStartTime + ", " + realStartTime + ", " +
- wasStopped + ", " + wasOOM);
- return new SessionInformation(wallStartTime, realStartTime, wasOOM, wasStopped);
- }
-
- /**
- * Initialize a new SessionInformation instance to 'split' the current
- * session.
- */
- public static SessionInformation forRuntimeTransition() {
- final boolean wasOOM = false;
- final boolean wasStopped = true;
- final long wallStartTime = System.currentTimeMillis();
- final long realStartTime = android.os.SystemClock.elapsedRealtime();
- Log.v(LOG_TAG, "Recording runtime session transition: " +
- wallStartTime + ", " + realStartTime);
- return new SessionInformation(wallStartTime, realStartTime, wasOOM, wasStopped);
- }
-
- public boolean wasKilled() {
- return wasOOM || !wasStopped;
- }
-
- /**
- * Record the beginning of this session to SharedPreferences by
- * recording our start time. If a session was already recorded, it is
- * overwritten (there can only be one running session at a time). Does
- * not commit the editor.
- */
- public void recordBegin(SharedPreferences.Editor editor) {
- Log.d(LOG_TAG, "Recording start of session: " + this.wallStartTime);
- editor.putLong(PREFS_SESSION_START, this.wallStartTime);
- }
-
- /**
- * Record the completion of this session to SharedPreferences by
- * deleting our start time. Does not commit the editor.
- */
- public void recordCompletion(SharedPreferences.Editor editor) {
- Log.d(LOG_TAG, "Recording session done: " + this.wallStartTime);
- editor.remove(PREFS_SESSION_START);
- }
-
- /**
- * Return the JSON that we'll put in the DB for this session.
- */
- public JSONObject getCompletionJSON(String reason, long realEndTime) throws JSONException {
- long durationSecs = (realEndTime - this.realStartTime) / 1000;
- JSONObject out = new JSONObject();
- out.put("r", reason);
- out.put("d", durationSecs);
- if (this.timedGeckoStartup > 0) {
- out.put("sg", this.timedGeckoStartup);
- }
- if (this.timedJavaStartup > 0) {
- out.put("sj", this.timedJavaStartup);
- }
- return out;
- }
-
- public JSONObject getCrashedJSON() throws JSONException {
- JSONObject out = new JSONObject();
- // We use ints here instead of booleans, because we're packing
- // stuff into JSON, and saving bytes in the DB is a worthwhile
- // goal.
- out.put("oom", this.wasOOM ? 1 : 0);
- out.put("stopped", this.wasStopped ? 1 : 0);
- out.put("r", "A");
- return out;
- }
-
- public void setTimedGeckoStartup(final long duration) {
- timedGeckoStartup = duration;
- }
-
- public void setTimedJavaStartup(final long duration) {
- timedJavaStartup = duration;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/health/StubbedHealthRecorder.java b/mobile/android/base/java/org/mozilla/gecko/health/StubbedHealthRecorder.java
deleted file mode 100644
index 65a972985..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/health/StubbedHealthRecorder.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.health;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-
-import org.json.JSONObject;
-
-/**
- * StubbedHealthRecorder is an implementation of HealthRecorder that does (you guessed it!)
- * nothing.
- */
-public class StubbedHealthRecorder implements HealthRecorder {
- @Override
- public boolean isEnabled() { return false; }
-
- @Override
- public void setCurrentSession(SessionInformation session) { }
- @Override
- public void checkForOrphanSessions() { }
-
- @Override
- public void recordGeckoStartupTime(long duration) { }
- @Override
- public void recordJavaStartupTime(long duration) { }
- @Override
- public void recordSearch(final String engineID, final String location) { }
- @Override
- public void recordSessionEnd(String reason, SharedPreferences.Editor editor) { }
- @Override
- public void recordSessionEnd(String reason, SharedPreferences.Editor editor, final int environment) { }
-
- @Override
- public void onAppLocaleChanged(String to) { }
- @Override
- public void onAddonChanged(String id, JSONObject json) { }
- @Override
- public void onAddonUninstalling(String id) { }
- @Override
- public void onEnvironmentChanged() { }
- @Override
- public void onEnvironmentChanged(final boolean startNewSession, final String sessionEndReason) { }
-
- @Override
- public void close(final Context context) { }
-
- @Override
- public void processDelayed() { }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/BookmarkFolderView.java b/mobile/android/base/java/org/mozilla/gecko/home/BookmarkFolderView.java
deleted file mode 100644
index 566422faf..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarkFolderView.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.UIAsyncTask;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import java.lang.ref.WeakReference;
-import java.util.Collections;
-import java.util.Set;
-import java.util.TreeSet;
-
-public class BookmarkFolderView extends LinearLayout {
- private static final Set<Integer> FOLDERS_WITH_COUNT;
-
- static {
- final Set<Integer> folders = new TreeSet<>();
- folders.add(BrowserContract.Bookmarks.FAKE_READINGLIST_SMARTFOLDER_ID);
-
- FOLDERS_WITH_COUNT = Collections.unmodifiableSet(folders);
- }
-
- public enum FolderState {
- /**
- * A standard folder, i.e. a folder in a list of bookmarks and folders.
- */
- FOLDER(R.drawable.folder_closed),
-
- /**
- * The parent folder: this indicates that you are able to return to the previous
- * folder ("Back to {name}").
- */
- PARENT(R.drawable.bookmark_folder_arrow_up),
-
- /**
- * The reading list smartfolder: this displays a reading list icon instead of the
- * normal folder icon.
- */
- READING_LIST(R.drawable.reading_list_folder);
-
- public final int image;
-
- FolderState(final int image) { this.image = image; }
- }
-
- private final TextView mTitle;
- private final TextView mSubtitle;
-
- private final ImageView mIcon;
-
- public BookmarkFolderView(Context context) {
- this(context, null);
- }
-
- public BookmarkFolderView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- LayoutInflater.from(context).inflate(R.layout.two_line_folder_row, this);
-
- mTitle = (TextView) findViewById(R.id.title);
- mSubtitle = (TextView) findViewById(R.id.subtitle);
- mIcon = (ImageView) findViewById(R.id.icon);
- }
-
- public void update(String title, int folderID) {
- setTitle(title);
- setID(folderID);
- }
-
- private void setTitle(String title) {
- mTitle.setText(title);
- }
-
- private static class ItemCountUpdateTask extends UIAsyncTask.WithoutParams<Integer> {
- private final WeakReference<TextView> mTextViewReference;
- private final int mFolderID;
-
- public ItemCountUpdateTask(final WeakReference<TextView> textViewReference,
- final int folderID) {
- super(ThreadUtils.getBackgroundHandler());
-
- mTextViewReference = textViewReference;
- mFolderID = folderID;
- }
-
- @Override
- protected Integer doInBackground() {
- final TextView textView = mTextViewReference.get();
-
- if (textView == null) {
- return null;
- }
-
- final BrowserDB db = BrowserDB.from(textView.getContext());
- return db.getBookmarkCountForFolder(textView.getContext().getContentResolver(), mFolderID);
- }
-
- @Override
- protected void onPostExecute(Integer count) {
- final TextView textView = mTextViewReference.get();
-
- if (textView == null) {
- return;
- }
-
- final String text;
- if (count == 1) {
- text = textView.getContext().getResources().getString(R.string.bookmark_folder_one_item);
- } else {
- text = textView.getContext().getResources().getString(R.string.bookmark_folder_items, count);
- }
-
- textView.setText(text);
- textView.setVisibility(View.VISIBLE);
- }
- }
-
- private void setID(final int folderID) {
- if (FOLDERS_WITH_COUNT.contains(folderID)) {
- final WeakReference<TextView> subTitleReference = new WeakReference<TextView>(mSubtitle);
-
- new ItemCountUpdateTask(subTitleReference, folderID).execute();
- } else {
- mSubtitle.setVisibility(View.GONE);
- }
- }
-
- public void setState(@NonNull FolderState state) {
- mIcon.setImageResource(state.image);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/BookmarkScreenshotRow.java b/mobile/android/base/java/org/mozilla/gecko/home/BookmarkScreenshotRow.java
deleted file mode 100644
index a1efff049..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarkScreenshotRow.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.util.AttributeSet;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract.UrlAnnotations;
-
-import java.text.DateFormat;
-import java.text.FieldPosition;
-import java.util.Date;
-
-/**
- * An entry of the screenshot list in the bookmarks panel.
- */
-class BookmarkScreenshotRow extends LinearLayout {
- private TextView titleView;
- private TextView dateView;
-
- // This DateFormat uses the current locale at instantiation time, which won't get updated if the locale is changed.
- // Since it's just a date, it's probably not worth the code complexity to fix that.
- private static final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.LONG);
- private static final DateFormat timeFormat = DateFormat.getTimeInstance(DateFormat.SHORT);
-
- // This parameter to DateFormat.format has no impact on the result but rather gets mutated by the method to
- // identify where a certain field starts and ends (by index). This is useful if you want to later modify the String;
- // I'm not sure why this argument isn't optional.
- private static final FieldPosition dummyFieldPosition = new FieldPosition(-1);
-
- public BookmarkScreenshotRow(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public void onFinishInflate() {
- super.onFinishInflate();
- titleView = (TextView) findViewById(R.id.title);
- dateView = (TextView) findViewById(R.id.date);
- }
-
- public void updateFromCursor(final Cursor c) {
- titleView.setText(getTitleFromCursor(c));
- dateView.setText(getDateFromCursor(c));
- }
-
- private static String getTitleFromCursor(final Cursor c) {
- final int index = c.getColumnIndexOrThrow(UrlAnnotations.URL);
- return c.getString(index);
- }
-
- private static String getDateFromCursor(final Cursor c) {
- final long timestamp = c.getLong(c.getColumnIndexOrThrow(UrlAnnotations.DATE_CREATED));
- final Date date = new Date(timestamp);
- final StringBuffer sb = new StringBuffer();
- dateFormat.format(date, sb, dummyFieldPosition)
- .append(" - ");
- timeFormat.format(date, sb, dummyFieldPosition);
- return sb.toString();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListAdapter.java
deleted file mode 100644
index b31116693..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListAdapter.java
+++ /dev/null
@@ -1,352 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract.Bookmarks;
-import org.mozilla.gecko.home.BookmarkFolderView.FolderState;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.View;
-
-/**
- * Adapter to back the BookmarksListView with a list of bookmarks.
- */
-class BookmarksListAdapter extends MultiTypeCursorAdapter {
- private static final int VIEW_TYPE_BOOKMARK_ITEM = 0;
- private static final int VIEW_TYPE_FOLDER = 1;
- private static final int VIEW_TYPE_SCREENSHOT = 2;
-
- private static final int[] VIEW_TYPES = new int[] { VIEW_TYPE_BOOKMARK_ITEM, VIEW_TYPE_FOLDER, VIEW_TYPE_SCREENSHOT };
- private static final int[] LAYOUT_TYPES =
- new int[] { R.layout.bookmark_item_row, R.layout.bookmark_folder_row, R.layout.bookmark_screenshot_row };
-
- public enum RefreshType implements Parcelable {
- PARENT,
- CHILD;
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(ordinal());
- }
-
- public static final Creator<RefreshType> CREATOR = new Creator<RefreshType>() {
- @Override
- public RefreshType createFromParcel(final Parcel source) {
- return RefreshType.values()[source.readInt()];
- }
-
- @Override
- public RefreshType[] newArray(final int size) {
- return new RefreshType[size];
- }
- };
- }
-
- public static class FolderInfo implements Parcelable {
- public final int id;
- public final String title;
-
- public FolderInfo(int id) {
- this(id, "");
- }
-
- public FolderInfo(Parcel in) {
- this(in.readInt(), in.readString());
- }
-
- public FolderInfo(int id, String title) {
- this.id = id;
- this.title = title;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(id);
- dest.writeString(title);
- }
-
- public static final Creator<FolderInfo> CREATOR = new Creator<FolderInfo>() {
- @Override
- public FolderInfo createFromParcel(Parcel in) {
- return new FolderInfo(in);
- }
-
- @Override
- public FolderInfo[] newArray(int size) {
- return new FolderInfo[size];
- }
- };
- }
-
- // A listener that knows how to refresh the list for a given folder id.
- // This is usually implemented by the enclosing fragment/activity.
- public static interface OnRefreshFolderListener {
- // The folder id to refresh the list with.
- public void onRefreshFolder(FolderInfo folderInfo, RefreshType refreshType);
- }
-
- /**
- * The type of data a bookmarks folder can display. This can be used to
- * distinguish bookmark folders from "smart folders" that contain non-bookmark
- * entries but still appear in the Bookmarks panel.
- */
- public enum FolderType {
- BOOKMARKS,
- SCREENSHOTS,
- }
-
- // mParentStack holds folder info instances (id + title) that allow
- // us to navigate back up the folder hierarchy.
- private LinkedList<FolderInfo> mParentStack;
-
- // Refresh folder listener.
- private OnRefreshFolderListener mListener;
-
- private FolderType openFolderType = FolderType.BOOKMARKS;
-
- public BookmarksListAdapter(Context context, Cursor cursor, List<FolderInfo> parentStack) {
- // Initializing with a null cursor.
- super(context, cursor, VIEW_TYPES, LAYOUT_TYPES);
-
- if (parentStack == null) {
- mParentStack = new LinkedList<FolderInfo>();
- } else {
- mParentStack = new LinkedList<FolderInfo>(parentStack);
- }
- }
-
- public void restoreData(List<FolderInfo> parentStack) {
- mParentStack = new LinkedList<FolderInfo>(parentStack);
- notifyDataSetChanged();
- }
-
- public List<FolderInfo> getParentStack() {
- return Collections.unmodifiableList(mParentStack);
- }
-
- public FolderType getOpenFolderType() {
- return openFolderType;
- }
-
- /**
- * Moves to parent folder, if one exists.
- *
- * @return Whether the adapter successfully moved to a parent folder.
- */
- public boolean moveToParentFolder() {
- // If we're already at the root, we can't move to a parent folder.
- // An empty parent stack here means we're still waiting for the
- // initial list of bookmarks and can't go to a parent folder.
- if (mParentStack.size() <= 1) {
- return false;
- }
-
- if (mListener != null) {
- // We pick the second folder in the stack as it represents
- // the parent folder.
- mListener.onRefreshFolder(mParentStack.get(1), RefreshType.PARENT);
- }
-
- return true;
- }
-
- /**
- * Moves to child folder, given a folderId.
- *
- * @param folderId The id of the folder to show.
- * @param folderTitle The title of the folder to show.
- */
- public void moveToChildFolder(int folderId, String folderTitle) {
- FolderInfo folderInfo = new FolderInfo(folderId, folderTitle);
-
- if (mListener != null) {
- mListener.onRefreshFolder(folderInfo, RefreshType.CHILD);
- }
- }
-
- /**
- * Set a listener that can refresh this adapter.
- *
- * @param listener The listener that can refresh the adapter.
- */
- public void setOnRefreshFolderListener(OnRefreshFolderListener listener) {
- mListener = listener;
- }
-
- private boolean isCurrentFolder(FolderInfo folderInfo) {
- return (mParentStack.size() > 0 &&
- mParentStack.peek().id == folderInfo.id);
- }
-
- public void swapCursor(Cursor c, FolderInfo folderInfo, RefreshType refreshType) {
- updateOpenFolderType(folderInfo);
- switch (refreshType) {
- case PARENT:
- if (!isCurrentFolder(folderInfo)) {
- mParentStack.removeFirst();
- }
- break;
-
- case CHILD:
- if (!isCurrentFolder(folderInfo)) {
- mParentStack.addFirst(folderInfo);
- }
- break;
-
- default:
- // Do nothing;
- }
-
- swapCursor(c);
- }
-
- private void updateOpenFolderType(final FolderInfo folderInfo) {
- if (folderInfo.id == Bookmarks.FIXED_SCREENSHOT_FOLDER_ID) {
- openFolderType = FolderType.SCREENSHOTS;
- } else {
- openFolderType = FolderType.BOOKMARKS;
- }
- }
-
- @Override
- public int getItemViewType(int position) {
- // The position also reflects the opened child folder row.
- if (isShowingChildFolder()) {
- if (position == 0) {
- return VIEW_TYPE_FOLDER;
- }
-
- // Accounting for the folder view.
- position--;
- }
-
- if (openFolderType == FolderType.SCREENSHOTS) {
- return VIEW_TYPE_SCREENSHOT;
- }
-
- final Cursor c = getCursor(position);
- if (c.getInt(c.getColumnIndexOrThrow(Bookmarks.TYPE)) == Bookmarks.TYPE_FOLDER) {
- return VIEW_TYPE_FOLDER;
- }
-
- // Default to returning normal item type.
- return VIEW_TYPE_BOOKMARK_ITEM;
- }
-
- /**
- * Get the title of the folder given a cursor moved to the position.
- *
- * @param context The context of the view.
- * @param cursor A cursor moved to the required position.
- * @return The title of the folder at the position.
- */
- public String getFolderTitle(Context context, Cursor c) {
- String guid = c.getString(c.getColumnIndexOrThrow(Bookmarks.GUID));
-
- // If we don't have a special GUID, just return the folder title from the DB.
- if (guid == null || guid.length() == 12) {
- return c.getString(c.getColumnIndexOrThrow(Bookmarks.TITLE));
- }
-
- Resources res = context.getResources();
-
- // Use localized strings for special folder names.
- if (guid.equals(Bookmarks.FAKE_DESKTOP_FOLDER_GUID)) {
- return res.getString(R.string.bookmarks_folder_desktop);
- } else if (guid.equals(Bookmarks.MENU_FOLDER_GUID)) {
- return res.getString(R.string.bookmarks_folder_menu);
- } else if (guid.equals(Bookmarks.TOOLBAR_FOLDER_GUID)) {
- return res.getString(R.string.bookmarks_folder_toolbar);
- } else if (guid.equals(Bookmarks.UNFILED_FOLDER_GUID)) {
- return res.getString(R.string.bookmarks_folder_unfiled);
- } else if (guid.equals(Bookmarks.SCREENSHOT_FOLDER_GUID)) {
- return res.getString(R.string.screenshot_folder_label_in_bookmarks);
- } else if (guid.equals(Bookmarks.FAKE_READINGLIST_SMARTFOLDER_GUID)) {
- return res.getString(R.string.readinglist_smartfolder_label_in_bookmarks);
- }
-
- // If for some reason we have a folder with a special GUID, but it's not one of
- // the special folders we expect in the UI, just return the title from the DB.
- return c.getString(c.getColumnIndexOrThrow(Bookmarks.TITLE));
- }
-
- /**
- * @return true, if currently showing a child folder, false otherwise.
- */
- public boolean isShowingChildFolder() {
- if (mParentStack.size() == 0) {
- return false;
- }
-
- return (mParentStack.peek().id != Bookmarks.FIXED_ROOT_ID);
- }
-
- @Override
- public int getCount() {
- return super.getCount() + (isShowingChildFolder() ? 1 : 0);
- }
-
- @Override
- public void bindView(View view, Context context, int position) {
- final int viewType = getItemViewType(position);
-
- final Cursor cursor;
- if (isShowingChildFolder()) {
- if (position == 0) {
- cursor = null;
- } else {
- // Accounting for the folder view.
- position--;
- cursor = getCursor(position);
- }
- } else {
- cursor = getCursor(position);
- }
-
- if (viewType == VIEW_TYPE_SCREENSHOT) {
- ((BookmarkScreenshotRow) view).updateFromCursor(cursor);
- } else if (viewType == VIEW_TYPE_BOOKMARK_ITEM) {
- final TwoLinePageRow row = (TwoLinePageRow) view;
- row.updateFromCursor(cursor);
- } else {
- final BookmarkFolderView row = (BookmarkFolderView) view;
- if (cursor == null) {
- final Resources res = context.getResources();
- row.update(res.getString(R.string.home_move_back_to_filter, mParentStack.get(1).title), -1);
- row.setState(FolderState.PARENT);
- } else {
- int id = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
-
- row.update(getFolderTitle(context, cursor), id);
-
- if (id == Bookmarks.FAKE_READINGLIST_SMARTFOLDER_ID) {
- row.setState(FolderState.READING_LIST);
- } else {
- row.setState(FolderState.FOLDER);
- }
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListView.java b/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListView.java
deleted file mode 100644
index 94157be10..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksListView.java
+++ /dev/null
@@ -1,218 +0,0 @@
- /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.EnumSet;
-import java.util.List;
-
-import android.util.Log;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.Bookmarks;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.HeaderViewListAdapter;
-import android.widget.ListAdapter;
-
-import org.mozilla.gecko.reader.SavedReaderViewHelper;
-import org.mozilla.gecko.util.NetworkUtils;
-
-/**
- * A ListView of bookmarks.
- */
-public class BookmarksListView extends HomeListView
- implements AdapterView.OnItemClickListener {
- public static final String LOGTAG = "GeckoBookmarksListView";
-
- public BookmarksListView(Context context) {
- this(context, null);
- }
-
- public BookmarksListView(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.bookmarksListViewStyle);
- }
-
- public BookmarksListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- setOnItemClickListener(this);
-
- setOnKeyListener(new View.OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- final int action = event.getAction();
-
- // If the user hit the BACK key, try to move to the parent folder.
- if (action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
- return getBookmarksListAdapter().moveToParentFolder();
- }
- return false;
- }
- });
- }
-
- /**
- * Get the appropriate telemetry extra for a given folder.
- *
- * baseFolderID is the ID of the first-level folder in the parent stack, i.e. the first folder
- * that was selected from the root hierarchy (e.g. Desktop, Reading List, or any mobile first-level
- * subfolder). If the current folder is a first-level folder, then the fixed root ID may be used
- * instead.
- *
- * We use baseFolderID only to distinguish whether or not we're currently in a desktop subfolder.
- * If it isn't equal to FAKE_DESKTOP_FOLDER_ID we know we're in a mobile subfolder, or one
- * of the smartfolders.
- */
- private String getTelemetryExtraForFolder(int folderID, int baseFolderID) {
- if (folderID == Bookmarks.FAKE_DESKTOP_FOLDER_ID) {
- return "folder_desktop";
- } else if (folderID == Bookmarks.FIXED_SCREENSHOT_FOLDER_ID) {
- return "folder_screenshots";
- } else if (folderID == Bookmarks.FAKE_READINGLIST_SMARTFOLDER_ID) {
- return "folder_reading_list";
- } else {
- // The stack depth is 2 for either the fake desktop folder, or any subfolder of mobile
- // bookmarks, we subtract these offsets so that any direct subfolder of mobile
- // has a level equal to 1. (Desktop folders will be one level deeper due to the
- // fake desktop folder, hence subtract 2.)
- if (baseFolderID == Bookmarks.FAKE_DESKTOP_FOLDER_ID) {
- return "folder_desktop_subfolder";
- } else {
- return "folder_mobile_subfolder";
- }
- }
- }
-
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- final BookmarksListAdapter adapter = getBookmarksListAdapter();
- if (adapter.isShowingChildFolder()) {
- if (position == 0) {
- // If we tap on an opened folder, move back to parent folder.
-
- final List<BookmarksListAdapter.FolderInfo> parentStack = ((BookmarksListAdapter) getAdapter()).getParentStack();
- if (parentStack.size() < 2) {
- throw new IllegalStateException("Cannot move to parent folder if we are already in the root folder");
- }
-
- // The first item (top of stack) is the current folder, we're returning to the next one
- BookmarksListAdapter.FolderInfo folder = parentStack.get(1);
- final int parentID = folder.id;
- final int baseFolderID;
- if (parentStack.size() > 2) {
- baseFolderID = parentStack.get(parentStack.size() - 2).id;
- } else {
- baseFolderID = Bookmarks.FIXED_ROOT_ID;
- }
-
- final String extra = getTelemetryExtraForFolder(parentID, baseFolderID);
-
- // Move to parent _after_ retrieving stack information
- adapter.moveToParentFolder();
-
- Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.LIST_ITEM, extra);
- return;
- }
-
- // Accounting for the folder view.
- position--;
- }
-
- final Cursor cursor = adapter.getCursor();
- if (cursor == null) {
- return;
- }
-
- cursor.moveToPosition(position);
-
- if (adapter.getOpenFolderType() == BookmarksListAdapter.FolderType.SCREENSHOTS) {
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, "bookmarks-screenshot");
-
- final String fileUrl = "file://" + cursor.getString(cursor.getColumnIndex(BrowserContract.UrlAnnotations.VALUE));
- getOnUrlOpenListener().onUrlOpen(fileUrl, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
- return;
- }
-
- int type = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.TYPE));
- if (type == Bookmarks.TYPE_FOLDER) {
- // If we're clicking on a folder, update adapter to move to that folder
- final int folderId = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
- final String folderTitle = adapter.getFolderTitle(parent.getContext(), cursor);
- adapter.moveToChildFolder(folderId, folderTitle);
-
- final List<BookmarksListAdapter.FolderInfo> parentStack = ((BookmarksListAdapter) getAdapter()).getParentStack();
-
- final int baseFolderID;
- if (parentStack.size() > 2) {
- baseFolderID = parentStack.get(parentStack.size() - 2).id;
- } else {
- baseFolderID = Bookmarks.FIXED_ROOT_ID;
- }
-
- final String extra = getTelemetryExtraForFolder(folderId, baseFolderID);
- Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.LIST_ITEM, extra);
- } else {
- // Otherwise, just open the URL
- final String url = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.URL));
-
- final SavedReaderViewHelper rvh = SavedReaderViewHelper.getSavedReaderViewHelper(getContext());
-
- final String extra;
- if (rvh.isURLCached(url)) {
- extra = "bookmarks-reader";
- } else {
- extra = "bookmarks";
- }
-
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, extra);
- Telemetry.addToHistogram("FENNEC_LOAD_SAVED_PAGE", NetworkUtils.isConnected(getContext()) ? 2 : 3);
-
- // This item is a TwoLinePageRow, so we allow switch-to-tab.
- getOnUrlOpenListener().onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
- }
- }
-
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
- // Adjust the item position to account for the parent folder row that is inserted
- // at the top of the list when viewing the contents of a folder.
- final BookmarksListAdapter adapter = getBookmarksListAdapter();
- if (adapter.isShowingChildFolder()) {
- position--;
- }
-
- // Temporarily prevent crashes until we figure out what we actually want to do here (bug 1252316).
- if (adapter.getOpenFolderType() == BookmarksListAdapter.FolderType.SCREENSHOTS) {
- return false;
- }
-
- return super.onItemLongClick(parent, view, position, id);
- }
-
- private BookmarksListAdapter getBookmarksListAdapter() {
- BookmarksListAdapter adapter;
- ListAdapter listAdapter = getAdapter();
- if (listAdapter instanceof HeaderViewListAdapter) {
- adapter = (BookmarksListAdapter) ((HeaderViewListAdapter) listAdapter).getWrappedAdapter();
- } else {
- adapter = (BookmarksListAdapter) listAdapter;
- }
- return adapter;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java b/mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java
deleted file mode 100644
index 4b4781996..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java
+++ /dev/null
@@ -1,316 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.Bookmarks;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy;
-import org.mozilla.gecko.home.BookmarksListAdapter.FolderInfo;
-import org.mozilla.gecko.home.BookmarksListAdapter.OnRefreshFolderListener;
-import org.mozilla.gecko.home.BookmarksListAdapter.RefreshType;
-import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-
-import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.database.Cursor;
-import android.database.MergeCursor;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.support.annotation.NonNull;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.Loader;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-/**
- * A page in about:home that displays a ListView of bookmarks.
- */
-public class BookmarksPanel extends HomeFragment {
- public static final String LOGTAG = "GeckoBookmarksPanel";
-
- // Cursor loader ID for list of bookmarks.
- private static final int LOADER_ID_BOOKMARKS_LIST = 0;
-
- // Information about the target bookmarks folder.
- private static final String BOOKMARKS_FOLDER_INFO = "folder_info";
-
- // Refresh type for folder refreshing loader.
- private static final String BOOKMARKS_REFRESH_TYPE = "refresh_type";
-
- // List of bookmarks.
- private BookmarksListView mList;
-
- // Adapter for list of bookmarks.
- private BookmarksListAdapter mListAdapter;
-
- // Adapter's parent stack.
- private List<FolderInfo> mSavedParentStack;
-
- // Reference to the View to display when there are no results.
- private View mEmptyView;
-
- // Callback for cursor loaders.
- private CursorLoaderCallbacks mLoaderCallbacks;
-
- @Override
- public void restoreData(@NonNull Bundle data) {
- final ArrayList<FolderInfo> stack = data.getParcelableArrayList("parentStack");
- if (stack == null) {
- return;
- }
-
- if (mListAdapter == null) {
- mSavedParentStack = new LinkedList<FolderInfo>(stack);
- } else {
- mListAdapter.restoreData(stack);
- }
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.home_bookmarks_panel, container, false);
-
- mList = (BookmarksListView) view.findViewById(R.id.bookmarks_list);
-
- mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
- @Override
- public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
- final int type = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.TYPE));
- if (type == Bookmarks.TYPE_FOLDER) {
- // We don't show a context menu for folders
- return null;
- }
- final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
- info.url = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.URL));
- info.title = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.TITLE));
- info.bookmarkId = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
- info.itemType = RemoveItemType.BOOKMARKS;
- return info;
- }
- });
-
- return view;
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- OnUrlOpenListener listener = null;
- try {
- listener = (OnUrlOpenListener) getActivity();
- } catch (ClassCastException e) {
- throw new ClassCastException(getActivity().toString()
- + " must implement HomePager.OnUrlOpenListener");
- }
-
- mList.setTag(HomePager.LIST_TAG_BOOKMARKS);
- mList.setOnUrlOpenListener(listener);
-
- registerForContextMenu(mList);
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- final Activity activity = getActivity();
-
- // Setup the list adapter.
- mListAdapter = new BookmarksListAdapter(activity, null, mSavedParentStack);
- mListAdapter.setOnRefreshFolderListener(new OnRefreshFolderListener() {
- @Override
- public void onRefreshFolder(FolderInfo folderInfo, RefreshType refreshType) {
- // Restart the loader with folder as the argument.
- Bundle bundle = new Bundle();
- bundle.putParcelable(BOOKMARKS_FOLDER_INFO, folderInfo);
- bundle.putParcelable(BOOKMARKS_REFRESH_TYPE, refreshType);
- getLoaderManager().restartLoader(LOADER_ID_BOOKMARKS_LIST, bundle, mLoaderCallbacks);
- }
- });
- mList.setAdapter(mListAdapter);
-
- // Create callbacks before the initial loader is started.
- mLoaderCallbacks = new CursorLoaderCallbacks();
- loadIfVisible();
- }
-
- @Override
- public void onDestroyView() {
- mList = null;
- mListAdapter = null;
- mEmptyView = null;
- super.onDestroyView();
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
- if (isVisible()) {
- // The parent stack is saved just so that the folder state can be
- // restored on rotation.
- mSavedParentStack = mListAdapter.getParentStack();
- }
- }
-
- @Override
- protected void load() {
- final Bundle bundle;
- if (mSavedParentStack != null && mSavedParentStack.size() > 1) {
- bundle = new Bundle();
- bundle.putParcelable(BOOKMARKS_FOLDER_INFO, mSavedParentStack.get(0));
- bundle.putParcelable(BOOKMARKS_REFRESH_TYPE, RefreshType.CHILD);
- } else {
- bundle = null;
- }
-
- getLoaderManager().initLoader(LOADER_ID_BOOKMARKS_LIST, bundle, mLoaderCallbacks);
- }
-
- private void updateUiFromCursor(Cursor c) {
- if ((c == null || c.getCount() == 0) && mEmptyView == null) {
- // Set empty page view. We delay this so that the empty view won't flash.
- final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
- mEmptyView = emptyViewStub.inflate();
-
- final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
- emptyIcon.setImageResource(R.drawable.icon_bookmarks_empty);
-
- final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
- emptyText.setText(R.string.home_bookmarks_empty);
-
- mList.setEmptyView(mEmptyView);
- }
- }
-
- /**
- * Loader for the list for bookmarks.
- */
- private static class BookmarksLoader extends SimpleCursorLoader {
- private final FolderInfo mFolderInfo;
- private final RefreshType mRefreshType;
- private final BrowserDB mDB;
-
- public BookmarksLoader(Context context) {
- this(context,
- new FolderInfo(Bookmarks.FIXED_ROOT_ID, context.getResources().getString(R.string.bookmarks_title)),
- RefreshType.CHILD);
- }
-
- public BookmarksLoader(Context context, FolderInfo folderInfo, RefreshType refreshType) {
- super(context);
- mFolderInfo = folderInfo;
- mRefreshType = refreshType;
- mDB = BrowserDB.from(context);
- }
-
- @Override
- public Cursor loadCursor() {
- final boolean isRootFolder = mFolderInfo.id == BrowserContract.Bookmarks.FIXED_ROOT_ID;
-
- final ContentResolver contentResolver = getContext().getContentResolver();
-
- Cursor partnerCursor = null;
- Cursor userCursor = null;
-
- if (GeckoSharedPrefs.forProfile(getContext()).getBoolean(GeckoPreferences.PREFS_READ_PARTNER_BOOKMARKS_PROVIDER, false)
- && (isRootFolder || mFolderInfo.id <= Bookmarks.FAKE_PARTNER_BOOKMARKS_START)) {
- partnerCursor = contentResolver.query(PartnerBookmarksProviderProxy.getUriForBookmarks(getContext(), mFolderInfo.id), null, null, null, null, null);
- }
-
- if (isRootFolder || mFolderInfo.id > Bookmarks.FAKE_PARTNER_BOOKMARKS_START) {
- userCursor = mDB.getBookmarksInFolder(contentResolver, mFolderInfo.id);
- }
-
-
- if (partnerCursor == null && userCursor == null) {
- return null;
- } else if (partnerCursor == null) {
- return userCursor;
- } else if (userCursor == null) {
- return partnerCursor;
- } else {
- return new MergeCursor(new Cursor[] { partnerCursor, userCursor });
- }
- }
-
- @Override
- public void onContentChanged() {
- // Invalidate the cached value that keeps track of whether or
- // not desktop bookmarks exist.
- mDB.invalidate();
- super.onContentChanged();
- }
-
- public FolderInfo getFolderInfo() {
- return mFolderInfo;
- }
-
- public RefreshType getRefreshType() {
- return mRefreshType;
- }
- }
-
- /**
- * Loader callbacks for the LoaderManager of this fragment.
- */
- private class CursorLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- if (args == null) {
- return new BookmarksLoader(getActivity());
- } else {
- FolderInfo folderInfo = (FolderInfo) args.getParcelable(BOOKMARKS_FOLDER_INFO);
- RefreshType refreshType = (RefreshType) args.getParcelable(BOOKMARKS_REFRESH_TYPE);
- return new BookmarksLoader(getActivity(), folderInfo, refreshType);
- }
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
- BookmarksLoader bl = (BookmarksLoader) loader;
- mListAdapter.swapCursor(c, bl.getFolderInfo(), bl.getRefreshType());
-
- if (mPanelStateChangeListener != null) {
- final List<FolderInfo> parentStack = mListAdapter.getParentStack();
- final Bundle bundle = new Bundle();
-
- // Bundle likes to store ArrayLists or Arrays, but we've got a generic List (which
- // is actually an unmodifiable wrapper around a LinkedList). We'll need to do a
- // LinkedList conversion at the other end, when saving we need to use this awkward
- // syntax to create an Array.
- bundle.putParcelableArrayList("parentStack", new ArrayList<FolderInfo>(parentStack));
-
- mPanelStateChangeListener.onStateChanged(bundle);
- }
- updateUiFromCursor(c);
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- if (mList != null) {
- mListAdapter.swapCursor(null);
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java b/mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java
deleted file mode 100644
index 7732932fe..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java
+++ /dev/null
@@ -1,1316 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Locale;
-
-import android.content.SharedPreferences;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.PrefsHelper;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.SuggestClient;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.History;
-import org.mozilla.gecko.db.BrowserContract.URLColumns;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.home.SearchLoader.SearchCursorLoader;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.toolbar.AutocompleteHandler;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.StringUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v4.app.LoaderManager.LoaderCallbacks;
-import android.support.v4.content.AsyncTaskLoader;
-import android.support.v4.content.Loader;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.LayoutInflater;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.view.WindowManager.LayoutParams;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.Animation;
-import android.view.animation.TranslateAnimation;
-import android.widget.AdapterView;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.TextView;
-
-/**
- * Fragment that displays frecency search results in a ListView.
- */
-public class BrowserSearch extends HomeFragment
- implements GeckoEventListener,
- SearchEngineBar.OnSearchBarClickListener {
-
- @RobocopTarget
- public interface SuggestClientFactory {
- public SuggestClient getSuggestClient(Context context, String template, int timeout, int max);
- }
-
- @RobocopTarget
- public static class DefaultSuggestClientFactory implements SuggestClientFactory {
- @Override
- public SuggestClient getSuggestClient(Context context, String template, int timeout, int max) {
- return new SuggestClient(context, template, timeout, max, true);
- }
- }
-
- /**
- * Set this to mock the suggestion mechanism. Public for access from tests.
- */
- @RobocopTarget
- public static volatile SuggestClientFactory sSuggestClientFactory = new DefaultSuggestClientFactory();
-
- // Logging tag name
- private static final String LOGTAG = "GeckoBrowserSearch";
-
- // Cursor loader ID for search query
- private static final int LOADER_ID_SEARCH = 0;
-
- // AsyncTask loader ID for suggestion query
- private static final int LOADER_ID_SUGGESTION = 1;
- private static final int LOADER_ID_SAVED_SUGGESTION = 2;
-
- // Timeout for the suggestion client to respond
- private static final int SUGGESTION_TIMEOUT = 3000;
-
- // Maximum number of suggestions from the search engine's suggestion client. This impacts network traffic and device
- // data consumption whereas R.integer.max_saved_suggestions controls how many suggestion to show in the UI.
- private static final int NETWORK_SUGGESTION_MAX = 3;
-
- // The maximum number of rows deep in a search we'll dig
- // for an autocomplete result
- private static final int MAX_AUTOCOMPLETE_SEARCH = 20;
-
- // Length of https:// + 1 required to make autocomplete
- // fill in the domain, for both http:// and https://
- private static final int HTTPS_PREFIX_LENGTH = 9;
-
- // Duration for fade-in animation
- private static final int ANIMATION_DURATION = 250;
-
- // Holds the current search term to use in the query
- private volatile String mSearchTerm;
-
- // Adapter for the list of search results
- private SearchAdapter mAdapter;
-
- // The view shown by the fragment
- private LinearLayout mView;
-
- // The list showing search results
- private HomeListView mList;
-
- // The bar on the bottom of the screen displaying search engine options.
- private SearchEngineBar mSearchEngineBar;
-
- // Client that performs search suggestion queries.
- // Public for testing.
- @RobocopTarget
- public volatile SuggestClient mSuggestClient;
-
- // List of search engines from Gecko.
- // Do not mutate this list.
- // Access to this member must only occur from the UI thread.
- private List<SearchEngine> mSearchEngines;
-
- // Search history suggestions
- private ArrayList<String> mSearchHistorySuggestions;
-
- // Track the locale that was last in use when we filled mSearchEngines.
- // Access to this member must only occur from the UI thread.
- private Locale mLastLocale;
-
- // Whether search suggestions are enabled or not
- private boolean mSuggestionsEnabled;
-
- // Whether history suggestions are enabled or not
- private boolean mSavedSearchesEnabled;
-
- // Callbacks used for the search loader
- private CursorLoaderCallbacks mCursorLoaderCallbacks;
-
- // Callbacks used for the search suggestion loader
- private SearchEngineSuggestionLoaderCallbacks mSearchEngineSuggestionLoaderCallbacks;
- private SearchHistorySuggestionLoaderCallbacks mSearchHistorySuggestionLoaderCallback;
-
- // Autocomplete handler used when filtering results
- private AutocompleteHandler mAutocompleteHandler;
-
- // On search listener
- private OnSearchListener mSearchListener;
-
- // On edit suggestion listener
- private OnEditSuggestionListener mEditSuggestionListener;
-
- // Whether the suggestions will fade in when shown
- private boolean mAnimateSuggestions;
-
- // Opt-in prompt view for search suggestions
- private View mSuggestionsOptInPrompt;
-
- public interface OnSearchListener {
- void onSearch(SearchEngine engine, String text, TelemetryContract.Method method);
- }
-
- public interface OnEditSuggestionListener {
- public void onEditSuggestion(String suggestion);
- }
-
- public static BrowserSearch newInstance() {
- BrowserSearch browserSearch = new BrowserSearch();
-
- final Bundle args = new Bundle();
- args.putBoolean(HomePager.CAN_LOAD_ARG, true);
- browserSearch.setArguments(args);
-
- return browserSearch;
- }
-
- public BrowserSearch() {
- mSearchTerm = "";
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
-
- try {
- mSearchListener = (OnSearchListener) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException(activity.toString()
- + " must implement BrowserSearch.OnSearchListener");
- }
-
- try {
- mEditSuggestionListener = (OnEditSuggestionListener) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException(activity.toString()
- + " must implement BrowserSearch.OnEditSuggestionListener");
- }
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
-
- mAutocompleteHandler = null;
- mSearchListener = null;
- mEditSuggestionListener = null;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mSearchEngines = new ArrayList<SearchEngine>();
- mSearchHistorySuggestions = new ArrayList<>();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
-
- mSearchEngines = null;
- }
-
- @Override
- public void onHiddenChanged(boolean hidden) {
- if (!hidden) {
- final Tab tab = Tabs.getInstance().getSelectedTab();
- final boolean isPrivate = (tab != null && tab.isPrivate());
-
- // Removes Search Suggestions Loader if in private browsing mode
- // Loader may have been inserted when browsing in normal tab
- if (isPrivate) {
- getLoaderManager().destroyLoader(LOADER_ID_SUGGESTION);
- }
-
- GeckoAppShell.notifyObservers("SearchEngines:GetVisible", null);
- }
- super.onHiddenChanged(hidden);
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(getContext());
- mSavedSearchesEnabled = prefs.getBoolean(GeckoPreferences.PREFS_HISTORY_SAVED_SEARCH, true);
-
- // Fetch engines if we need to.
- if (mSearchEngines.isEmpty() || !Locale.getDefault().equals(mLastLocale)) {
- GeckoAppShell.notifyObservers("SearchEngines:GetVisible", null);
- } else {
- updateSearchEngineBar();
- }
-
- Telemetry.startUISession(TelemetryContract.Session.FRECENCY);
- }
-
- @Override
- public void onPause() {
- super.onPause();
-
- Telemetry.stopUISession(TelemetryContract.Session.FRECENCY);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- // All list views are styled to look the same with a global activity theme.
- // If the style of the list changes, inflate it from an XML.
- mView = (LinearLayout) inflater.inflate(R.layout.browser_search, container, false);
- mList = (HomeListView) mView.findViewById(R.id.home_list_view);
- mSearchEngineBar = (SearchEngineBar) mView.findViewById(R.id.search_engine_bar);
-
- return mView;
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
-
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
- "SearchEngines:Data");
-
- mSearchEngineBar.setAdapter(null);
- mSearchEngineBar = null;
-
- mList.setAdapter(null);
- mList = null;
-
- mView = null;
- mSuggestionsOptInPrompt = null;
- mSuggestClient = null;
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- mList.setTag(HomePager.LIST_TAG_BROWSER_SEARCH);
-
- mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- // Perform the user-entered search if the user clicks on a search engine row.
- // This row will be disabled if suggestions (in addition to the user-entered term) are showing.
- if (view instanceof SearchEngineRow) {
- ((SearchEngineRow) view).performUserEnteredSearch();
- return;
- }
-
- // Account for the search engine rows.
- position -= getPrimaryEngineCount();
- final Cursor c = mAdapter.getCursor(position);
- final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
-
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, "frecency");
-
- // This item is a TwoLinePageRow, so we allow switch-to-tab.
- mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
- }
- });
-
- mList.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
- // Don't do anything when the user long-clicks on a search engine row.
- if (view instanceof SearchEngineRow) {
- return true;
- }
-
- // Account for the search engine rows.
- position -= getPrimaryEngineCount();
- return mList.onItemLongClick(parent, view, position, id);
- }
- });
-
- final ListSelectionListener listener = new ListSelectionListener();
- mList.setOnItemSelectedListener(listener);
- mList.setOnFocusChangeListener(listener);
-
- mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
- @Override
- public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
- final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
- info.url = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
- info.title = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.TITLE));
-
- int bookmarkId = cursor.getInt(cursor.getColumnIndexOrThrow(BrowserContract.Combined.BOOKMARK_ID));
- info.bookmarkId = bookmarkId;
-
- int historyId = cursor.getInt(cursor.getColumnIndexOrThrow(BrowserContract.Combined.HISTORY_ID));
- info.historyId = historyId;
-
- boolean isBookmark = bookmarkId != -1;
- boolean isHistory = historyId != -1;
-
- if (isBookmark && isHistory) {
- info.itemType = HomeContextMenuInfo.RemoveItemType.COMBINED;
- } else if (isBookmark) {
- info.itemType = HomeContextMenuInfo.RemoveItemType.BOOKMARKS;
- } else if (isHistory) {
- info.itemType = HomeContextMenuInfo.RemoveItemType.HISTORY;
- }
-
- return info;
- }
- });
-
- mList.setOnKeyListener(new View.OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, android.view.KeyEvent event) {
- final View selected = mList.getSelectedView();
-
- if (selected instanceof SearchEngineRow) {
- return selected.onKeyDown(keyCode, event);
- }
- return false;
- }
- });
-
- registerForContextMenu(mList);
- GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
- "SearchEngines:Data");
-
- mSearchEngineBar.setOnSearchBarClickListener(this);
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
- if (!(menuInfo instanceof HomeContextMenuInfo)) {
- return;
- }
-
- HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
-
- MenuInflater inflater = new MenuInflater(view.getContext());
- inflater.inflate(R.menu.browsersearch_contextmenu, menu);
-
- menu.setHeaderTitle(info.getDisplayTitle());
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- ContextMenuInfo menuInfo = item.getMenuInfo();
- if (!(menuInfo instanceof HomeContextMenuInfo)) {
- return false;
- }
-
- final HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
- final Context context = getActivity();
-
- final int itemId = item.getItemId();
-
- if (itemId == R.id.browsersearch_remove) {
- // Position for Top Sites grid items, but will always be -1 since this is only for BrowserSearch result
- final int position = -1;
-
- new RemoveItemByUrlTask(context, info.url, info.itemType, position).execute();
- return true;
- }
-
- return false;
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- // Initialize the search adapter
- mAdapter = new SearchAdapter(getActivity());
- mList.setAdapter(mAdapter);
-
- // Only create an instance when we need it
- mSearchEngineSuggestionLoaderCallbacks = null;
- mSearchHistorySuggestionLoaderCallback = null;
-
- // Create callbacks before the initial loader is started
- mCursorLoaderCallbacks = new CursorLoaderCallbacks();
- loadIfVisible();
- }
-
- @Override
- public void handleMessage(String event, final JSONObject message) {
- if (event.equals("SearchEngines:Data")) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- setSearchEngines(message);
- }
- });
- }
- }
-
- @Override
- protected void load() {
- SearchLoader.init(getLoaderManager(), LOADER_ID_SEARCH, mCursorLoaderCallbacks, mSearchTerm);
- }
-
- private void handleAutocomplete(String searchTerm, Cursor c) {
- if (c == null ||
- mAutocompleteHandler == null ||
- TextUtils.isEmpty(searchTerm)) {
- return;
- }
-
- // Avoid searching the path if we don't have to. Currently just
- // decided by whether there is a '/' character in the string.
- final boolean searchPath = searchTerm.indexOf('/') > 0;
- final String autocompletion = findAutocompletion(searchTerm, c, searchPath);
-
- if (autocompletion == null || mAutocompleteHandler == null) {
- return;
- }
-
- // Prefetch auto-completed domain since it's a likely target
- GeckoAppShell.notifyObservers("Session:Prefetch", "http://" + autocompletion);
-
- mAutocompleteHandler.onAutocomplete(autocompletion);
- mAutocompleteHandler = null;
- }
-
- /**
- * Returns the substring of a provided URI, starting at the given offset,
- * and extending up to the end of the path segment in which the provided
- * index is found.
- *
- * For example, given
- *
- * "www.reddit.com/r/boop/abcdef", 0, ?
- *
- * this method returns
- *
- * ?=2: "www.reddit.com/"
- * ?=17: "www.reddit.com/r/boop/"
- * ?=21: "www.reddit.com/r/boop/"
- * ?=22: "www.reddit.com/r/boop/abcdef"
- *
- */
- private static String uriSubstringUpToMatchedPath(final String url, final int offset, final int begin) {
- final int afterEnd = url.length();
-
- // We want to include the trailing slash, but not other characters.
- int chop = url.indexOf('/', begin);
- if (chop != -1) {
- ++chop;
- if (chop < offset) {
- // This isn't supposed to happen. Fall back to returning the whole damn thing.
- return url;
- }
- } else {
- chop = url.indexOf('?', begin);
- if (chop == -1) {
- chop = url.indexOf('#', begin);
- }
- if (chop == -1) {
- chop = afterEnd;
- }
- }
-
- return url.substring(offset, chop);
- }
-
- LinkedHashSet<String> domains = null;
- private LinkedHashSet<String> getDomains() {
- if (domains == null) {
- domains = new LinkedHashSet<String>(500);
- BufferedReader buf = null;
- try {
- buf = new BufferedReader(new InputStreamReader(getResources().openRawResource(R.raw.topdomains)));
- String res = null;
-
- do {
- res = buf.readLine();
- if (res != null) {
- domains.add(res);
- }
- } while (res != null);
- } catch (IOException e) {
- Log.e(LOGTAG, "Error reading domains", e);
- } finally {
- if (buf != null) {
- try {
- buf.close();
- } catch (IOException e) { }
- }
- }
- }
- return domains;
- }
-
- private String searchDomains(String search) {
- for (String domain : getDomains()) {
- if (domain.startsWith(search)) {
- return domain;
- }
- }
- return null;
- }
-
- private String findAutocompletion(String searchTerm, Cursor c, boolean searchPath) {
- if (!c.moveToFirst()) {
- // No cursor probably means no history, so let's try the fallback list.
- return searchDomains(searchTerm);
- }
-
- final int searchLength = searchTerm.length();
- final int urlIndex = c.getColumnIndexOrThrow(History.URL);
- int searchCount = 0;
-
- do {
- final String url = c.getString(urlIndex);
-
- if (searchCount == 0) {
- // Prefetch the first item in the list since it's weighted the highest
- GeckoAppShell.notifyObservers("Session:Prefetch", url);
- }
-
- // Does the completion match against the whole URL? This will match
- // about: pages, as well as user input including "http://...".
- if (url.startsWith(searchTerm)) {
- return uriSubstringUpToMatchedPath(url, 0,
- (searchLength > HTTPS_PREFIX_LENGTH) ? searchLength : HTTPS_PREFIX_LENGTH);
- }
-
- final Uri uri = Uri.parse(url);
- final String host = uri.getHost();
-
- // Host may be null for about pages.
- if (host == null) {
- continue;
- }
-
- if (host.startsWith(searchTerm)) {
- return host + "/";
- }
-
- final String strippedHost = StringUtils.stripCommonSubdomains(host);
- if (strippedHost.startsWith(searchTerm)) {
- return strippedHost + "/";
- }
-
- ++searchCount;
-
- if (!searchPath) {
- continue;
- }
-
- // Otherwise, if we're matching paths, let's compare against the string itself.
- final int hostOffset = url.indexOf(strippedHost);
- if (hostOffset == -1) {
- // This was a URL string that parsed to a different host (normalized?).
- // Give up.
- continue;
- }
-
- // We already matched the non-stripped host, so now we're
- // substring-searching in the part of the URL without the common
- // subdomains.
- if (url.startsWith(searchTerm, hostOffset)) {
- // Great! Return including the rest of the path segment.
- return uriSubstringUpToMatchedPath(url, hostOffset, hostOffset + searchLength);
- }
- } while (searchCount < MAX_AUTOCOMPLETE_SEARCH && c.moveToNext());
-
- // If we can't find an autocompletion domain from history, let's try using the fallback list.
- return searchDomains(searchTerm);
- }
-
- public void resetScrollState() {
- mSearchEngineBar.scrollToPosition(0);
- }
-
- private void filterSuggestions() {
- Tab tab = Tabs.getInstance().getSelectedTab();
- final boolean isPrivate = (tab != null && tab.isPrivate());
-
- // mSuggestClient may be null if we haven't received our search engine list yet - hence
- // we need to exit here in that case.
- if (isPrivate || mSuggestClient == null || (!mSuggestionsEnabled && !mSavedSearchesEnabled)) {
- mSearchHistorySuggestions.clear();
- return;
- }
-
- // Suggestions from search engine
- if (mSearchEngineSuggestionLoaderCallbacks == null) {
- mSearchEngineSuggestionLoaderCallbacks = new SearchEngineSuggestionLoaderCallbacks();
- }
- getLoaderManager().restartLoader(LOADER_ID_SUGGESTION, null, mSearchEngineSuggestionLoaderCallbacks);
-
- // Saved suggestions
- if (mSearchHistorySuggestionLoaderCallback == null) {
- mSearchHistorySuggestionLoaderCallback = new SearchHistorySuggestionLoaderCallbacks();
- }
- getLoaderManager().restartLoader(LOADER_ID_SAVED_SUGGESTION, null, mSearchHistorySuggestionLoaderCallback);
- }
-
- private void setSuggestions(ArrayList<String> suggestions) {
- ThreadUtils.assertOnUiThread();
-
- // mSearchEngines may be null if the setSuggestions calls after onDestroy (bug 1310621).
- // So drop the suggestions if search engines are not available
- if (mSearchEngines != null && !mSearchEngines.isEmpty()) {
- mSearchEngines.get(0).setSuggestions(suggestions);
- mAdapter.notifyDataSetChanged();
- }
-
- }
-
- private void setSavedSuggestions(ArrayList<String> savedSuggestions) {
- ThreadUtils.assertOnUiThread();
-
- mSearchHistorySuggestions = savedSuggestions;
- mAdapter.notifyDataSetChanged();
- }
-
- private boolean shouldUpdateSearchEngine(ArrayList<SearchEngine> searchEngines) {
- if (searchEngines.size() != mSearchEngines.size()) {
- return true;
- }
-
- int size = searchEngines.size();
-
- for (int i = 0; i < size; i++) {
- if (!mSearchEngines.get(i).name.equals(searchEngines.get(i).name)) {
- return true;
- }
- }
-
- return false;
- }
-
- private void setSearchEngines(JSONObject data) {
- ThreadUtils.assertOnUiThread();
-
- // This method is called via a Runnable posted from the Gecko thread, so
- // it's possible the fragment and/or its view has been destroyed by the
- // time we get here. If so, just abort.
- if (mView == null) {
- return;
- }
-
- try {
- final JSONObject suggest = data.getJSONObject("suggest");
- final String suggestEngine = suggest.optString("engine", null);
- final String suggestTemplate = suggest.optString("template", null);
- final boolean suggestionsPrompted = suggest.getBoolean("prompted");
- final JSONArray engines = data.getJSONArray("searchEngines");
-
- mSuggestionsEnabled = suggest.getBoolean("enabled");
-
- ArrayList<SearchEngine> searchEngines = new ArrayList<SearchEngine>();
- for (int i = 0; i < engines.length(); i++) {
- final JSONObject engineJSON = engines.getJSONObject(i);
- final SearchEngine engine = new SearchEngine((Context) getActivity(), engineJSON);
-
- if (engine.name.equals(suggestEngine) && suggestTemplate != null) {
- // Suggest engine should be at the front of the list.
- // We're baking in an assumption here that the suggest engine
- // is also the default engine.
- searchEngines.add(0, engine);
-
- ensureSuggestClientIsSet(suggestTemplate);
- } else {
- searchEngines.add(engine);
- }
- }
-
- // checking if the new searchEngine is different from mSearchEngine, will have to re-layout if yes
- boolean change = shouldUpdateSearchEngine(searchEngines);
-
- if (mAdapter != null && change) {
- mSearchEngines = Collections.unmodifiableList(searchEngines);
- mLastLocale = Locale.getDefault();
- updateSearchEngineBar();
-
- mAdapter.notifyDataSetChanged();
- }
-
- final Tab tab = Tabs.getInstance().getSelectedTab();
- final boolean isPrivate = (tab != null && tab.isPrivate());
-
- // Show suggestions opt-in prompt only if suggestions are not enabled yet,
- // user hasn't been prompted and we're not on a private browsing tab.
- // The prompt might have been inflated already when this view was previously called.
- // Remove the opt-in prompt if it has been inflated in the view and dealt with by the user,
- // or if we're on a private browsing tab
- if (!mSuggestionsEnabled && !suggestionsPrompted && !isPrivate) {
- showSuggestionsOptIn();
- } else {
- removeSuggestionsOptIn();
- }
-
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error getting search engine JSON", e);
- }
-
- filterSuggestions();
- }
-
- private void updateSearchEngineBar() {
- final int primaryEngineCount = getPrimaryEngineCount();
-
- if (primaryEngineCount < mSearchEngines.size()) {
- mSearchEngineBar.setSearchEngines(
- mSearchEngines.subList(primaryEngineCount, mSearchEngines.size())
- );
- mSearchEngineBar.setVisibility(View.VISIBLE);
- } else {
- mSearchEngineBar.setVisibility(View.GONE);
- }
- }
-
- @Override
- public void onSearchBarClickListener(final SearchEngine searchEngine) {
- final TelemetryContract.Method method = TelemetryContract.Method.LIST_ITEM;
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, method, "searchenginebar");
- mSearchListener.onSearch(searchEngine, mSearchTerm, method);
- }
-
- private void ensureSuggestClientIsSet(final String suggestTemplate) {
- // Don't update the suggestClient if we already have a client with the correct template
- if (mSuggestClient != null && suggestTemplate.equals(mSuggestClient.getSuggestTemplate())) {
- return;
- }
-
- mSuggestClient = sSuggestClientFactory.getSuggestClient(getActivity(), suggestTemplate, SUGGESTION_TIMEOUT, NETWORK_SUGGESTION_MAX);
- }
-
- private void showSuggestionsOptIn() {
- // Only make the ViewStub visible again if it has already previously been shown.
- // (An inflated ViewStub is removed from the View hierarchy so a second call to findViewById will return null,
- // which also further necessitates handling this separately.)
- if (mSuggestionsOptInPrompt != null) {
- mSuggestionsOptInPrompt.setVisibility(View.VISIBLE);
- return;
- }
-
- mSuggestionsOptInPrompt = ((ViewStub) mView.findViewById(R.id.suggestions_opt_in_prompt)).inflate();
-
- TextView promptText = (TextView) mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_title);
- promptText.setText(getResources().getString(R.string.suggestions_prompt));
-
- final View yesButton = mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_yes);
- final View noButton = mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_no);
-
- final OnClickListener listener = new OnClickListener() {
- @Override
- public void onClick(View v) {
- // Prevent the buttons from being clicked multiple times (bug 816902)
- yesButton.setOnClickListener(null);
- noButton.setOnClickListener(null);
-
- setSuggestionsEnabled(v == yesButton);
- }
- };
-
- yesButton.setOnClickListener(listener);
- noButton.setOnClickListener(listener);
-
- // If the prompt gains focus, automatically pass focus to the
- // yes button in the prompt.
- final View prompt = mSuggestionsOptInPrompt.findViewById(R.id.prompt);
- prompt.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus) {
- yesButton.requestFocus();
- }
- }
- });
- }
-
- private void removeSuggestionsOptIn() {
- if (mSuggestionsOptInPrompt == null) {
- return;
- }
-
- mSuggestionsOptInPrompt.setVisibility(View.GONE);
- }
-
- private void setSuggestionsEnabled(final boolean enabled) {
- // Clicking the yes/no buttons quickly can cause the click events be
- // queued before the listeners are removed above, so it's possible
- // setSuggestionsEnabled() can be called twice. mSuggestionsOptInPrompt
- // can be null if this happens (bug 828480).
- if (mSuggestionsOptInPrompt == null) {
- return;
- }
-
- // Make suggestions appear immediately after the user opts in
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- SuggestClient client = mSuggestClient;
- if (client != null) {
- client.query(mSearchTerm);
- }
- }
- });
-
- PrefsHelper.setPref("browser.search.suggest.prompted", true);
- PrefsHelper.setPref("browser.search.suggest.enabled", enabled);
-
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, (enabled ? "suggestions_optin_yes" : "suggestions_optin_no"));
-
- TranslateAnimation slideAnimation = new TranslateAnimation(0, mSuggestionsOptInPrompt.getWidth(), 0, 0);
- slideAnimation.setDuration(ANIMATION_DURATION);
- slideAnimation.setInterpolator(new AccelerateInterpolator());
- slideAnimation.setFillAfter(true);
- final View prompt = mSuggestionsOptInPrompt.findViewById(R.id.prompt);
-
- TranslateAnimation shrinkAnimation = new TranslateAnimation(0, 0, 0, -1 * mSuggestionsOptInPrompt.getHeight());
- shrinkAnimation.setDuration(ANIMATION_DURATION);
- shrinkAnimation.setFillAfter(true);
- shrinkAnimation.setStartOffset(slideAnimation.getDuration());
- shrinkAnimation.setAnimationListener(new Animation.AnimationListener() {
- @Override
- public void onAnimationStart(Animation a) {
- // Increase the height of the view so a gap isn't shown during animation
- mView.getLayoutParams().height = mView.getHeight() +
- mSuggestionsOptInPrompt.getHeight();
- mView.requestLayout();
- }
-
- @Override
- public void onAnimationRepeat(Animation a) {}
-
- @Override
- public void onAnimationEnd(Animation a) {
- // Removing the view immediately results in a NPE in
- // dispatchDraw(), possibly because this callback executes
- // before drawing is finished. Posting this as a Runnable fixes
- // the issue.
- mView.post(new Runnable() {
- @Override
- public void run() {
- mView.removeView(mSuggestionsOptInPrompt);
- mList.clearAnimation();
- mSuggestionsOptInPrompt = null;
-
- // Reset the view height
- mView.getLayoutParams().height = LayoutParams.MATCH_PARENT;
-
- // Show search suggestions and update them
- if (enabled) {
- mSuggestionsEnabled = enabled;
- mAnimateSuggestions = true;
- mAdapter.notifyDataSetChanged();
- filterSuggestions();
- }
- }
- });
- }
- });
-
- prompt.startAnimation(slideAnimation);
- mSuggestionsOptInPrompt.startAnimation(shrinkAnimation);
- mList.startAnimation(shrinkAnimation);
- }
-
- private int getPrimaryEngineCount() {
- return mSearchEngines.size() > 0 ? 1 : 0;
- }
-
- private void restartSearchLoader() {
- SearchLoader.restart(getLoaderManager(), LOADER_ID_SEARCH, mCursorLoaderCallbacks, mSearchTerm);
- }
-
- private void initSearchLoader() {
- SearchLoader.init(getLoaderManager(), LOADER_ID_SEARCH, mCursorLoaderCallbacks, mSearchTerm);
- }
-
- public void filter(String searchTerm, AutocompleteHandler handler) {
- if (TextUtils.isEmpty(searchTerm)) {
- return;
- }
-
- final boolean isNewFilter = !TextUtils.equals(mSearchTerm, searchTerm);
-
- mSearchTerm = searchTerm;
- mAutocompleteHandler = handler;
-
- if (mAdapter != null) {
- if (isNewFilter) {
- // The adapter depends on the search term to determine its number
- // of items. Make it we notify the view about it.
- mAdapter.notifyDataSetChanged();
-
- // Restart loaders with the new search term
- restartSearchLoader();
- filterSuggestions();
- } else {
- // The search term hasn't changed, simply reuse any existing
- // loader for the current search term. This will ensure autocompletion
- // is consistently triggered (see bug 933739).
- initSearchLoader();
- }
- }
- }
-
- abstract private static class SuggestionAsyncLoader extends AsyncTaskLoader<ArrayList<String>> {
- protected final String mSearchTerm;
- private ArrayList<String> mSuggestions;
-
- public SuggestionAsyncLoader(Context context, String searchTerm) {
- super(context);
- mSearchTerm = searchTerm;
- }
-
- @Override
- public void deliverResult(ArrayList<String> suggestions) {
- mSuggestions = suggestions;
-
- if (isStarted()) {
- super.deliverResult(mSuggestions);
- }
- }
-
- @Override
- protected void onStartLoading() {
- if (mSuggestions != null) {
- deliverResult(mSuggestions);
- }
-
- if (takeContentChanged() || mSuggestions == null) {
- forceLoad();
- }
- }
-
- @Override
- protected void onStopLoading() {
- cancelLoad();
- }
-
- @Override
- protected void onReset() {
- super.onReset();
-
- onStopLoading();
- mSuggestions = null;
- }
- }
-
- private static class SearchEngineSuggestionAsyncLoader extends SuggestionAsyncLoader {
- private final SuggestClient mSuggestClient;
-
- public SearchEngineSuggestionAsyncLoader(Context context, SuggestClient suggestClient, String searchTerm) {
- super(context, searchTerm);
- mSuggestClient = suggestClient;
- }
-
- @Override
- public ArrayList<String> loadInBackground() {
- return mSuggestClient.query(mSearchTerm);
- }
- }
-
- private static class SearchHistorySuggestionAsyncLoader extends SuggestionAsyncLoader {
- public SearchHistorySuggestionAsyncLoader(Context context, String searchTerm) {
- super(context, searchTerm);
- }
-
- @Override
- public ArrayList<String> loadInBackground() {
- final ContentResolver cr = getContext().getContentResolver();
-
- String[] columns = new String[] { BrowserContract.SearchHistory.QUERY };
- String actualQuery = BrowserContract.SearchHistory.QUERY + " LIKE ?";
- String[] queryArgs = new String[] { '%' + mSearchTerm + '%' };
-
- // For deduplication, the worst case is that all the first NETWORK_SUGGESTION_MAX history suggestions are duplicates
- // of search engine suggestions, and the there is a duplicate for the search term itself. A duplicate of the
- // search term can occur if the user has previously searched for the same thing.
- final int maxSavedSuggestions = NETWORK_SUGGESTION_MAX + 1 + getContext().getResources().getInteger(R.integer.max_saved_suggestions);
-
- final String sortOrderAndLimit = BrowserContract.SearchHistory.DATE + " DESC LIMIT " + maxSavedSuggestions;
- final Cursor result = cr.query(BrowserContract.SearchHistory.CONTENT_URI, columns, actualQuery, queryArgs, sortOrderAndLimit);
-
- if (result == null) {
- return new ArrayList<>();
- }
-
- final ArrayList<String> savedSuggestions = new ArrayList<>();
- try {
- if (result.moveToFirst()) {
- final int searchColumn = result.getColumnIndexOrThrow(BrowserContract.SearchHistory.QUERY);
- do {
- final String savedSearch = result.getString(searchColumn);
- savedSuggestions.add(savedSearch);
- } while (result.moveToNext());
- }
- } finally {
- result.close();
- }
-
- return savedSuggestions;
- }
- }
-
- private class SearchAdapter extends MultiTypeCursorAdapter {
- private static final int ROW_SEARCH = 0;
- private static final int ROW_STANDARD = 1;
- private static final int ROW_SUGGEST = 2;
-
- public SearchAdapter(Context context) {
- super(context, null, new int[] { ROW_STANDARD,
- ROW_SEARCH,
- ROW_SUGGEST },
- new int[] { R.layout.home_item_row,
- R.layout.home_search_item_row,
- R.layout.home_search_item_row });
- }
-
- @Override
- public int getItemViewType(int position) {
- if (position < getPrimaryEngineCount()) {
- if (mSuggestionsEnabled && mSearchEngines.get(position).hasSuggestions()) {
- // Give suggestion views their own type to prevent them from
- // sharing other recycled search result views. Using other
- // recycled views for the suggestion row can break animations
- // (bug 815937).
-
- return ROW_SUGGEST;
- } else {
- return ROW_SEARCH;
- }
- }
-
- return ROW_STANDARD;
- }
-
- @Override
- public boolean isEnabled(int position) {
- // If we're using a gamepad or keyboard, allow the row to be
- // focused so it can pass the focus to its child suggestion views.
- if (!mList.isInTouchMode()) {
- return true;
- }
-
- // If the suggestion row only contains one item (the user-entered
- // query), allow the entire row to be clickable; clicking the row
- // has the same effect as clicking the single suggestion. If the
- // row contains multiple items, clicking the row will do nothing.
-
- if (position < getPrimaryEngineCount()) {
- return !mSearchEngines.get(position).hasSuggestions();
- }
-
- return true;
- }
-
- // Add the search engines to the number of reported results.
- @Override
- public int getCount() {
- final int resultCount = super.getCount();
-
- // Don't show search engines or suggestions if search field is empty
- if (TextUtils.isEmpty(mSearchTerm)) {
- return resultCount;
- }
-
- return resultCount + getPrimaryEngineCount();
- }
-
- @Override
- public void bindView(View view, Context context, int position) {
- final int type = getItemViewType(position);
-
- if (type == ROW_SEARCH || type == ROW_SUGGEST) {
- final SearchEngineRow row = (SearchEngineRow) view;
- row.setOnUrlOpenListener(mUrlOpenListener);
- row.setOnSearchListener(mSearchListener);
- row.setOnEditSuggestionListener(mEditSuggestionListener);
- row.setSearchTerm(mSearchTerm);
-
- final SearchEngine engine = mSearchEngines.get(position);
- final boolean haveSuggestions = (engine.hasSuggestions() || !mSearchHistorySuggestions.isEmpty());
- final boolean animate = (mAnimateSuggestions && haveSuggestions);
- row.updateSuggestions(mSuggestionsEnabled, engine, mSearchHistorySuggestions, animate);
- if (animate) {
- // Only animate suggestions the first time they are shown
- mAnimateSuggestions = false;
- }
- } else {
- // Account for the search engines
- position -= getPrimaryEngineCount();
-
- final Cursor c = getCursor(position);
- final TwoLinePageRow row = (TwoLinePageRow) view;
- row.updateFromCursor(c);
- }
- }
- }
-
- private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- return SearchLoader.createInstance(getActivity(), args);
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
- if (mAdapter != null) {
- mAdapter.swapCursor(c);
-
- // We should handle autocompletion based on the search term
- // associated with the loader that has just provided
- // the results.
- SearchCursorLoader searchLoader = (SearchCursorLoader) loader;
- handleAutocomplete(searchLoader.getSearchTerm(), c);
- }
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- if (mAdapter != null) {
- mAdapter.swapCursor(null);
- }
- }
- }
-
- private class SearchEngineSuggestionLoaderCallbacks implements LoaderCallbacks<ArrayList<String>> {
- @Override
- public Loader<ArrayList<String>> onCreateLoader(int id, Bundle args) {
- // mSuggestClient is set to null in onDestroyView(), so using it
- // safely here relies on the fact that onCreateLoader() is called
- // synchronously in restartLoader().
- return new SearchEngineSuggestionAsyncLoader(getActivity(), mSuggestClient, mSearchTerm);
- }
-
- @Override
- public void onLoadFinished(Loader<ArrayList<String>> loader, ArrayList<String> suggestions) {
- setSuggestions(suggestions);
- }
-
- @Override
- public void onLoaderReset(Loader<ArrayList<String>> loader) {
- setSuggestions(new ArrayList<String>());
- }
- }
-
- private class SearchHistorySuggestionLoaderCallbacks implements LoaderCallbacks<ArrayList<String>> {
- @Override
- public Loader<ArrayList<String>> onCreateLoader(int id, Bundle args) {
- // mSuggestClient is set to null in onDestroyView(), so using it
- // safely here relies on the fact that onCreateLoader() is called
- // synchronously in restartLoader().
- return new SearchHistorySuggestionAsyncLoader(getActivity(), mSearchTerm);
- }
-
- @Override
- public void onLoadFinished(Loader<ArrayList<String>> loader, ArrayList<String> suggestions) {
- setSavedSuggestions(suggestions);
- }
-
- @Override
- public void onLoaderReset(Loader<ArrayList<String>> loader) {
- setSavedSuggestions(new ArrayList<String>());
- }
- }
-
- private static class ListSelectionListener implements View.OnFocusChangeListener,
- AdapterView.OnItemSelectedListener {
- private SearchEngineRow mSelectedEngineRow;
-
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus) {
- View selectedRow = ((ListView) v).getSelectedView();
- if (selectedRow != null) {
- selectRow(selectedRow);
- }
- } else {
- deselectRow();
- }
- }
-
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- deselectRow();
- selectRow(view);
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> parent) {
- deselectRow();
- }
-
- private void selectRow(View row) {
- if (row instanceof SearchEngineRow) {
- mSelectedEngineRow = (SearchEngineRow) row;
- mSelectedEngineRow.onSelected();
- }
- }
-
- private void deselectRow() {
- if (mSelectedEngineRow != null) {
- mSelectedEngineRow.onDeselected();
- mSelectedEngineRow = null;
- }
- }
- }
-
- /**
- * HomeSearchListView is a list view for displaying search engine results on the awesome screen.
- */
- public static class HomeSearchListView extends HomeListView {
- public HomeSearchListView(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.homeListViewStyle);
- }
-
- public HomeSearchListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- // Dismiss the soft keyboard.
- requestFocus();
- }
-
- return super.onTouchEvent(event);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/ClientsAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/ClientsAdapter.java
deleted file mode 100644
index f288a2745..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/ClientsAdapter.java
+++ /dev/null
@@ -1,373 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.support.annotation.UiThread;
-import android.support.v4.util.Pair;
-import android.support.v7.widget.RecyclerView;
-import android.text.format.DateUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.RemoteClient;
-import org.mozilla.gecko.db.RemoteTab;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-import static org.mozilla.gecko.home.CombinedHistoryItem.ItemType.*;
-
-public class ClientsAdapter extends RecyclerView.Adapter<CombinedHistoryItem> implements CombinedHistoryRecyclerView.AdapterContextMenuBuilder {
- public static final String LOGTAG = "GeckoClientsAdapter";
-
- /**
- * If a device claims to have synced before this date, we will assume it has never synced.
- */
- public static final Date EARLIEST_VALID_SYNCED_DATE;
- static {
- final Calendar c = GregorianCalendar.getInstance();
- c.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
- EARLIEST_VALID_SYNCED_DATE = c.getTime();
- }
-
- List<Pair<String, Integer>> adapterList = new LinkedList<>();
-
- // List of hidden remote clients.
- // Only accessed from the UI thread.
- protected final List<RemoteClient> hiddenClients = new ArrayList<>();
- private Map<String, RemoteClient> visibleClients = new HashMap<>();
-
- // Maintain group collapsed and hidden state. Only accessed from the UI thread.
- protected static RemoteTabsExpandableListState sState;
-
- private final Context context;
-
- public ClientsAdapter(Context context) {
- this.context = context;
-
- // This races when multiple Fragments are created. That's okay: one
- // will win, and thereafter, all will be okay. If we create and then
- // drop an instance the shared SharedPreferences backing all the
- // instances will maintain the state for us. Since everything happens on
- // the UI thread, this doesn't even need to be volatile.
- if (sState == null) {
- sState = new RemoteTabsExpandableListState(GeckoSharedPrefs.forProfile(context));
- }
-
- this.setHasStableIds(true);
- }
-
- @Override
- public CombinedHistoryItem onCreateViewHolder(ViewGroup parent, int viewType) {
- final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
- final View view;
-
- final CombinedHistoryItem.ItemType itemType = CombinedHistoryItem.ItemType.viewTypeToItemType(viewType);
-
- switch (itemType) {
- case NAVIGATION_BACK:
- view = inflater.inflate(R.layout.home_combined_back_item, parent, false);
- return new CombinedHistoryItem.HistoryItem(view);
-
- case CLIENT:
- view = inflater.inflate(R.layout.home_remote_tabs_group, parent, false);
- return new CombinedHistoryItem.ClientItem(view);
-
- case CHILD:
- view = inflater.inflate(R.layout.home_item_row, parent, false);
- return new CombinedHistoryItem.HistoryItem(view);
-
- case HIDDEN_DEVICES:
- view = inflater.inflate(R.layout.home_remote_tabs_hidden_devices, parent, false);
- return new CombinedHistoryItem.BasicItem(view);
- }
- return null;
- }
-
- @Override
- public void onBindViewHolder(CombinedHistoryItem holder, final int position) {
- final CombinedHistoryItem.ItemType itemType = getItemTypeForPosition(position);
-
- switch (itemType) {
- case CLIENT:
- final CombinedHistoryItem.ClientItem clientItem = (CombinedHistoryItem.ClientItem) holder;
- final String clientGuid = adapterList.get(position).first;
- final RemoteClient client = visibleClients.get(clientGuid);
- clientItem.bind(context, client, sState.isClientCollapsed(clientGuid));
- break;
-
- case CHILD:
- final Pair<String, Integer> pair = adapterList.get(position);
- RemoteTab remoteTab = visibleClients.get(pair.first).tabs.get(pair.second);
- ((CombinedHistoryItem.HistoryItem) holder).bind(remoteTab);
- break;
-
- case HIDDEN_DEVICES:
- final String hiddenDevicesLabel = context.getResources().getString(R.string.home_remote_tabs_many_hidden_devices, hiddenClients.size());
- ((TextView) holder.itemView).setText(hiddenDevicesLabel);
- break;
- }
- }
-
- @Override
- public int getItemCount () {
- return adapterList.size();
- }
-
- private CombinedHistoryItem.ItemType getItemTypeForPosition(int position) {
- if (position == 0) {
- return NAVIGATION_BACK;
- }
-
- final Pair<String, Integer> pair = adapterList.get(position);
- if (pair == null) {
- return HIDDEN_DEVICES;
- } else if (pair.second == -1) {
- return CLIENT;
- } else {
- return CHILD;
- }
- }
-
- @Override
- public int getItemViewType(int position) {
- return CombinedHistoryItem.ItemType.itemTypeToViewType(getItemTypeForPosition(position));
- }
-
- @Override
- public long getItemId(int position) {
- // RecyclerView.NO_ID is -1, so start our hard-coded IDs at -2.
- final int NAVIGATION_BACK_ID = -2;
- final int HIDDEN_DEVICES_ID = -3;
-
- final String clientGuid;
- // adapterList is a list of tuples (clientGuid, tabId).
- final Pair<String, Integer> pair = adapterList.get(position);
-
- switch (getItemTypeForPosition(position)) {
- case NAVIGATION_BACK:
- return NAVIGATION_BACK_ID;
-
- case HIDDEN_DEVICES:
- return HIDDEN_DEVICES_ID;
-
- // For Clients, return hashCode of their GUIDs.
- case CLIENT:
- clientGuid = pair.first;
- return clientGuid.hashCode();
-
- // For Tabs, return hashCode of their URLs.
- case CHILD:
- clientGuid = pair.first;
- final Integer tabId = pair.second;
-
- final RemoteClient remoteClient = visibleClients.get(clientGuid);
- if (remoteClient == null) {
- return RecyclerView.NO_ID;
- }
-
- final RemoteTab remoteTab = remoteClient.tabs.get(tabId);
- if (remoteTab == null) {
- return RecyclerView.NO_ID;
- }
-
- return remoteTab.url.hashCode();
-
- default:
- throw new IllegalStateException("Unexpected Home Panel item type");
- }
- }
-
- public int getClientsCount() {
- return hiddenClients.size() + visibleClients.size();
- }
-
- @UiThread
- public void setClients(List<RemoteClient> clients) {
- adapterList.clear();
- adapterList.add(null);
-
- hiddenClients.clear();
- visibleClients.clear();
-
- for (RemoteClient client : clients) {
- final String guid = client.guid;
- if (sState.isClientHidden(guid)) {
- hiddenClients.add(client);
- } else {
- visibleClients.put(guid, client);
- adapterList.addAll(getVisibleItems(client));
- }
- }
-
- // Add item for unhiding clients.
- if (!hiddenClients.isEmpty()) {
- adapterList.add(null);
- }
-
- notifyDataSetChanged();
- }
-
- private static List<Pair<String, Integer>> getVisibleItems(RemoteClient client) {
- List<Pair<String, Integer>> list = new LinkedList<>();
- final String guid = client.guid;
- list.add(new Pair<>(guid, -1));
- if (!sState.isClientCollapsed(client.guid)) {
- for (int i = 0; i < client.tabs.size(); i++) {
- list.add(new Pair<>(guid, i));
- }
- }
- return list;
- }
-
- public List<RemoteClient> getHiddenClients() {
- return hiddenClients;
- }
-
- public void toggleClient(int position) {
- final Pair<String, Integer> pair = adapterList.get(position);
- if (pair.second != -1) {
- return;
- }
-
- final String clientGuid = pair.first;
- final RemoteClient client = visibleClients.get(clientGuid);
-
- final boolean isCollapsed = sState.isClientCollapsed(clientGuid);
-
- sState.setClientCollapsed(clientGuid, !isCollapsed);
- notifyItemChanged(position);
-
- if (isCollapsed) {
- for (int i = client.tabs.size() - 1; i > -1; i--) {
- // Insert child tabs at the index right after the client item that was clicked.
- adapterList.add(position + 1, new Pair<>(clientGuid, i));
- }
- notifyItemRangeInserted(position + 1, client.tabs.size());
- } else {
- int i = client.tabs.size();
- while (i > 0) {
- adapterList.remove(position + 1);
- i--;
- }
- notifyItemRangeRemoved(position + 1, client.tabs.size());
- }
- }
-
- public void unhideClients(List<RemoteClient> selectedClients) {
- final int numClients = selectedClients.size();
- if (numClients == 0) {
- return;
- }
-
- final int insertionIndex = adapterList.size() - 1;
- int itemCount = numClients;
-
- for (RemoteClient client : selectedClients) {
- final String clientGuid = client.guid;
-
- sState.setClientHidden(clientGuid, false);
- hiddenClients.remove(client);
-
- visibleClients.put(clientGuid, client);
- sState.setClientCollapsed(clientGuid, false);
- adapterList.addAll(adapterList.size() - 1, getVisibleItems(client));
-
- itemCount += client.tabs.size();
- }
-
- notifyItemRangeInserted(insertionIndex, itemCount);
-
- final int hiddenDevicesIndex = adapterList.size() - 1;
- if (hiddenClients.isEmpty()) {
- // No more hidden clients, remove "unhide" item.
- adapterList.remove(hiddenDevicesIndex);
- notifyItemRemoved(hiddenDevicesIndex);
- } else {
- // Update "hidden clients" item because number of hidden clients changed.
- notifyItemChanged(hiddenDevicesIndex);
- }
- }
-
- public void removeItem(int position) {
- final CombinedHistoryItem.ItemType itemType = getItemTypeForPosition(position);
- switch (itemType) {
- case CLIENT:
- final String clientGuid = adapterList.get(position).first;
- final RemoteClient client = visibleClients.remove(clientGuid);
- final boolean hadHiddenClients = !hiddenClients.isEmpty();
-
- int removeCount = sState.isClientCollapsed(clientGuid) ? 1 : client.tabs.size() + 1;
- int c = removeCount;
- while (c > 0) {
- adapterList.remove(position);
- c--;
- }
- notifyItemRangeRemoved(position, removeCount);
-
- sState.setClientHidden(clientGuid, true);
- hiddenClients.add(client);
-
- if (!hadHiddenClients) {
- // Add item for unhiding clients;
- adapterList.add(null);
- notifyItemInserted(adapterList.size() - 1);
- } else {
- // Update "hidden clients" item because number of hidden clients changed.
- notifyItemChanged(adapterList.size() - 1);
- }
- break;
- }
- }
-
- @Override
- public HomeContextMenuInfo makeContextMenuInfoFromPosition(View view, int position) {
- final CombinedHistoryItem.ItemType itemType = getItemTypeForPosition(position);
- HomeContextMenuInfo info;
- final Pair<String, Integer> pair = adapterList.get(position);
- switch (itemType) {
- case CHILD:
- info = new HomeContextMenuInfo(view, position, -1);
- return populateChildInfoFromTab(info, visibleClients.get(pair.first).tabs.get(pair.second));
-
- case CLIENT:
- info = new CombinedHistoryPanel.RemoteTabsClientContextMenuInfo(view, position, -1, visibleClients.get(pair.first));
- return info;
- }
- return null;
- }
-
- protected static HomeContextMenuInfo populateChildInfoFromTab(HomeContextMenuInfo info, RemoteTab tab) {
- info.url = tab.url;
- info.title = tab.title;
- return info;
- }
-
- /**
- * Return a relative "Last synced" time span for the given tab record.
- *
- * @param now local time.
- * @param time to format string for.
- * @return string describing time span
- */
- public static String getLastSyncedString(Context context, long now, long time) {
- if (new Date(time).before(EARLIEST_VALID_SYNCED_DATE)) {
- return context.getString(R.string.remote_tabs_never_synced);
- }
- final CharSequence relativeTimeSpanString = DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS);
- return context.getResources().getString(R.string.remote_tabs_last_synced, relativeTimeSpanString);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java
deleted file mode 100644
index 402ed26e7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryAdapter.java
+++ /dev/null
@@ -1,433 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home;
-
-import android.content.res.Resources;
-import android.support.annotation.UiThread;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-
-import android.database.Cursor;
-import android.util.SparseArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.util.ThreadUtils;
-
-public class CombinedHistoryAdapter extends RecyclerView.Adapter<CombinedHistoryItem> implements CombinedHistoryRecyclerView.AdapterContextMenuBuilder {
- private static final int RECENT_TABS_SMARTFOLDER_INDEX = 0;
-
- // Array for the time ranges in milliseconds covered by each section.
- static final HistorySectionsHelper.SectionDateRange[] sectionDateRangeArray = new HistorySectionsHelper.SectionDateRange[SectionHeader.values().length];
-
- // Semantic names for the time covered by each section
- public enum SectionHeader {
- TODAY,
- YESTERDAY,
- WEEK,
- THIS_MONTH,
- MONTH_AGO,
- TWO_MONTHS_AGO,
- THREE_MONTHS_AGO,
- FOUR_MONTHS_AGO,
- FIVE_MONTHS_AGO,
- OLDER_THAN_SIX_MONTHS
- }
-
- private HomeFragment.PanelStateChangeListener panelStateChangeListener;
-
- private Cursor historyCursor;
- private DevicesUpdateHandler devicesUpdateHandler;
- private int deviceCount = 0;
- private RecentTabsUpdateHandler recentTabsUpdateHandler;
- private int recentTabsCount = 0;
-
- private LinearLayoutManager linearLayoutManager; // Only used on the UI thread, so no need to be volatile.
-
- // We use a sparse array to store each section header's position in the panel [more cheaply than a HashMap].
- private final SparseArray<SectionHeader> sectionHeaders;
-
- public CombinedHistoryAdapter(Resources resources, int cachedRecentTabsCount) {
- super();
- recentTabsCount = cachedRecentTabsCount;
- sectionHeaders = new SparseArray<>();
- HistorySectionsHelper.updateRecentSectionOffset(resources, sectionDateRangeArray);
- this.setHasStableIds(true);
- }
-
- public void setPanelStateChangeListener(
- HomeFragment.PanelStateChangeListener panelStateChangeListener) {
- this.panelStateChangeListener = panelStateChangeListener;
- }
-
- @UiThread
- public void setLinearLayoutManager(LinearLayoutManager linearLayoutManager) {
- this.linearLayoutManager = linearLayoutManager;
- }
-
- public void setHistory(Cursor history) {
- historyCursor = history;
- populateSectionHeaders(historyCursor, sectionHeaders);
- notifyDataSetChanged();
- }
-
- public interface DevicesUpdateHandler {
- void onDeviceCountUpdated(int count);
- }
-
- public DevicesUpdateHandler getDeviceUpdateHandler() {
- if (devicesUpdateHandler == null) {
- devicesUpdateHandler = new DevicesUpdateHandler() {
- @Override
- public void onDeviceCountUpdated(int count) {
- deviceCount = count;
- notifyItemChanged(getSyncedDevicesSmartFolderIndex());
- }
- };
- }
- return devicesUpdateHandler;
- }
-
- public interface RecentTabsUpdateHandler {
- void onRecentTabsCountUpdated(int count, boolean countReliable);
- }
-
- public RecentTabsUpdateHandler getRecentTabsUpdateHandler() {
- if (recentTabsUpdateHandler != null) {
- return recentTabsUpdateHandler;
- }
-
- recentTabsUpdateHandler = new RecentTabsUpdateHandler() {
- @Override
- public void onRecentTabsCountUpdated(final int count, final boolean countReliable) {
- // Now that other items can move around depending on the visibility of the
- // Recent Tabs folder, only update the recentTabsCount on the UI thread.
- ThreadUtils.postToUiThread(new Runnable() {
- @UiThread
- @Override
- public void run() {
- if (!countReliable && count <= recentTabsCount) {
- // The final tab count (where countReliable = true) is normally >= than
- // previous values with countReliable = false. Hence we only want to
- // update the displayed tab count with a preliminary value if it's larger
- // than the previous count, so as to avoid the displayed count jumping
- // downwards and then back up, as well as unnecessary folder animations.
- return;
- }
-
- final boolean prevFolderVisibility = isRecentTabsFolderVisible();
- recentTabsCount = count;
- final boolean folderVisible = isRecentTabsFolderVisible();
-
- if (prevFolderVisibility == folderVisible) {
- if (prevFolderVisibility) {
- notifyItemChanged(RECENT_TABS_SMARTFOLDER_INDEX);
- }
- return;
- }
-
- // If the Recent Tabs smart folder has become hidden/unhidden,
- // we need to recalculate the history section header positions.
- populateSectionHeaders(historyCursor, sectionHeaders);
-
- if (folderVisible) {
- int scrollPos = -1;
- if (linearLayoutManager != null) {
- scrollPos = linearLayoutManager.findFirstCompletelyVisibleItemPosition();
- }
-
- notifyItemInserted(RECENT_TABS_SMARTFOLDER_INDEX);
- // If the list exceeds the display height and we want to show the new
- // item inserted at position 0, we need to scroll up manually
- // (see https://code.google.com/p/android/issues/detail?id=174227#c2).
- // However we only do this if our current scroll position is at the
- // top of the list.
- if (linearLayoutManager != null && scrollPos == 0) {
- linearLayoutManager.scrollToPosition(0);
- }
- } else {
- notifyItemRemoved(RECENT_TABS_SMARTFOLDER_INDEX);
- }
-
- if (countReliable && panelStateChangeListener != null) {
- panelStateChangeListener.setCachedRecentTabsCount(recentTabsCount);
- }
- }
- });
- }
- };
- return recentTabsUpdateHandler;
- }
-
- @UiThread
- private boolean isRecentTabsFolderVisible() {
- return recentTabsCount > 0;
- }
-
- @UiThread
- // Number of smart folders for determining practical empty state.
- public int getNumVisibleSmartFolders() {
- int visibleFolders = 1; // Synced devices folder is always visible.
-
- if (isRecentTabsFolderVisible()) {
- visibleFolders += 1;
- }
-
- return visibleFolders;
- }
-
- @UiThread
- private int getSyncedDevicesSmartFolderIndex() {
- return isRecentTabsFolderVisible() ?
- RECENT_TABS_SMARTFOLDER_INDEX + 1 :
- RECENT_TABS_SMARTFOLDER_INDEX;
- }
-
- @Override
- public CombinedHistoryItem onCreateViewHolder(ViewGroup viewGroup, int viewType) {
- final LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
- final View view;
-
- final CombinedHistoryItem.ItemType itemType = CombinedHistoryItem.ItemType.viewTypeToItemType(viewType);
-
- switch (itemType) {
- case RECENT_TABS:
- case SYNCED_DEVICES:
- view = inflater.inflate(R.layout.home_smartfolder, viewGroup, false);
- return new CombinedHistoryItem.SmartFolder(view);
-
- case SECTION_HEADER:
- view = inflater.inflate(R.layout.home_header_row, viewGroup, false);
- return new CombinedHistoryItem.BasicItem(view);
-
- case HISTORY:
- view = inflater.inflate(R.layout.home_item_row, viewGroup, false);
- return new CombinedHistoryItem.HistoryItem(view);
- default:
- throw new IllegalArgumentException("Unexpected Home Panel item type");
- }
- }
-
- @Override
- public void onBindViewHolder(CombinedHistoryItem viewHolder, int position) {
- final CombinedHistoryItem.ItemType itemType = getItemTypeForPosition(position);
- final int localPosition = transformAdapterPositionForDataStructure(itemType, position);
-
- switch (itemType) {
- case RECENT_TABS:
- ((CombinedHistoryItem.SmartFolder) viewHolder).bind(R.drawable.icon_recent, R.string.home_closed_tabs_title2, R.string.home_closed_tabs_one, R.string.home_closed_tabs_number, recentTabsCount);
- break;
-
- case SYNCED_DEVICES:
- ((CombinedHistoryItem.SmartFolder) viewHolder).bind(R.drawable.cloud, R.string.home_synced_devices_smartfolder, R.string.home_synced_devices_one, R.string.home_synced_devices_number, deviceCount);
- break;
-
- case SECTION_HEADER:
- ((TextView) viewHolder.itemView).setText(getSectionHeaderTitle(sectionHeaders.get(localPosition)));
- break;
-
- case HISTORY:
- if (historyCursor == null || !historyCursor.moveToPosition(localPosition)) {
- throw new IllegalStateException("Couldn't move cursor to position " + localPosition);
- }
- ((CombinedHistoryItem.HistoryItem) viewHolder).bind(historyCursor);
- break;
- }
- }
-
- /**
- * Transform an adapter position to the position for the data structure backing the item type.
- *
- * The type is not strictly necessary and could be fetched from <code>getItemTypeForPosition</code>,
- * but is used for explicitness.
- *
- * @param type ItemType of the item
- * @param position position in the adapter
- * @return position of the item in the data structure
- */
- @UiThread
- private int transformAdapterPositionForDataStructure(CombinedHistoryItem.ItemType type, int position) {
- if (type == CombinedHistoryItem.ItemType.SECTION_HEADER) {
- return position;
- } else if (type == CombinedHistoryItem.ItemType.HISTORY) {
- return position - getHeadersBefore(position) - getNumVisibleSmartFolders();
- } else {
- return position;
- }
- }
-
- @UiThread
- private CombinedHistoryItem.ItemType getItemTypeForPosition(int position) {
- if (position == RECENT_TABS_SMARTFOLDER_INDEX && isRecentTabsFolderVisible()) {
- return CombinedHistoryItem.ItemType.RECENT_TABS;
- }
- if (position == getSyncedDevicesSmartFolderIndex()) {
- return CombinedHistoryItem.ItemType.SYNCED_DEVICES;
- }
- final int sectionPosition = transformAdapterPositionForDataStructure(CombinedHistoryItem.ItemType.SECTION_HEADER, position);
- if (sectionHeaders.get(sectionPosition) != null) {
- return CombinedHistoryItem.ItemType.SECTION_HEADER;
- }
- return CombinedHistoryItem.ItemType.HISTORY;
- }
-
- @UiThread
- @Override
- public int getItemViewType(int position) {
- return CombinedHistoryItem.ItemType.itemTypeToViewType(getItemTypeForPosition(position));
- }
-
- @UiThread
- @Override
- public int getItemCount() {
- final int historySize = historyCursor == null ? 0 : historyCursor.getCount();
- return historySize + sectionHeaders.size() + getNumVisibleSmartFolders();
- }
-
- /**
- * Returns stable ID for each position. Data behind historyCursor is a sorted Combined view.
- *
- * @param position view item position for which to generate a stable ID
- * @return stable ID for given position
- */
- @UiThread
- @Override
- public long getItemId(int position) {
- // Two randomly selected large primes used to generate non-clashing IDs.
- final long PRIME_BOOKMARKS = 32416189867L;
- final long PRIME_SECTION_HEADERS = 32416187737L;
-
- // RecyclerView.NO_ID is -1, so let's start from -2 for our hard-coded IDs.
- final int RECENT_TABS_ID = -2;
- final int SYNCED_DEVICES_ID = -3;
-
- switch (getItemTypeForPosition(position)) {
- case RECENT_TABS:
- return RECENT_TABS_ID;
- case SYNCED_DEVICES:
- return SYNCED_DEVICES_ID;
- case SECTION_HEADER:
- // We might have multiple section headers, so we try get unique IDs for them.
- return position * PRIME_SECTION_HEADERS;
- case HISTORY:
- final int historyPosition = transformAdapterPositionForDataStructure(
- CombinedHistoryItem.ItemType.HISTORY, position);
- if (!historyCursor.moveToPosition(historyPosition)) {
- return RecyclerView.NO_ID;
- }
-
- final int historyIdCol = historyCursor.getColumnIndexOrThrow(BrowserContract.Combined.HISTORY_ID);
- final long historyId = historyCursor.getLong(historyIdCol);
-
- if (historyId != -1) {
- return historyId;
- }
-
- final int bookmarkIdCol = historyCursor.getColumnIndexOrThrow(BrowserContract.Combined.BOOKMARK_ID);
- final long bookmarkId = historyCursor.getLong(bookmarkIdCol);
-
- // Avoid clashing with historyId.
- return bookmarkId * PRIME_BOOKMARKS;
- default:
- throw new IllegalStateException("Unexpected Home Panel item type");
- }
- }
-
- /**
- * Add only the SectionHeaders that have history items within their range to a SparseArray, where the
- * array index is the position of the header in the history-only (no clients) ordering.
- * @param c data Cursor
- * @param sparseArray SparseArray to populate
- */
- @UiThread
- private void populateSectionHeaders(Cursor c, SparseArray<SectionHeader> sparseArray) {
- ThreadUtils.assertOnUiThread();
-
- sparseArray.clear();
-
- if (c == null || !c.moveToFirst()) {
- return;
- }
-
- SectionHeader section = null;
-
- do {
- final int historyPosition = c.getPosition();
- final long visitTime = c.getLong(c.getColumnIndexOrThrow(BrowserContract.History.DATE_LAST_VISITED));
- final SectionHeader itemSection = getSectionFromTime(visitTime);
-
- if (section != itemSection) {
- section = itemSection;
- sparseArray.append(historyPosition + sparseArray.size() + getNumVisibleSmartFolders(), section);
- }
-
- if (section == SectionHeader.OLDER_THAN_SIX_MONTHS) {
- break;
- }
- } while (c.moveToNext());
- }
-
- private static String getSectionHeaderTitle(SectionHeader section) {
- return sectionDateRangeArray[section.ordinal()].displayName;
- }
-
- private static SectionHeader getSectionFromTime(long time) {
- for (int i = 0; i < SectionHeader.OLDER_THAN_SIX_MONTHS.ordinal(); i++) {
- if (time > sectionDateRangeArray[i].start) {
- return SectionHeader.values()[i];
- }
- }
-
- return SectionHeader.OLDER_THAN_SIX_MONTHS;
- }
-
- /**
- * Returns the number of section headers before the given history item at the adapter position.
- * @param position position in the adapter
- */
- private int getHeadersBefore(int position) {
- // Skip the first header case because there will always be a header.
- for (int i = 1; i < sectionHeaders.size(); i++) {
- // If the position of the header is greater than the history position,
- // return the number of headers tested.
- if (sectionHeaders.keyAt(i) > position) {
- return i;
- }
- }
- return sectionHeaders.size();
- }
-
- @Override
- public HomeContextMenuInfo makeContextMenuInfoFromPosition(View view, int position) {
- final CombinedHistoryItem.ItemType itemType = getItemTypeForPosition(position);
- if (itemType == CombinedHistoryItem.ItemType.HISTORY) {
- final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, -1);
-
- historyCursor.moveToPosition(transformAdapterPositionForDataStructure(CombinedHistoryItem.ItemType.HISTORY, position));
- return populateHistoryInfoFromCursor(info, historyCursor);
- }
- return null;
- }
-
- protected static HomeContextMenuInfo populateHistoryInfoFromCursor(HomeContextMenuInfo info, Cursor cursor) {
- info.url = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
- info.title = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.TITLE));
- info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(BrowserContract.Combined.HISTORY_ID));
- info.itemType = HomeContextMenuInfo.RemoveItemType.HISTORY;
- final int bookmarkIdCol = cursor.getColumnIndexOrThrow(BrowserContract.Combined.BOOKMARK_ID);
- if (cursor.isNull(bookmarkIdCol)) {
- // If this is a combined cursor, we may get a history item without a
- // bookmark, in which case the bookmarks ID column value will be null.
- info.bookmarkId = -1;
- } else {
- info.bookmarkId = cursor.getInt(bookmarkIdCol);
- }
- return info;
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryItem.java b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryItem.java
deleted file mode 100644
index a2c1b72c2..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryItem.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v4.content.ContextCompat;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.RemoteClient;
-import org.mozilla.gecko.db.RemoteTab;
-import org.mozilla.gecko.home.RecentTabsAdapter.ClosedTab;
-
-public abstract class CombinedHistoryItem extends RecyclerView.ViewHolder {
- private static final String LOGTAG = "CombinedHistoryItem";
-
- public CombinedHistoryItem(View view) {
- super(view);
- }
-
- public enum ItemType {
- CLIENT, HIDDEN_DEVICES, SECTION_HEADER, HISTORY, NAVIGATION_BACK, CHILD, SYNCED_DEVICES,
- RECENT_TABS, CLOSED_TAB;
-
- public static ItemType viewTypeToItemType(int viewType) {
- if (viewType >= ItemType.values().length) {
- Log.e(LOGTAG, "No corresponding ItemType!");
- }
- return ItemType.values()[viewType];
- }
-
- public static int itemTypeToViewType(ItemType itemType) {
- return itemType.ordinal();
- }
- }
-
- public static class BasicItem extends CombinedHistoryItem {
- public BasicItem(View view) {
- super(view);
- }
- }
-
- public static class SmartFolder extends CombinedHistoryItem {
- final Context context;
- final ImageView icon;
- final TextView title;
- final TextView subtext;
-
- public SmartFolder(View view) {
- super(view);
- context = view.getContext();
-
- icon = (ImageView) view.findViewById(R.id.device_type);
- title = (TextView) view.findViewById(R.id.title);
- subtext = (TextView) view.findViewById(R.id.subtext);
- }
-
- public void bind(int drawableRes, int titleRes, int singleDeviceRes, int multiDeviceRes, int numDevices) {
- icon.setImageResource(drawableRes);
- title.setText(titleRes);
- final String subtitle = numDevices == 1 ? context.getString(singleDeviceRes) : context.getString(multiDeviceRes, numDevices);
- subtext.setText(subtitle);
- }
- }
-
- public static class HistoryItem extends CombinedHistoryItem {
- public HistoryItem(View view) {
- super(view);
- }
-
- public void bind(Cursor historyCursor) {
- final TwoLinePageRow pageRow = (TwoLinePageRow) this.itemView;
- pageRow.setShowIcons(true);
- pageRow.updateFromCursor(historyCursor);
- }
-
- public void bind(RemoteTab remoteTab) {
- final TwoLinePageRow childPageRow = (TwoLinePageRow) this.itemView;
- childPageRow.setShowIcons(true);
- childPageRow.update(remoteTab.title, remoteTab.url);
- }
-
- public void bind(ClosedTab closedTab) {
- final TwoLinePageRow childPageRow = (TwoLinePageRow) this.itemView;
- childPageRow.setShowIcons(false);
- childPageRow.update(closedTab.title, closedTab.url);
- }
- }
-
- public static class ClientItem extends CombinedHistoryItem {
- final TextView nameView;
- final ImageView deviceTypeView;
- final TextView lastModifiedView;
- final ImageView deviceExpanded;
-
- public ClientItem(View view) {
- super(view);
- nameView = (TextView) view.findViewById(R.id.client);
- deviceTypeView = (ImageView) view.findViewById(R.id.device_type);
- lastModifiedView = (TextView) view.findViewById(R.id.last_synced);
- deviceExpanded = (ImageView) view.findViewById(R.id.device_expanded);
- }
-
- public void bind(Context context, RemoteClient client, boolean isCollapsed) {
- this.nameView.setText(client.name);
- final long now = System.currentTimeMillis();
- this.lastModifiedView.setText(ClientsAdapter.getLastSyncedString(context, now, client.lastModified));
-
- if (client.isDesktop()) {
- deviceTypeView.setImageResource(isCollapsed ? R.drawable.sync_desktop_inactive : R.drawable.sync_desktop);
- } else {
- deviceTypeView.setImageResource(isCollapsed ? R.drawable.sync_mobile_inactive : R.drawable.sync_mobile);
- }
-
- nameView.setTextColor(ContextCompat.getColor(context, isCollapsed ? R.color.tabs_tray_icon_grey : R.color.placeholder_active_grey));
- if (client.tabs.size() > 0) {
- deviceExpanded.setImageResource(isCollapsed ? R.drawable.home_group_collapsed : R.drawable.arrow_down);
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
deleted file mode 100644
index c9afecd63..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryPanel.java
+++ /dev/null
@@ -1,697 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.accounts.Account;
-import android.app.AlertDialog;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.support.annotation.UiThread;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.Loader;
-import android.support.v4.widget.SwipeRefreshLayout;
-import android.support.v7.widget.DefaultItemAnimator;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.text.SpannableStringBuilder;
-import android.text.TextPaint;
-import android.text.method.LinkMovementMethod;
-import android.text.style.ClickableSpan;
-import android.text.style.UnderlineSpan;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.LayoutInflater;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.RemoteClientsDialogFragment;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.SyncStatusListener;
-import org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener.PanelLevel;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.RemoteClient;
-import org.mozilla.gecko.restrictions.Restrictable;
-import org.mozilla.gecko.widget.HistoryDividerItemDecoration;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import static org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener.PanelLevel.PARENT;
-import static org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener.PanelLevel.CHILD_SYNC;
-import static org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener.PanelLevel.CHILD_RECENT_TABS;
-
-public class CombinedHistoryPanel extends HomeFragment implements RemoteClientsDialogFragment.RemoteClientsListener {
- private static final String LOGTAG = "GeckoCombinedHistoryPnl";
-
- private static final String[] STAGES_TO_SYNC_ON_REFRESH = new String[] { "clients", "tabs" };
- private final int LOADER_ID_HISTORY = 0;
- private final int LOADER_ID_REMOTE = 1;
-
- // String placeholders to mark formatting.
- private final static String FORMAT_S1 = "%1$s";
- private final static String FORMAT_S2 = "%2$s";
-
- private CombinedHistoryRecyclerView mRecyclerView;
- private CombinedHistoryAdapter mHistoryAdapter;
- private ClientsAdapter mClientsAdapter;
- private RecentTabsAdapter mRecentTabsAdapter;
- private CursorLoaderCallbacks mCursorLoaderCallbacks;
-
- private Bundle mSavedRestoreBundle;
-
- private PanelLevel mPanelLevel;
- private Button mPanelFooterButton;
-
- private PanelStateUpdateHandler mPanelStateUpdateHandler;
-
- // Child refresh layout view.
- protected SwipeRefreshLayout mRefreshLayout;
-
- // Sync listener that stops refreshing when a sync is completed.
- protected RemoteTabsSyncListener mSyncStatusListener;
-
- // Reference to the View to display when there are no results.
- private View mHistoryEmptyView;
- private View mClientsEmptyView;
- private View mRecentTabsEmptyView;
-
- public interface OnPanelLevelChangeListener {
- enum PanelLevel {
- PARENT, CHILD_SYNC, CHILD_RECENT_TABS
- }
-
- /**
- * Propagates level changes.
- * @param level
- * @return true if level changed, false otherwise.
- */
- boolean changeLevel(PanelLevel level);
- }
-
- @Override
- public void onCreate(Bundle savedInstance) {
- super.onCreate(savedInstance);
-
- int cachedRecentTabsCount = 0;
- if (mPanelStateChangeListener != null ) {
- cachedRecentTabsCount = mPanelStateChangeListener.getCachedRecentTabsCount();
- }
- mHistoryAdapter = new CombinedHistoryAdapter(getResources(), cachedRecentTabsCount);
- if (mPanelStateChangeListener != null) {
- mHistoryAdapter.setPanelStateChangeListener(mPanelStateChangeListener);
- }
-
- mClientsAdapter = new ClientsAdapter(getContext());
- // The RecentTabsAdapter doesn't use a cursor and therefore can't use the CursorLoader's
- // onLoadFinished() callback for updating the panel state when the closed tab count changes.
- // Instead, we provide it with independent callbacks as necessary.
- mRecentTabsAdapter = new RecentTabsAdapter(getContext(),
- mHistoryAdapter.getRecentTabsUpdateHandler(), getPanelStateUpdateHandler());
-
- mSyncStatusListener = new RemoteTabsSyncListener();
- FirefoxAccounts.addSyncStatusListener(mSyncStatusListener);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- return inflater.inflate(R.layout.home_combined_history_panel, container, false);
- }
-
- @UiThread
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- mRecyclerView = (CombinedHistoryRecyclerView) view.findViewById(R.id.combined_recycler_view);
- setUpRecyclerView();
-
- mRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.refresh_layout);
- setUpRefreshLayout();
-
- mClientsEmptyView = view.findViewById(R.id.home_clients_empty_view);
- mHistoryEmptyView = view.findViewById(R.id.home_history_empty_view);
- mRecentTabsEmptyView = view.findViewById(R.id.home_recent_tabs_empty_view);
- setUpEmptyViews();
-
- mPanelFooterButton = (Button) view.findViewById(R.id.history_panel_footer_button);
- mPanelFooterButton.setText(R.string.home_clear_history_button);
- mPanelFooterButton.setOnClickListener(new OnFooterButtonClickListener());
-
- mRecentTabsAdapter.startListeningForClosedTabs();
- mRecentTabsAdapter.startListeningForHistorySanitize();
-
- if (mSavedRestoreBundle != null) {
- setPanelStateFromBundle(mSavedRestoreBundle);
- mSavedRestoreBundle = null;
- }
- }
-
- @UiThread
- private void setUpRecyclerView() {
- if (mPanelLevel == null) {
- mPanelLevel = PARENT;
- }
-
- mRecyclerView.setAdapter(mPanelLevel == PARENT ? mHistoryAdapter :
- mPanelLevel == CHILD_SYNC ? mClientsAdapter : mRecentTabsAdapter);
-
- final RecyclerView.ItemAnimator animator = new DefaultItemAnimator();
- animator.setAddDuration(100);
- animator.setChangeDuration(100);
- animator.setMoveDuration(100);
- animator.setRemoveDuration(100);
- mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
- mHistoryAdapter.setLinearLayoutManager((LinearLayoutManager) mRecyclerView.getLayoutManager());
- mRecyclerView.setItemAnimator(animator);
- mRecyclerView.addItemDecoration(new HistoryDividerItemDecoration(getContext()));
- mRecyclerView.setOnHistoryClickedListener(mUrlOpenListener);
- mRecyclerView.setOnPanelLevelChangeListener(new OnLevelChangeListener());
- mRecyclerView.setHiddenClientsDialogBuilder(new HiddenClientsHelper());
- mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
- @Override
- public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
- super.onScrolled(recyclerView, dx, dy);
- final LinearLayoutManager llm = (LinearLayoutManager) recyclerView.getLayoutManager();
- if ((mPanelLevel == PARENT) && (llm.findLastCompletelyVisibleItemPosition() == HistoryCursorLoader.HISTORY_LIMIT)) {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.LIST, "history_scroll_max");
- }
-
- }
- });
- registerForContextMenu(mRecyclerView);
- }
-
- private void setUpRefreshLayout() {
- mRefreshLayout.setColorSchemeResources(R.color.fennec_ui_orange, R.color.action_orange);
- mRefreshLayout.setOnRefreshListener(new RemoteTabsRefreshListener());
- mRefreshLayout.setEnabled(false);
- }
-
- private void setUpEmptyViews() {
- // Set up history empty view.
- final ImageView historyIcon = (ImageView) mHistoryEmptyView.findViewById(R.id.home_empty_image);
- historyIcon.setVisibility(View.GONE);
-
- final TextView historyText = (TextView) mHistoryEmptyView.findViewById(R.id.home_empty_text);
- historyText.setText(R.string.home_most_recent_empty);
-
- final TextView historyHint = (TextView) mHistoryEmptyView.findViewById(R.id.home_empty_hint);
-
- if (!Restrictions.isAllowed(getActivity(), Restrictable.PRIVATE_BROWSING)) {
- historyHint.setVisibility(View.GONE);
- } else {
- final String hintText = getResources().getString(R.string.home_most_recent_emptyhint);
- final SpannableStringBuilder hintBuilder = formatHintText(hintText);
- if (hintBuilder != null) {
- historyHint.setText(hintBuilder);
- historyHint.setMovementMethod(LinkMovementMethod.getInstance());
- historyHint.setVisibility(View.VISIBLE);
- }
- }
-
- // Set up Clients empty view.
- final Button syncSetupButton = (Button) mClientsEmptyView.findViewById(R.id.sync_setup_button);
- syncSetupButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "history_syncsetup");
- // This Activity will redirect to the correct Activity as needed.
- final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
- startActivity(intent);
- }
- });
-
- // Set up Recent Tabs empty view.
- final ImageView recentTabsIcon = (ImageView) mRecentTabsEmptyView.findViewById(R.id.home_empty_image);
- recentTabsIcon.setImageResource(R.drawable.icon_remote_tabs_empty);
-
- final TextView recentTabsText = (TextView) mRecentTabsEmptyView.findViewById(R.id.home_empty_text);
- recentTabsText.setText(R.string.home_last_tabs_empty);
- }
-
- @Override
- public void setPanelStateChangeListener(
- PanelStateChangeListener panelStateChangeListener) {
- super.setPanelStateChangeListener(panelStateChangeListener);
- if (mHistoryAdapter != null) {
- mHistoryAdapter.setPanelStateChangeListener(panelStateChangeListener);
- }
- }
-
- @Override
- public void restoreData(Bundle data) {
- if (mRecyclerView != null) {
- setPanelStateFromBundle(data);
- } else {
- mSavedRestoreBundle = data;
- }
- }
-
- private void setPanelStateFromBundle(Bundle data) {
- if (data != null && data.getBoolean("goToRecentTabs", false) && mPanelLevel != CHILD_RECENT_TABS) {
- mPanelLevel = CHILD_RECENT_TABS;
- mRecyclerView.swapAdapter(mRecentTabsAdapter, true);
- updateEmptyView(CHILD_RECENT_TABS);
- updateButtonFromLevel();
- }
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- mCursorLoaderCallbacks = new CursorLoaderCallbacks();
- }
-
- @Override
- protected void load() {
- getLoaderManager().initLoader(LOADER_ID_HISTORY, null, mCursorLoaderCallbacks);
- getLoaderManager().initLoader(LOADER_ID_REMOTE, null, mCursorLoaderCallbacks);
- }
-
- private static class RemoteTabsCursorLoader extends SimpleCursorLoader {
- private final GeckoProfile mProfile;
-
- public RemoteTabsCursorLoader(Context context) {
- super(context);
- mProfile = GeckoProfile.get(context);
- }
-
- @Override
- public Cursor loadCursor() {
- return BrowserDB.from(mProfile).getTabsAccessor().getRemoteTabsCursor(getContext());
- }
- }
-
- private static class HistoryCursorLoader extends SimpleCursorLoader {
- // Max number of history results
- public static final int HISTORY_LIMIT = 100;
- private final BrowserDB mDB;
-
- public HistoryCursorLoader(Context context) {
- super(context);
- mDB = BrowserDB.from(context);
- }
-
- @Override
- public Cursor loadCursor() {
- final ContentResolver cr = getContext().getContentResolver();
- return mDB.getRecentHistory(cr, HISTORY_LIMIT);
- }
- }
-
- private class CursorLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
- private BrowserDB mDB; // Pseudo-final: set in onCreateLoader.
-
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- if (mDB == null) {
- mDB = BrowserDB.from(getActivity());
- }
-
- switch (id) {
- case LOADER_ID_HISTORY:
- return new HistoryCursorLoader(getContext());
- case LOADER_ID_REMOTE:
- return new RemoteTabsCursorLoader(getContext());
- default:
- Log.e(LOGTAG, "Unknown loader id!");
- return null;
- }
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
- final int loaderId = loader.getId();
- switch (loaderId) {
- case LOADER_ID_HISTORY:
- mHistoryAdapter.setHistory(c);
- updateEmptyView(PARENT);
- break;
-
- case LOADER_ID_REMOTE:
- final List<RemoteClient> clients = mDB.getTabsAccessor().getClientsFromCursor(c);
- mHistoryAdapter.getDeviceUpdateHandler().onDeviceCountUpdated(clients.size());
- mClientsAdapter.setClients(clients);
- updateEmptyView(CHILD_SYNC);
- break;
- }
-
- updateButtonFromLevel();
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- mClientsAdapter.setClients(Collections.<RemoteClient>emptyList());
- mHistoryAdapter.setHistory(null);
- }
- }
-
- public interface PanelStateUpdateHandler {
- void onPanelStateUpdated(PanelLevel level);
- }
-
- public PanelStateUpdateHandler getPanelStateUpdateHandler() {
- if (mPanelStateUpdateHandler == null) {
- mPanelStateUpdateHandler = new PanelStateUpdateHandler() {
- @Override
- public void onPanelStateUpdated(PanelLevel level) {
- updateEmptyView(level);
- updateButtonFromLevel();
- }
- };
- }
- return mPanelStateUpdateHandler;
- }
-
- protected class OnLevelChangeListener implements OnPanelLevelChangeListener {
- @Override
- public boolean changeLevel(PanelLevel level) {
- if (level == mPanelLevel) {
- return false;
- }
-
- mPanelLevel = level;
- switch (level) {
- case PARENT:
- mRecyclerView.swapAdapter(mHistoryAdapter, true);
- mRefreshLayout.setEnabled(false);
- break;
- case CHILD_SYNC:
- mRecyclerView.swapAdapter(mClientsAdapter, true);
- mRefreshLayout.setEnabled(mClientsAdapter.getClientsCount() > 0);
- break;
- case CHILD_RECENT_TABS:
- mRecyclerView.swapAdapter(mRecentTabsAdapter, true);
- break;
- }
-
- updateEmptyView(level);
- updateButtonFromLevel();
- return true;
- }
- }
-
- private void updateButtonFromLevel() {
- switch (mPanelLevel) {
- case PARENT:
- final boolean historyRestricted = !Restrictions.isAllowed(getActivity(), Restrictable.CLEAR_HISTORY);
- if (historyRestricted || mHistoryAdapter.getItemCount() == mHistoryAdapter.getNumVisibleSmartFolders()) {
- mPanelFooterButton.setVisibility(View.GONE);
- } else {
- mPanelFooterButton.setText(R.string.home_clear_history_button);
- mPanelFooterButton.setVisibility(View.VISIBLE);
- }
- break;
- case CHILD_RECENT_TABS:
- if (mRecentTabsAdapter.getClosedTabsCount() > 1) {
- mPanelFooterButton.setText(R.string.home_restore_all);
- mPanelFooterButton.setVisibility(View.VISIBLE);
- } else {
- mPanelFooterButton.setVisibility(View.GONE);
- }
- break;
- case CHILD_SYNC:
- mPanelFooterButton.setVisibility(View.GONE);
- break;
- }
- }
-
- private class OnFooterButtonClickListener implements View.OnClickListener {
- @Override
- public void onClick(View view) {
- switch (mPanelLevel) {
- case PARENT:
- final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity());
- dialogBuilder.setMessage(R.string.home_clear_history_confirm);
- dialogBuilder.setNegativeButton(R.string.button_cancel, new AlertDialog.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- dialog.dismiss();
- }
- });
-
- dialogBuilder.setPositiveButton(R.string.button_ok, new AlertDialog.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- dialog.dismiss();
-
- // Send message to Java to clear history.
- final JSONObject json = new JSONObject();
- try {
- json.put("history", true);
- } catch (JSONException e) {
- Log.e(LOGTAG, "JSON error", e);
- }
-
- GeckoAppShell.notifyObservers("Sanitize:ClearData", json.toString());
- Telemetry.sendUIEvent(TelemetryContract.Event.SANITIZE, TelemetryContract.Method.BUTTON, "history");
- }
- });
-
- dialogBuilder.show();
- break;
- case CHILD_RECENT_TABS:
- final String telemetryExtra = mRecentTabsAdapter.restoreAllTabs();
- if (telemetryExtra != null) {
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.BUTTON, telemetryExtra);
- }
- break;
- }
- }
- }
-
- private void updateEmptyView(PanelLevel level) {
- boolean showEmptyHistoryView = (mPanelLevel == PARENT && mHistoryEmptyView.isShown());
- boolean showEmptyClientsView = (mPanelLevel == CHILD_SYNC && mClientsEmptyView.isShown());
- boolean showEmptyRecentTabsView = (mPanelLevel == CHILD_RECENT_TABS && mRecentTabsEmptyView.isShown());
-
- if (mPanelLevel == level) {
- switch (mPanelLevel) {
- case PARENT:
- showEmptyHistoryView = mHistoryAdapter.getItemCount() == mHistoryAdapter.getNumVisibleSmartFolders();
- break;
-
- case CHILD_SYNC:
- showEmptyClientsView = mClientsAdapter.getItemCount() == 1;
- break;
-
- case CHILD_RECENT_TABS:
- showEmptyRecentTabsView = mRecentTabsAdapter.getClosedTabsCount() == 0;
- break;
- }
- }
-
- final boolean showEmptyView = showEmptyClientsView || showEmptyHistoryView || showEmptyRecentTabsView;
- mRecyclerView.setOverScrollMode(showEmptyView ? View.OVER_SCROLL_NEVER : View.OVER_SCROLL_IF_CONTENT_SCROLLS);
-
- mHistoryEmptyView.setVisibility(showEmptyHistoryView ? View.VISIBLE : View.GONE);
- mClientsEmptyView.setVisibility(showEmptyClientsView ? View.VISIBLE : View.GONE);
- mRecentTabsEmptyView.setVisibility(showEmptyRecentTabsView ? View.VISIBLE : View.GONE);
- }
-
- /**
- * Make Span that is clickable, and underlined
- * between the string markers <code>FORMAT_S1</code> and
- * <code>FORMAT_S2</code>.
- *
- * @param text String to format
- * @return formatted SpannableStringBuilder, or null if there
- * is not any text to format.
- */
- private SpannableStringBuilder formatHintText(String text) {
- // Set formatting as marked by string placeholders.
- final int underlineStart = text.indexOf(FORMAT_S1);
- final int underlineEnd = text.indexOf(FORMAT_S2);
-
- // Check that there is text to be formatted.
- if (underlineStart >= underlineEnd) {
- return null;
- }
-
- final SpannableStringBuilder ssb = new SpannableStringBuilder(text);
-
- // Set clickable text.
- final ClickableSpan clickableSpan = new ClickableSpan() {
- @Override
- public void onClick(View widget) {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "hint_private_browsing");
- try {
- final JSONObject json = new JSONObject();
- json.put("type", "Menu:Open");
- GeckoApp.getEventDispatcher().dispatchEvent(json, null);
- EventDispatcher.getInstance().dispatchEvent(json, null);
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error forming JSON for Private Browsing contextual hint", e);
- }
- }
- };
-
- ssb.setSpan(clickableSpan, 0, text.length(), 0);
-
- // Remove underlining set by ClickableSpan.
- final UnderlineSpan noUnderlineSpan = new UnderlineSpan() {
- @Override
- public void updateDrawState(TextPaint textPaint) {
- textPaint.setUnderlineText(false);
- }
- };
-
- ssb.setSpan(noUnderlineSpan, 0, text.length(), 0);
-
- // Add underlining for "Private Browsing".
- ssb.setSpan(new UnderlineSpan(), underlineStart, underlineEnd, 0);
-
- ssb.delete(underlineEnd, underlineEnd + FORMAT_S2.length());
- ssb.delete(underlineStart, underlineStart + FORMAT_S1.length());
-
- return ssb;
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
- if (!(menuInfo instanceof RemoteTabsClientContextMenuInfo)) {
- // Long pressed item was not a RemoteTabsGroup item. Superclass
- // can handle this.
- super.onCreateContextMenu(menu, view, menuInfo);
- return;
- }
-
- // Long pressed item was a remote client; provide the appropriate menu.
- final MenuInflater inflater = new MenuInflater(view.getContext());
- inflater.inflate(R.menu.home_remote_tabs_client_contextmenu, menu);
-
- final RemoteTabsClientContextMenuInfo info = (RemoteTabsClientContextMenuInfo) menuInfo;
- menu.setHeaderTitle(info.client.name);
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- if (super.onContextItemSelected(item)) {
- // HomeFragment was able to handle to selected item.
- return true;
- }
-
- final ContextMenu.ContextMenuInfo menuInfo = item.getMenuInfo();
- if (!(menuInfo instanceof RemoteTabsClientContextMenuInfo)) {
- return false;
- }
-
- final RemoteTabsClientContextMenuInfo info = (RemoteTabsClientContextMenuInfo) menuInfo;
-
- final int itemId = item.getItemId();
- if (itemId == R.id.home_remote_tabs_hide_client) {
- mClientsAdapter.removeItem(info.position);
- return true;
- }
-
- return false;
- }
-
- interface DialogBuilder<E> {
- void createAndShowDialog(List<E> items);
- }
-
- protected class HiddenClientsHelper implements DialogBuilder<RemoteClient> {
- @Override
- public void createAndShowDialog(List<RemoteClient> clientsList) {
- final RemoteClientsDialogFragment dialog = RemoteClientsDialogFragment.newInstance(
- getResources().getString(R.string.home_remote_tabs_hidden_devices_title),
- getResources().getString(R.string.home_remote_tabs_unhide_selected_devices),
- RemoteClientsDialogFragment.ChoiceMode.MULTIPLE, new ArrayList<>(clientsList));
- dialog.setTargetFragment(CombinedHistoryPanel.this, 0);
- dialog.show(getActivity().getSupportFragmentManager(), "show-clients");
- }
- }
-
- @Override
- public void onClients(List<RemoteClient> clients) {
- mClientsAdapter.unhideClients(clients);
- }
-
- /**
- * Stores information regarding the creation of the context menu for a remote client.
- */
- protected static class RemoteTabsClientContextMenuInfo extends HomeContextMenuInfo {
- protected final RemoteClient client;
-
- public RemoteTabsClientContextMenuInfo(View targetView, int position, long id, RemoteClient client) {
- super(targetView, position, id);
- this.client = client;
- }
- }
-
- protected class RemoteTabsRefreshListener implements SwipeRefreshLayout.OnRefreshListener {
- @Override
- public void onRefresh() {
- if (FirefoxAccounts.firefoxAccountsExist(getActivity())) {
- final Account account = FirefoxAccounts.getFirefoxAccount(getActivity());
- FirefoxAccounts.requestImmediateSync(account, STAGES_TO_SYNC_ON_REFRESH, null);
- } else {
- Log.wtf(LOGTAG, "No Firefox Account found; this should never happen. Ignoring.");
- mRefreshLayout.setRefreshing(false);
- }
- }
- }
-
- protected class RemoteTabsSyncListener implements SyncStatusListener {
- @Override
- public Context getContext() {
- return getActivity();
- }
-
- @Override
- public Account getAccount() {
- return FirefoxAccounts.getFirefoxAccount(getContext());
- }
-
- @Override
- public void onSyncStarted() {
- }
-
- @Override
- public void onSyncFinished() {
- mRefreshLayout.setRefreshing(false);
- }
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
-
- mRecentTabsAdapter.stopListeningForClosedTabs();
- mRecentTabsAdapter.stopListeningForHistorySanitize();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
-
- if (mSyncStatusListener != null) {
- FirefoxAccounts.removeSyncStatusListener(mSyncStatusListener);
- mSyncStatusListener = null;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryRecyclerView.java b/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryRecyclerView.java
deleted file mode 100644
index e813e4c44..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/CombinedHistoryRecyclerView.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.View;
-import org.mozilla.gecko.db.RemoteClient;
-import org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.widget.RecyclerViewClickSupport;
-
-import java.util.EnumSet;
-
-import static org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener.PanelLevel.CHILD_RECENT_TABS;
-import static org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener.PanelLevel.CHILD_SYNC;
-import static org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener.PanelLevel.PARENT;
-
-public class CombinedHistoryRecyclerView extends RecyclerView
- implements RecyclerViewClickSupport.OnItemClickListener, RecyclerViewClickSupport.OnItemLongClickListener {
- public static String LOGTAG = "CombinedHistoryRecycView";
-
- protected interface AdapterContextMenuBuilder {
- HomeContextMenuInfo makeContextMenuInfoFromPosition(View view, int position);
- }
-
- protected HomePager.OnUrlOpenListener mOnUrlOpenListener;
- protected OnPanelLevelChangeListener mOnPanelLevelChangeListener;
- protected CombinedHistoryPanel.DialogBuilder<RemoteClient> mDialogBuilder;
- protected HomeContextMenuInfo mContextMenuInfo;
-
- public CombinedHistoryRecyclerView(Context context) {
- super(context);
- init(context);
- }
-
- public CombinedHistoryRecyclerView(Context context, AttributeSet attributeSet) {
- super(context, attributeSet);
- init(context);
- }
-
- public CombinedHistoryRecyclerView(Context context, AttributeSet attributeSet, int defStyle) {
- super(context, attributeSet, defStyle);
- init(context);
- }
-
- private void init(Context context) {
- LinearLayoutManager layoutManager = new LinearLayoutManager(context);
- layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
- setLayoutManager(layoutManager);
-
- RecyclerViewClickSupport.addTo(this)
- .setOnItemClickListener(this)
- .setOnItemLongClickListener(this);
-
- setOnKeyListener(new View.OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- final int action = event.getAction();
-
- // If the user hit the BACK key, try to move to the parent folder.
- if (action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
- return mOnPanelLevelChangeListener.changeLevel(PARENT);
- }
- return false;
- }
- });
- }
-
- public void setOnHistoryClickedListener(HomePager.OnUrlOpenListener listener) {
- this.mOnUrlOpenListener = listener;
- }
-
- public void setOnPanelLevelChangeListener(OnPanelLevelChangeListener listener) {
- this.mOnPanelLevelChangeListener = listener;
- }
-
- public void setHiddenClientsDialogBuilder(CombinedHistoryPanel.DialogBuilder<RemoteClient> builder) {
- mDialogBuilder = builder;
- }
-
- @Override
- public void onItemClicked(RecyclerView recyclerView, int position, View v) {
- final int viewType = getAdapter().getItemViewType(position);
- final CombinedHistoryItem.ItemType itemType = CombinedHistoryItem.ItemType.viewTypeToItemType(viewType);
- final String telemetryExtra;
-
- switch (itemType) {
- case RECENT_TABS:
- mOnPanelLevelChangeListener.changeLevel(CHILD_RECENT_TABS);
- break;
-
- case SYNCED_DEVICES:
- mOnPanelLevelChangeListener.changeLevel(CHILD_SYNC);
- break;
-
- case CLIENT:
- ((ClientsAdapter) getAdapter()).toggleClient(position);
- break;
-
- case HIDDEN_DEVICES:
- if (mDialogBuilder != null) {
- mDialogBuilder.createAndShowDialog(((ClientsAdapter) getAdapter()).getHiddenClients());
- }
- break;
-
- case NAVIGATION_BACK:
- mOnPanelLevelChangeListener.changeLevel(PARENT);
- break;
-
- case CHILD:
- case HISTORY:
- if (mOnUrlOpenListener != null) {
- final TwoLinePageRow historyItem = (TwoLinePageRow) v;
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, "history");
- mOnUrlOpenListener.onUrlOpen(historyItem.getUrl(), EnumSet.of(HomePager.OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
- }
- break;
-
- case CLOSED_TAB:
- telemetryExtra = ((RecentTabsAdapter) getAdapter()).restoreTabFromPosition(position);
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, telemetryExtra);
- break;
- }
- }
-
- @Override
- public boolean onItemLongClicked(RecyclerView recyclerView, int position, View v) {
- mContextMenuInfo = ((AdapterContextMenuBuilder) getAdapter()).makeContextMenuInfoFromPosition(v, position);
- return showContextMenuForChild(this);
- }
-
- @Override
- public HomeContextMenuInfo getContextMenuInfo() {
- return mContextMenuInfo;
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/DynamicPanel.java b/mobile/android/base/java/org/mozilla/gecko/home/DynamicPanel.java
deleted file mode 100644
index d2c136219..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/DynamicPanel.java
+++ /dev/null
@@ -1,393 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.HomeItems;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.home.PanelLayout.ContextMenuRegistry;
-import org.mozilla.gecko.home.PanelLayout.DatasetHandler;
-import org.mozilla.gecko.home.PanelLayout.DatasetRequest;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.UIAsyncTask;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.Loader;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-/**
- * Fragment that displays dynamic content specified by a {@code PanelConfig}.
- * The {@code DynamicPanel} UI is built based on the given {@code LayoutType}
- * and its associated list of {@code ViewConfig}.
- *
- * {@code DynamicPanel} manages all necessary Loaders to load panel datasets
- * from their respective content providers. Each panel dataset has its own
- * associated Loader. This is enforced by defining the Loader IDs based on
- * their associated dataset IDs.
- *
- * The {@code PanelLayout} can make load and reset requests on datasets via
- * the provided {@code DatasetHandler}. This way it doesn't need to know the
- * details of how datasets are loaded and reset. Each time a dataset is
- * requested, {@code DynamicPanel} restarts a Loader with the respective ID (see
- * {@code PanelDatasetHandler}).
- *
- * See {@code PanelLayout} for more details on how {@code DynamicPanel}
- * receives dataset requests and delivers them back to the {@code PanelLayout}.
- */
-public class DynamicPanel extends HomeFragment {
- private static final String LOGTAG = "GeckoDynamicPanel";
-
- // Dataset ID to be used by the loader
- private static final String DATASET_REQUEST = "dataset_request";
-
- // Max number of items to display in the panel
- private static final int RESULT_LIMIT = 100;
-
- // The main view for this fragment. This contains the PanelLayout and PanelAuthLayout.
- private FrameLayout mView;
-
- // The panel layout associated with this panel
- private PanelLayout mPanelLayout;
-
- // The layout used to show authentication UI for this panel
- private PanelAuthLayout mPanelAuthLayout;
-
- // Cache used to keep track of whether or not the user has been authenticated.
- private PanelAuthCache mPanelAuthCache;
-
- // Hold a reference to the UiAsyncTask we use to check the state of the
- // PanelAuthCache, so that we can cancel it if necessary.
- private UIAsyncTask.WithoutParams<Boolean> mAuthStateTask;
-
- // The configuration associated with this panel
- private PanelConfig mPanelConfig;
-
- // Callbacks used for the loader
- private PanelLoaderCallbacks mLoaderCallbacks;
-
- // The current UI mode in the fragment
- private UIMode mUIMode;
-
- /*
- * Different UI modes to display depending on the authentication state.
- *
- * PANEL: Layout to display panel data.
- * AUTH: Authentication UI.
- */
- private enum UIMode {
- PANEL,
- AUTH
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final Bundle args = getArguments();
- if (args != null) {
- mPanelConfig = (PanelConfig) args.getParcelable(HomePager.PANEL_CONFIG_ARG);
- }
-
- if (mPanelConfig == null) {
- throw new IllegalStateException("Can't create a DynamicPanel without a PanelConfig");
- }
-
- mPanelAuthCache = new PanelAuthCache(getActivity());
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- mView = new FrameLayout(getActivity());
- return mView;
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- // Restore whatever the UI mode the fragment had before
- // a device rotation.
- if (mUIMode != null) {
- setUIMode(mUIMode);
- }
-
- mPanelAuthCache.setOnChangeListener(new PanelAuthChangeListener());
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
- mView = null;
- mPanelLayout = null;
- mPanelAuthLayout = null;
-
- mPanelAuthCache.setOnChangeListener(null);
-
- if (mAuthStateTask != null) {
- mAuthStateTask.cancel();
- mAuthStateTask = null;
- }
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- // Create callbacks before the initial loader is started.
- mLoaderCallbacks = new PanelLoaderCallbacks();
- loadIfVisible();
- }
-
- @Override
- protected void load() {
- Log.d(LOGTAG, "Loading layout");
-
- if (requiresAuth()) {
- mAuthStateTask = new UIAsyncTask.WithoutParams<Boolean>(ThreadUtils.getBackgroundHandler()) {
- @Override
- public synchronized Boolean doInBackground() {
- return mPanelAuthCache.isAuthenticated(mPanelConfig.getId());
- }
-
- @Override
- public void onPostExecute(Boolean isAuthenticated) {
- mAuthStateTask = null;
- setUIMode(isAuthenticated ? UIMode.PANEL : UIMode.AUTH);
- }
- };
- mAuthStateTask.execute();
- } else {
- setUIMode(UIMode.PANEL);
- }
- }
-
- /**
- * @return true if this panel requires authentication.
- */
- private boolean requiresAuth() {
- return mPanelConfig.getAuthConfig() != null;
- }
-
- /**
- * Lazily creates layout for panel data.
- */
- private void createPanelLayout() {
- final ContextMenuRegistry contextMenuRegistry = new ContextMenuRegistry() {
- @Override
- public void register(View view) {
- registerForContextMenu(view);
- }
- };
-
- switch (mPanelConfig.getLayoutType()) {
- case FRAME:
- final PanelDatasetHandler datasetHandler = new PanelDatasetHandler();
- mPanelLayout = new FramePanelLayout(getActivity(), mPanelConfig, datasetHandler,
- mUrlOpenListener, contextMenuRegistry);
- break;
-
- default:
- throw new IllegalStateException("Unrecognized layout type in DynamicPanel");
- }
-
- Log.d(LOGTAG, "Created layout of type: " + mPanelConfig.getLayoutType());
- mView.addView(mPanelLayout);
- }
-
- /**
- * Lazily creates layout for authentication UI.
- */
- private void createPanelAuthLayout() {
- mPanelAuthLayout = new PanelAuthLayout(getActivity(), mPanelConfig);
- mView.addView(mPanelAuthLayout, 0);
- }
-
- private void setUIMode(UIMode mode) {
- switch (mode) {
- case PANEL:
- if (mPanelAuthLayout != null) {
- mPanelAuthLayout.setVisibility(View.GONE);
- }
- if (mPanelLayout == null) {
- createPanelLayout();
- }
- mPanelLayout.setVisibility(View.VISIBLE);
-
- // Only trigger a reload if the UI mode has changed
- // (e.g. auth cache changes) and the fragment is allowed
- // to load its contents. Any loaders associated with the
- // panel layout will be automatically re-bound after a
- // device rotation, no need to explicitly load it again.
- if (mUIMode != mode && canLoad()) {
- mPanelLayout.load();
- }
- break;
-
- case AUTH:
- if (mPanelLayout != null) {
- mPanelLayout.setVisibility(View.GONE);
- }
- if (mPanelAuthLayout == null) {
- createPanelAuthLayout();
- }
- mPanelAuthLayout.setVisibility(View.VISIBLE);
- break;
-
- default:
- throw new IllegalStateException("Unrecognized UIMode in DynamicPanel");
- }
-
- mUIMode = mode;
- }
-
- /**
- * Used by the PanelLayout to make load and reset requests to
- * the holding fragment.
- */
- private class PanelDatasetHandler implements DatasetHandler {
- @Override
- public void requestDataset(DatasetRequest request) {
- Log.d(LOGTAG, "Requesting request: " + request);
-
- final Bundle bundle = new Bundle();
- bundle.putParcelable(DATASET_REQUEST, request);
-
- getLoaderManager().restartLoader(request.getViewIndex(),
- bundle, mLoaderCallbacks);
- }
-
- @Override
- public void resetDataset(int viewIndex) {
- Log.d(LOGTAG, "Resetting dataset: " + viewIndex);
-
- final LoaderManager lm = getLoaderManager();
-
- // Release any resources associated with the dataset if
- // it's currently loaded in memory.
- final Loader<?> datasetLoader = lm.getLoader(viewIndex);
- if (datasetLoader != null) {
- datasetLoader.reset();
- }
- }
- }
-
- /**
- * Cursor loader for the panel datasets.
- */
- private static class PanelDatasetLoader extends SimpleCursorLoader {
- private DatasetRequest mRequest;
-
- public PanelDatasetLoader(Context context, DatasetRequest request) {
- super(context);
- mRequest = request;
- }
-
- public DatasetRequest getRequest() {
- return mRequest;
- }
-
- @Override
- public void onContentChanged() {
- // Ensure the refresh request doesn't affect the view's filter
- // stack (i.e. use DATASET_LOAD type) but keep the current
- // dataset ID and filter.
- final DatasetRequest newRequest =
- new DatasetRequest(mRequest.getViewIndex(),
- DatasetRequest.Type.DATASET_LOAD,
- mRequest.getDatasetId(),
- mRequest.getFilterDetail());
-
- mRequest = newRequest;
- super.onContentChanged();
- }
-
- @Override
- public Cursor loadCursor() {
- final ContentResolver cr = getContext().getContentResolver();
-
- final String selection;
- final String[] selectionArgs;
-
- // Null represents the root filter
- if (mRequest.getFilter() == null) {
- selection = HomeItems.FILTER + " IS NULL";
- selectionArgs = null;
- } else {
- selection = HomeItems.FILTER + " = ?";
- selectionArgs = new String[] { mRequest.getFilter() };
- }
-
- final Uri queryUri = HomeItems.CONTENT_URI.buildUpon()
- .appendQueryParameter(BrowserContract.PARAM_DATASET_ID,
- mRequest.getDatasetId())
- .appendQueryParameter(BrowserContract.PARAM_LIMIT,
- String.valueOf(RESULT_LIMIT))
- .build();
-
- // XXX: You can use HomeItems.CONTENT_FAKE_URI for development
- // to pull items from fake_home_items.json.
- return cr.query(queryUri, null, selection, selectionArgs, null);
- }
- }
-
- /**
- * LoaderCallbacks implementation that interacts with the LoaderManager.
- */
- private class PanelLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- final DatasetRequest request = (DatasetRequest) args.getParcelable(DATASET_REQUEST);
-
- Log.d(LOGTAG, "Creating loader for request: " + request);
- return new PanelDatasetLoader(getActivity(), request);
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
- final DatasetRequest request = getRequestFromLoader(loader);
- Log.d(LOGTAG, "Finished loader for request: " + request);
-
- if (mPanelLayout != null) {
- mPanelLayout.deliverDataset(request, cursor);
- }
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- final DatasetRequest request = getRequestFromLoader(loader);
- Log.d(LOGTAG, "Resetting loader for request: " + request);
-
- if (mPanelLayout != null) {
- mPanelLayout.releaseDataset(request.getViewIndex());
- }
- }
-
- private DatasetRequest getRequestFromLoader(Loader<Cursor> loader) {
- final PanelDatasetLoader datasetLoader = (PanelDatasetLoader) loader;
- return datasetLoader.getRequest();
- }
- }
-
- private class PanelAuthChangeListener implements PanelAuthCache.OnChangeListener {
- @Override
- public void onChange(String panelId, boolean isAuthenticated) {
- if (!mPanelConfig.getId().equals(panelId)) {
- return;
- }
-
- setUIMode(isAuthenticated ? UIMode.PANEL : UIMode.AUTH);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/FramePanelLayout.java b/mobile/android/base/java/org/mozilla/gecko/home/FramePanelLayout.java
deleted file mode 100644
index 7168c1576..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/FramePanelLayout.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.home.HomeConfig.ViewConfig;
-
-import android.content.Context;
-import android.util.Log;
-import android.view.View;
-
-class FramePanelLayout extends PanelLayout {
- private static final String LOGTAG = "GeckoFramePanelLayout";
-
- private final View mChildView;
- private final ViewConfig mChildConfig;
-
- public FramePanelLayout(Context context, PanelConfig panelConfig, DatasetHandler datasetHandler,
- OnUrlOpenListener urlOpenListener, ContextMenuRegistry contextMenuRegistry) {
- super(context, panelConfig, datasetHandler, urlOpenListener, contextMenuRegistry);
-
- // This layout can only hold one view so we simply
- // take the first defined view from PanelConfig.
- mChildConfig = panelConfig.getViewAt(0);
- if (mChildConfig == null) {
- throw new IllegalStateException("FramePanelLayout requires a view in PanelConfig");
- }
-
- mChildView = createPanelView(mChildConfig);
- addView(mChildView);
- }
-
- @Override
- public void load() {
- Log.d(LOGTAG, "Loading");
-
- if (mChildView instanceof DatasetBacked) {
- final FilterDetail filter = new FilterDetail(mChildConfig.getFilter(), null);
-
- final DatasetRequest request = new DatasetRequest(mChildConfig.getIndex(),
- mChildConfig.getDatasetId(),
- filter);
-
- Log.d(LOGTAG, "Requesting child request: " + request);
- requestDataset(request);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HistorySectionsHelper.java b/mobile/android/base/java/org/mozilla/gecko/home/HistorySectionsHelper.java
deleted file mode 100644
index 7a49559f6..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HistorySectionsHelper.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.res.Resources;
-
-import org.mozilla.gecko.home.CombinedHistoryAdapter.SectionHeader;
-import org.mozilla.gecko.R;
-
-import java.util.Calendar;
-import java.util.Locale;
-
-
-public class HistorySectionsHelper {
-
- // Constants for different time sections.
- private static final long MS_PER_DAY = 86400000;
- private static final long MS_PER_WEEK = MS_PER_DAY * 7;
-
- public static class SectionDateRange {
- public final long start;
- public final long end;
- public final String displayName;
-
- private SectionDateRange(long start, long end, String displayName) {
- this.start = start;
- this.end = end;
- this.displayName = displayName;
- }
- }
-
- /**
- * Updates the time range in milliseconds covered by each section header and sets the title.
- * @param res Resources for fetching string names
- * @param sectionsArray Array of section bookkeeping objects
- */
- public static void updateRecentSectionOffset(final Resources res, SectionDateRange[] sectionsArray) {
- final long now = System.currentTimeMillis();
- final Calendar cal = Calendar.getInstance();
-
- // Update calendar to this day.
- cal.set(Calendar.HOUR_OF_DAY, 0);
- cal.set(Calendar.MINUTE, 0);
- cal.set(Calendar.SECOND, 0);
- cal.set(Calendar.MILLISECOND, 1);
- final long currentDayMS = cal.getTimeInMillis();
-
- // Calculate the start and end time for each section header and set its display text.
- sectionsArray[SectionHeader.TODAY.ordinal()] =
- new SectionDateRange(currentDayMS, now, res.getString(R.string.history_today_section));
-
- sectionsArray[SectionHeader.YESTERDAY.ordinal()] =
- new SectionDateRange(currentDayMS - MS_PER_DAY, currentDayMS, res.getString(R.string.history_yesterday_section));
-
- sectionsArray[SectionHeader.WEEK.ordinal()] =
- new SectionDateRange(currentDayMS - MS_PER_WEEK, now, res.getString(R.string.history_week_section));
-
- // Update the calendar to beginning of next month to avoid problems calculating the last day of this month.
- cal.add(Calendar.MONTH, 1);
- cal.set(Calendar.DAY_OF_MONTH, cal.getMinimum(Calendar.DAY_OF_MONTH));
-
- // Iterate over the remaining history sections, moving backwards in time.
- for (int i = SectionHeader.THIS_MONTH.ordinal(); i < SectionHeader.OLDER_THAN_SIX_MONTHS.ordinal(); i++) {
- final long end = cal.getTimeInMillis();
-
- cal.add(Calendar.MONTH, -1);
- final long start = cal.getTimeInMillis();
-
- final String displayName = cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault());
-
- sectionsArray[i] = new SectionDateRange(start, end, displayName);
- }
-
- sectionsArray[SectionHeader.OLDER_THAN_SIX_MONTHS.ordinal()] =
- new SectionDateRange(0L, cal.getTimeInMillis(), res.getString(R.string.history_older_section));
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeAdapter.java
deleted file mode 100644
index 98d1ae6d8..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeAdapter.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.activitystream.ActivityStream;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.home.HomeConfig.PanelType;
-import org.mozilla.gecko.home.activitystream.ActivityStreamHomeFragment;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentStatePagerAdapter;
-import android.view.ViewGroup;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class HomeAdapter extends FragmentStatePagerAdapter {
-
- private final Context mContext;
- private final ArrayList<PanelInfo> mPanelInfos;
- private final Map<String, HomeFragment> mPanels;
- private final Map<String, Bundle> mRestoreBundles;
-
- private boolean mCanLoadHint;
-
- private OnAddPanelListener mAddPanelListener;
-
- private HomeFragment.PanelStateChangeListener mPanelStateChangeListener = null;
-
- public interface OnAddPanelListener {
- void onAddPanel(String title);
- }
-
- public void setPanelStateChangeListener(HomeFragment.PanelStateChangeListener listener) {
- mPanelStateChangeListener = listener;
-
- for (Fragment fragment : mPanels.values()) {
- ((HomeFragment) fragment).setPanelStateChangeListener(listener);
- }
- }
-
- public HomeAdapter(Context context, FragmentManager fm) {
- super(fm);
-
- mContext = context;
- mCanLoadHint = HomeFragment.DEFAULT_CAN_LOAD_HINT;
-
- mPanelInfos = new ArrayList<>();
- mPanels = new HashMap<>();
- mRestoreBundles = new HashMap<>();
- }
-
- @Override
- public int getCount() {
- return mPanelInfos.size();
- }
-
- @Override
- public Fragment getItem(int position) {
- PanelInfo info = mPanelInfos.get(position);
- return Fragment.instantiate(mContext, info.getClassName(mContext), info.getArgs());
- }
-
- @Override
- public CharSequence getPageTitle(int position) {
- if (mPanelInfos.size() > 0) {
- PanelInfo info = mPanelInfos.get(position);
- return info.getTitle().toUpperCase();
- }
-
- return null;
- }
-
- @Override
- public Object instantiateItem(ViewGroup container, int position) {
- final HomeFragment fragment = (HomeFragment) super.instantiateItem(container, position);
- fragment.setPanelStateChangeListener(mPanelStateChangeListener);
-
- final String id = mPanelInfos.get(position).getId();
- mPanels.put(id, fragment);
-
- if (mRestoreBundles.containsKey(id)) {
- fragment.restoreData(mRestoreBundles.get(id));
- mRestoreBundles.remove(id);
- }
-
- return fragment;
- }
-
- public void setRestoreData(int position, Bundle data) {
- final String id = mPanelInfos.get(position).getId();
- final HomeFragment fragment = mPanels.get(id);
-
- // We have no guarantees as to whether our desired fragment is instantiated yet: therefore
- // we might need to either pass data to the fragment, or store it for later.
- if (fragment != null) {
- fragment.restoreData(data);
- } else {
- mRestoreBundles.put(id, data);
- }
-
- }
-
- @Override
- public void destroyItem(ViewGroup container, int position, Object object) {
- final String id = mPanelInfos.get(position).getId();
-
- super.destroyItem(container, position, object);
- mPanels.remove(id);
- }
-
- public void setOnAddPanelListener(OnAddPanelListener listener) {
- mAddPanelListener = listener;
- }
-
- public int getItemPosition(String panelId) {
- for (int i = 0; i < mPanelInfos.size(); i++) {
- final String id = mPanelInfos.get(i).getId();
- if (id.equals(panelId)) {
- return i;
- }
- }
-
- return -1;
- }
-
- public String getPanelIdAtPosition(int position) {
- // getPanelIdAtPosition() might be called before HomeAdapter
- // has got its initial list of PanelConfigs. Just bail.
- if (mPanelInfos.isEmpty()) {
- return null;
- }
-
- return mPanelInfos.get(position).getId();
- }
-
- private void addPanel(PanelInfo info) {
- mPanelInfos.add(info);
-
- if (mAddPanelListener != null) {
- mAddPanelListener.onAddPanel(info.getTitle());
- }
- }
-
- public void update(List<PanelConfig> panelConfigs) {
- mPanels.clear();
- mPanelInfos.clear();
-
- if (panelConfigs != null) {
- for (PanelConfig panelConfig : panelConfigs) {
- final PanelInfo info = new PanelInfo(panelConfig);
- addPanel(info);
- }
- }
-
- notifyDataSetChanged();
- }
-
- public boolean getCanLoadHint() {
- return mCanLoadHint;
- }
-
- public void setCanLoadHint(boolean canLoadHint) {
- // We cache the last hint value so that we can use it when
- // creating new panels. See PanelInfo.getArgs().
- mCanLoadHint = canLoadHint;
-
- // Enable/disable loading on all existing panels
- for (Fragment panelFragment : mPanels.values()) {
- final HomeFragment panel = (HomeFragment) panelFragment;
- panel.setCanLoadHint(canLoadHint);
- }
- }
-
- private final class PanelInfo {
- private final PanelConfig mPanelConfig;
-
- PanelInfo(PanelConfig panelConfig) {
- mPanelConfig = panelConfig;
- }
-
- public String getId() {
- return mPanelConfig.getId();
- }
-
- public String getTitle() {
- return mPanelConfig.getTitle();
- }
-
- public String getClassName(Context context) {
- final PanelType type = mPanelConfig.getType();
-
- // Override top_sites with ActivityStream panel when enabled
- // PanelType.toString() returns the panel id
- if (type.toString() == "top_sites" &&
- ActivityStream.isEnabled(context) &&
- ActivityStream.isHomePanel()) {
- return ActivityStreamHomeFragment.class.getName();
- }
- return type.getPanelClass().getName();
- }
-
- public Bundle getArgs() {
- final Bundle args = new Bundle();
-
- args.putBoolean(HomePager.CAN_LOAD_ARG, mCanLoadHint);
-
- // Only DynamicPanels need the PanelConfig argument
- if (mPanelConfig.isDynamic()) {
- args.putParcelable(HomePager.PANEL_CONFIG_ARG, mPanelConfig);
- }
-
- return args;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java
deleted file mode 100644
index 10f5db39e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeBanner.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.json.JSONObject;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.animation.PropertyAnimator.Property;
-import org.mozilla.gecko.animation.ViewHelper;
-import org.mozilla.gecko.util.ResourceDrawableUtils;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.widget.EllipsisTextView;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.text.Html;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-public class HomeBanner extends LinearLayout
- implements GeckoEventListener {
- private static final String LOGTAG = "GeckoHomeBanner";
-
- // Used for tracking scroll length
- private float mTouchY = -1;
-
- // Used to detect for upwards scroll to push banner all the way up
- private boolean mSnapBannerToTop;
-
- // Tracks whether or not the banner should be shown on the current panel.
- private boolean mActive;
-
- // The user is currently swiping between HomePager pages
- private boolean mScrollingPages;
-
- // Tracks whether the user swiped the banner down, preventing us from autoshowing when the user
- // switches back to the default page.
- private boolean mUserSwipedDown;
-
- // We must use this custom TextView to address an issue on 2.3 and lower where ellipsized text
- // will not wrap more than 2 lines.
- private final EllipsisTextView mTextView;
- private final ImageView mIconView;
-
- // The height of the banner view.
- private final float mHeight;
-
- // Listener that gets called when the banner is dismissed from the close button.
- private OnDismissListener mOnDismissListener;
-
- public interface OnDismissListener {
- public void onDismiss();
- }
-
- public HomeBanner(Context context) {
- this(context, null);
- }
-
- public HomeBanner(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- LayoutInflater.from(context).inflate(R.layout.home_banner_content, this);
-
- mTextView = (EllipsisTextView) findViewById(R.id.text);
- mIconView = (ImageView) findViewById(R.id.icon);
-
- mHeight = getResources().getDimensionPixelSize(R.dimen.home_banner_height);
-
- // Disable the banner until a message is set.
- setEnabled(false);
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- // Tapping on the close button will ensure that the banner is never
- // showed again on this session.
- final ImageButton closeButton = (ImageButton) findViewById(R.id.close);
-
- // The drawable should have 50% opacity.
- closeButton.getDrawable().setAlpha(127);
-
- closeButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- HomeBanner.this.dismiss();
-
- // Send the current message id back to JS.
- GeckoAppShell.notifyObservers("HomeBanner:Dismiss", (String) getTag());
- }
- });
-
- setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- HomeBanner.this.dismiss();
-
- // Send the current message id back to JS.
- GeckoAppShell.notifyObservers("HomeBanner:Click", (String) getTag());
- }
- });
-
- GeckoApp.getEventDispatcher().registerGeckoThreadListener(this, "HomeBanner:Data");
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this, "HomeBanner:Data");
- }
-
- public void setScrollingPages(boolean scrollingPages) {
- mScrollingPages = scrollingPages;
- }
-
- public void setOnDismissListener(OnDismissListener listener) {
- mOnDismissListener = listener;
- }
-
- /**
- * Hides and disables the banner.
- */
- private void dismiss() {
- setVisibility(View.GONE);
- setEnabled(false);
-
- if (mOnDismissListener != null) {
- mOnDismissListener.onDismiss();
- }
- }
-
- /**
- * Sends a message to gecko to request a new banner message. UI is updated in handleMessage.
- */
- public void update() {
- GeckoAppShell.notifyObservers("HomeBanner:Get", null);
- }
-
- @Override
- public void handleMessage(String event, JSONObject message) {
- final String id = message.optString("id");
- final String text = message.optString("text");
- final String iconURI = message.optString("iconURI");
-
- // Don't update the banner if the message doesn't have valid id and text.
- if (TextUtils.isEmpty(id) || TextUtils.isEmpty(text)) {
- return;
- }
-
- // Update the banner message on the UI thread.
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- // Store the current message id to pass back to JS in the view's OnClickListener.
- setTag(id);
- mTextView.setOriginalText(Html.fromHtml(text));
-
- ResourceDrawableUtils.getDrawable(getContext(), iconURI, new ResourceDrawableUtils.BitmapLoader() {
- @Override
- public void onBitmapFound(final Drawable d) {
- // Hide the image view if we don't have an icon to show.
- if (d == null) {
- mIconView.setVisibility(View.GONE);
- } else {
- mIconView.setImageDrawable(d);
- mIconView.setVisibility(View.VISIBLE);
- }
- }
- });
-
- GeckoAppShell.notifyObservers("HomeBanner:Shown", id);
-
- // Enable the banner after a message is set.
- setEnabled(true);
-
- // Animate the banner if it is currently active.
- if (mActive) {
- animateUp();
- }
- }
- });
- }
-
- public void setActive(boolean active) {
- // No need to animate if not changing
- if (mActive == active) {
- return;
- }
-
- mActive = active;
-
- // Don't animate if the banner isn't enabled.
- if (!isEnabled()) {
- return;
- }
-
- if (active) {
- animateUp();
- } else {
- animateDown();
- }
- }
-
- private void ensureVisible() {
- // The banner visibility is set to GONE after it is animated off screen,
- // so we need to make it visible again.
- if (getVisibility() == View.GONE) {
- // Translate the banner off screen before setting it to VISIBLE.
- ViewHelper.setTranslationY(this, mHeight);
- setVisibility(View.VISIBLE);
- }
- }
-
- private void animateUp() {
- // Don't try to animate if the user swiped the banner down previously to hide it.
- if (mUserSwipedDown) {
- return;
- }
-
- ensureVisible();
-
- final PropertyAnimator animator = new PropertyAnimator(100);
- animator.attach(this, Property.TRANSLATION_Y, 0);
- animator.start();
- }
-
- private void animateDown() {
- if (ViewHelper.getTranslationY(this) == mHeight) {
- // Hide the banner to avoid intercepting clicks on pre-honeycomb devices.
- setVisibility(View.GONE);
- return;
- }
-
- final PropertyAnimator animator = new PropertyAnimator(100);
- animator.attach(this, Property.TRANSLATION_Y, mHeight);
- animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
- @Override
- public void onPropertyAnimationStart() {
- }
-
- @Override
- public void onPropertyAnimationEnd() {
- // Hide the banner to avoid intercepting clicks on pre-honeycomb devices.
- setVisibility(View.GONE);
- }
- });
- animator.start();
- }
-
- public void handleHomeTouch(MotionEvent event) {
- if (!mActive || !isEnabled() || mScrollingPages) {
- return;
- }
-
- ensureVisible();
-
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN: {
- // Track the beginning of the touch
- mTouchY = event.getRawY();
- break;
- }
-
- case MotionEvent.ACTION_MOVE: {
- final float curY = event.getRawY();
- final float delta = mTouchY - curY;
- mSnapBannerToTop = delta <= 0.0f;
-
- float newTranslationY = ViewHelper.getTranslationY(this) + delta;
-
- // Clamp the values to be between 0 and height.
- if (newTranslationY < 0.0f) {
- newTranslationY = 0.0f;
- } else if (newTranslationY > mHeight) {
- newTranslationY = mHeight;
- }
-
- // Don't change this value if it wasn't a significant movement
- if (delta >= 10 || delta <= -10) {
- mUserSwipedDown = (newTranslationY == mHeight);
- }
-
- ViewHelper.setTranslationY(this, newTranslationY);
- mTouchY = curY;
- break;
- }
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL: {
- mTouchY = -1;
- if (mSnapBannerToTop) {
- animateUp();
- } else {
- animateDown();
- mUserSwipedDown = true;
- }
- break;
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeConfig.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeConfig.java
deleted file mode 100644
index 08e79be3a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeConfig.java
+++ /dev/null
@@ -1,1694 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.content.Context;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-import android.util.Pair;
-
-public final class HomeConfig {
- public static final String PREF_KEY_BOOKMARKS_PANEL_ENABLED = "bookmarksPanelEnabled";
- public static final String PREF_KEY_HISTORY_PANEL_ENABLED = "combinedHistoryPanelEnabled";
-
- /**
- * Used to determine what type of HomeFragment subclass to use when creating
- * a given panel. With the exception of DYNAMIC, all of these types correspond
- * to a default set of built-in panels. The DYNAMIC panel type is used by
- * third-party services to create panels with varying types of content.
- */
- @RobocopTarget
- public static enum PanelType implements Parcelable {
- TOP_SITES("top_sites", TopSitesPanel.class),
- BOOKMARKS("bookmarks", BookmarksPanel.class),
- COMBINED_HISTORY("combined_history", CombinedHistoryPanel.class),
- DYNAMIC("dynamic", DynamicPanel.class),
- // Deprecated panels that should no longer exist but are kept around for
- // migration code. Class references have been replaced with new version of the panel.
- DEPRECATED_REMOTE_TABS("remote_tabs", CombinedHistoryPanel.class),
- DEPRECATED_HISTORY("history", CombinedHistoryPanel.class),
- DEPRECATED_READING_LIST("reading_list", BookmarksPanel.class),
- DEPRECATED_RECENT_TABS("recent_tabs", CombinedHistoryPanel.class);
-
- private final String mId;
- private final Class<?> mPanelClass;
-
- PanelType(String id, Class<?> panelClass) {
- mId = id;
- mPanelClass = panelClass;
- }
-
- public static PanelType fromId(String id) {
- if (id == null) {
- throw new IllegalArgumentException("Could not convert null String to PanelType");
- }
-
- for (PanelType panelType : PanelType.values()) {
- if (TextUtils.equals(panelType.mId, id.toLowerCase())) {
- return panelType;
- }
- }
-
- throw new IllegalArgumentException("Could not convert String id to PanelType");
- }
-
- @Override
- public String toString() {
- return mId;
- }
-
- public Class<?> getPanelClass() {
- return mPanelClass;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(ordinal());
- }
-
- public static final Creator<PanelType> CREATOR = new Creator<PanelType>() {
- @Override
- public PanelType createFromParcel(final Parcel source) {
- return PanelType.values()[source.readInt()];
- }
-
- @Override
- public PanelType[] newArray(final int size) {
- return new PanelType[size];
- }
- };
- }
-
- public static class PanelConfig implements Parcelable {
- private final PanelType mType;
- private final String mTitle;
- private final String mId;
- private final LayoutType mLayoutType;
- private final List<ViewConfig> mViews;
- private final AuthConfig mAuthConfig;
- private final EnumSet<Flags> mFlags;
- private final int mPosition;
-
- static final String JSON_KEY_TYPE = "type";
- static final String JSON_KEY_TITLE = "title";
- static final String JSON_KEY_ID = "id";
- static final String JSON_KEY_LAYOUT = "layout";
- static final String JSON_KEY_VIEWS = "views";
- static final String JSON_KEY_AUTH_CONFIG = "authConfig";
- static final String JSON_KEY_DEFAULT = "default";
- static final String JSON_KEY_DISABLED = "disabled";
- static final String JSON_KEY_POSITION = "position";
-
- public enum Flags {
- DEFAULT_PANEL,
- DISABLED_PANEL
- }
-
- public PanelConfig(JSONObject json) throws JSONException, IllegalArgumentException {
- final String panelType = json.optString(JSON_KEY_TYPE, null);
- if (TextUtils.isEmpty(panelType)) {
- mType = PanelType.DYNAMIC;
- } else {
- mType = PanelType.fromId(panelType);
- }
-
- mTitle = json.getString(JSON_KEY_TITLE);
- mId = json.getString(JSON_KEY_ID);
-
- final String layoutTypeId = json.optString(JSON_KEY_LAYOUT, null);
- if (layoutTypeId != null) {
- mLayoutType = LayoutType.fromId(layoutTypeId);
- } else {
- mLayoutType = null;
- }
-
- final JSONArray jsonViews = json.optJSONArray(JSON_KEY_VIEWS);
- if (jsonViews != null) {
- mViews = new ArrayList<ViewConfig>();
-
- final int viewCount = jsonViews.length();
- for (int i = 0; i < viewCount; i++) {
- final JSONObject jsonViewConfig = (JSONObject) jsonViews.get(i);
- final ViewConfig viewConfig = new ViewConfig(i, jsonViewConfig);
- mViews.add(viewConfig);
- }
- } else {
- mViews = null;
- }
-
- final JSONObject jsonAuthConfig = json.optJSONObject(JSON_KEY_AUTH_CONFIG);
- if (jsonAuthConfig != null) {
- mAuthConfig = new AuthConfig(jsonAuthConfig);
- } else {
- mAuthConfig = null;
- }
-
- mFlags = EnumSet.noneOf(Flags.class);
-
- if (json.optBoolean(JSON_KEY_DEFAULT, false)) {
- mFlags.add(Flags.DEFAULT_PANEL);
- }
-
- if (json.optBoolean(JSON_KEY_DISABLED, false)) {
- mFlags.add(Flags.DISABLED_PANEL);
- }
-
- mPosition = json.optInt(JSON_KEY_POSITION, -1);
-
- validate();
- }
-
- @SuppressWarnings("unchecked")
- public PanelConfig(Parcel in) {
- mType = (PanelType) in.readParcelable(getClass().getClassLoader());
- mTitle = in.readString();
- mId = in.readString();
- mLayoutType = (LayoutType) in.readParcelable(getClass().getClassLoader());
-
- mViews = new ArrayList<ViewConfig>();
- in.readTypedList(mViews, ViewConfig.CREATOR);
-
- mAuthConfig = (AuthConfig) in.readParcelable(getClass().getClassLoader());
-
- mFlags = (EnumSet<Flags>) in.readSerializable();
- mPosition = in.readInt();
-
- validate();
- }
-
- public PanelConfig(PanelConfig panelConfig) {
- mType = panelConfig.mType;
- mTitle = panelConfig.mTitle;
- mId = panelConfig.mId;
- mLayoutType = panelConfig.mLayoutType;
-
- mViews = new ArrayList<ViewConfig>();
- List<ViewConfig> viewConfigs = panelConfig.mViews;
- if (viewConfigs != null) {
- for (ViewConfig viewConfig : viewConfigs) {
- mViews.add(new ViewConfig(viewConfig));
- }
- }
-
- mAuthConfig = panelConfig.mAuthConfig;
- mFlags = panelConfig.mFlags.clone();
- mPosition = panelConfig.mPosition;
-
- validate();
- }
-
- public PanelConfig(PanelType type, String title, String id) {
- this(type, title, id, EnumSet.noneOf(Flags.class));
- }
-
- public PanelConfig(PanelType type, String title, String id, EnumSet<Flags> flags) {
- this(type, title, id, null, null, null, flags, -1);
- }
-
- public PanelConfig(PanelType type, String title, String id, LayoutType layoutType,
- List<ViewConfig> views, AuthConfig authConfig, EnumSet<Flags> flags, int position) {
- mType = type;
- mTitle = title;
- mId = id;
- mLayoutType = layoutType;
- mViews = views;
- mAuthConfig = authConfig;
- mFlags = flags;
- mPosition = position;
-
- validate();
- }
-
- private void validate() {
- if (mType == null) {
- throw new IllegalArgumentException("Can't create PanelConfig with null type");
- }
-
- if (TextUtils.isEmpty(mTitle)) {
- throw new IllegalArgumentException("Can't create PanelConfig with empty title");
- }
-
- if (TextUtils.isEmpty(mId)) {
- throw new IllegalArgumentException("Can't create PanelConfig with empty id");
- }
-
- if (mLayoutType == null && mType == PanelType.DYNAMIC) {
- throw new IllegalArgumentException("Can't create a dynamic PanelConfig with null layout type");
- }
-
- if ((mViews == null || mViews.size() == 0) && mType == PanelType.DYNAMIC) {
- throw new IllegalArgumentException("Can't create a dynamic PanelConfig with no views");
- }
-
- if (mFlags == null) {
- throw new IllegalArgumentException("Can't create PanelConfig with null flags");
- }
- }
-
- public PanelType getType() {
- return mType;
- }
-
- public String getTitle() {
- return mTitle;
- }
-
- public String getId() {
- return mId;
- }
-
- public LayoutType getLayoutType() {
- return mLayoutType;
- }
-
- public int getViewCount() {
- return (mViews != null ? mViews.size() : 0);
- }
-
- public ViewConfig getViewAt(int index) {
- return (mViews != null ? mViews.get(index) : null);
- }
-
- public EnumSet<Flags> getFlags() {
- return mFlags.clone();
- }
-
- public boolean isDynamic() {
- return (mType == PanelType.DYNAMIC);
- }
-
- public boolean isDefault() {
- return mFlags.contains(Flags.DEFAULT_PANEL);
- }
-
- private void setIsDefault(boolean isDefault) {
- if (isDefault) {
- mFlags.add(Flags.DEFAULT_PANEL);
- } else {
- mFlags.remove(Flags.DEFAULT_PANEL);
- }
- }
-
- public boolean isDisabled() {
- return mFlags.contains(Flags.DISABLED_PANEL);
- }
-
- private void setIsDisabled(boolean isDisabled) {
- if (isDisabled) {
- mFlags.add(Flags.DISABLED_PANEL);
- } else {
- mFlags.remove(Flags.DISABLED_PANEL);
- }
- }
-
- public AuthConfig getAuthConfig() {
- return mAuthConfig;
- }
-
- public int getPosition() {
- return mPosition;
- }
-
- public JSONObject toJSON() throws JSONException {
- final JSONObject json = new JSONObject();
-
- json.put(JSON_KEY_TYPE, mType.toString());
- json.put(JSON_KEY_TITLE, mTitle);
- json.put(JSON_KEY_ID, mId);
-
- if (mLayoutType != null) {
- json.put(JSON_KEY_LAYOUT, mLayoutType.toString());
- }
-
- if (mViews != null) {
- final JSONArray jsonViews = new JSONArray();
-
- final int viewCount = mViews.size();
- for (int i = 0; i < viewCount; i++) {
- final ViewConfig viewConfig = mViews.get(i);
- final JSONObject jsonViewConfig = viewConfig.toJSON();
- jsonViews.put(jsonViewConfig);
- }
-
- json.put(JSON_KEY_VIEWS, jsonViews);
- }
-
- if (mAuthConfig != null) {
- json.put(JSON_KEY_AUTH_CONFIG, mAuthConfig.toJSON());
- }
-
- if (mFlags.contains(Flags.DEFAULT_PANEL)) {
- json.put(JSON_KEY_DEFAULT, true);
- }
-
- if (mFlags.contains(Flags.DISABLED_PANEL)) {
- json.put(JSON_KEY_DISABLED, true);
- }
-
- json.put(JSON_KEY_POSITION, mPosition);
-
- return json;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == null) {
- return false;
- }
-
- if (this == o) {
- return true;
- }
-
- if (!(o instanceof PanelConfig)) {
- return false;
- }
-
- final PanelConfig other = (PanelConfig) o;
- return mId.equals(other.mId);
- }
-
- @Override
- public int hashCode() {
- return super.hashCode();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelable(mType, 0);
- dest.writeString(mTitle);
- dest.writeString(mId);
- dest.writeParcelable(mLayoutType, 0);
- dest.writeTypedList(mViews);
- dest.writeParcelable(mAuthConfig, 0);
- dest.writeSerializable(mFlags);
- dest.writeInt(mPosition);
- }
-
- public static final Creator<PanelConfig> CREATOR = new Creator<PanelConfig>() {
- @Override
- public PanelConfig createFromParcel(final Parcel in) {
- return new PanelConfig(in);
- }
-
- @Override
- public PanelConfig[] newArray(final int size) {
- return new PanelConfig[size];
- }
- };
- }
-
- public static enum LayoutType implements Parcelable {
- FRAME("frame");
-
- private final String mId;
-
- LayoutType(String id) {
- mId = id;
- }
-
- public static LayoutType fromId(String id) {
- if (id == null) {
- throw new IllegalArgumentException("Could not convert null String to LayoutType");
- }
-
- for (LayoutType layoutType : LayoutType.values()) {
- if (TextUtils.equals(layoutType.mId, id.toLowerCase())) {
- return layoutType;
- }
- }
-
- throw new IllegalArgumentException("Could not convert String id to LayoutType");
- }
-
- @Override
- public String toString() {
- return mId;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(ordinal());
- }
-
- public static final Creator<LayoutType> CREATOR = new Creator<LayoutType>() {
- @Override
- public LayoutType createFromParcel(final Parcel source) {
- return LayoutType.values()[source.readInt()];
- }
-
- @Override
- public LayoutType[] newArray(final int size) {
- return new LayoutType[size];
- }
- };
- }
-
- public static enum ViewType implements Parcelable {
- LIST("list"),
- GRID("grid");
-
- private final String mId;
-
- ViewType(String id) {
- mId = id;
- }
-
- public static ViewType fromId(String id) {
- if (id == null) {
- throw new IllegalArgumentException("Could not convert null String to ViewType");
- }
-
- for (ViewType viewType : ViewType.values()) {
- if (TextUtils.equals(viewType.mId, id.toLowerCase())) {
- return viewType;
- }
- }
-
- throw new IllegalArgumentException("Could not convert String id to ViewType");
- }
-
- @Override
- public String toString() {
- return mId;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(ordinal());
- }
-
- public static final Creator<ViewType> CREATOR = new Creator<ViewType>() {
- @Override
- public ViewType createFromParcel(final Parcel source) {
- return ViewType.values()[source.readInt()];
- }
-
- @Override
- public ViewType[] newArray(final int size) {
- return new ViewType[size];
- }
- };
- }
-
- public static enum ItemType implements Parcelable {
- ARTICLE("article"),
- IMAGE("image"),
- ICON("icon");
-
- private final String mId;
-
- ItemType(String id) {
- mId = id;
- }
-
- public static ItemType fromId(String id) {
- if (id == null) {
- throw new IllegalArgumentException("Could not convert null String to ItemType");
- }
-
- for (ItemType itemType : ItemType.values()) {
- if (TextUtils.equals(itemType.mId, id.toLowerCase())) {
- return itemType;
- }
- }
-
- throw new IllegalArgumentException("Could not convert String id to ItemType");
- }
-
- @Override
- public String toString() {
- return mId;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(ordinal());
- }
-
- public static final Creator<ItemType> CREATOR = new Creator<ItemType>() {
- @Override
- public ItemType createFromParcel(final Parcel source) {
- return ItemType.values()[source.readInt()];
- }
-
- @Override
- public ItemType[] newArray(final int size) {
- return new ItemType[size];
- }
- };
- }
-
- public static enum ItemHandler implements Parcelable {
- BROWSER("browser"),
- INTENT("intent");
-
- private final String mId;
-
- ItemHandler(String id) {
- mId = id;
- }
-
- public static ItemHandler fromId(String id) {
- if (id == null) {
- throw new IllegalArgumentException("Could not convert null String to ItemHandler");
- }
-
- for (ItemHandler itemHandler : ItemHandler.values()) {
- if (TextUtils.equals(itemHandler.mId, id.toLowerCase())) {
- return itemHandler;
- }
- }
-
- throw new IllegalArgumentException("Could not convert String id to ItemHandler");
- }
-
- @Override
- public String toString() {
- return mId;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(ordinal());
- }
-
- public static final Creator<ItemHandler> CREATOR = new Creator<ItemHandler>() {
- @Override
- public ItemHandler createFromParcel(final Parcel source) {
- return ItemHandler.values()[source.readInt()];
- }
-
- @Override
- public ItemHandler[] newArray(final int size) {
- return new ItemHandler[size];
- }
- };
- }
-
- public static class ViewConfig implements Parcelable {
- private final int mIndex;
- private final ViewType mType;
- private final String mDatasetId;
- private final ItemType mItemType;
- private final ItemHandler mItemHandler;
- private final String mBackImageUrl;
- private final String mFilter;
- private final EmptyViewConfig mEmptyViewConfig;
- private final HeaderConfig mHeaderConfig;
- private final EnumSet<Flags> mFlags;
-
- static final String JSON_KEY_TYPE = "type";
- static final String JSON_KEY_DATASET = "dataset";
- static final String JSON_KEY_ITEM_TYPE = "itemType";
- static final String JSON_KEY_ITEM_HANDLER = "itemHandler";
- static final String JSON_KEY_BACK_IMAGE_URL = "backImageUrl";
- static final String JSON_KEY_FILTER = "filter";
- static final String JSON_KEY_EMPTY = "empty";
- static final String JSON_KEY_HEADER = "header";
- static final String JSON_KEY_REFRESH_ENABLED = "refreshEnabled";
-
- public enum Flags {
- REFRESH_ENABLED
- }
-
- public ViewConfig(int index, JSONObject json) throws JSONException, IllegalArgumentException {
- mIndex = index;
- mType = ViewType.fromId(json.getString(JSON_KEY_TYPE));
- mDatasetId = json.getString(JSON_KEY_DATASET);
- mItemType = ItemType.fromId(json.getString(JSON_KEY_ITEM_TYPE));
- mItemHandler = ItemHandler.fromId(json.getString(JSON_KEY_ITEM_HANDLER));
- mBackImageUrl = json.optString(JSON_KEY_BACK_IMAGE_URL, null);
- mFilter = json.optString(JSON_KEY_FILTER, null);
-
- final JSONObject jsonEmptyViewConfig = json.optJSONObject(JSON_KEY_EMPTY);
- if (jsonEmptyViewConfig != null) {
- mEmptyViewConfig = new EmptyViewConfig(jsonEmptyViewConfig);
- } else {
- mEmptyViewConfig = null;
- }
-
- final JSONObject jsonHeaderConfig = json.optJSONObject(JSON_KEY_HEADER);
- mHeaderConfig = jsonHeaderConfig != null ? new HeaderConfig(jsonHeaderConfig) : null;
-
- mFlags = EnumSet.noneOf(Flags.class);
- if (json.optBoolean(JSON_KEY_REFRESH_ENABLED, false)) {
- mFlags.add(Flags.REFRESH_ENABLED);
- }
-
- validate();
- }
-
- @SuppressWarnings("unchecked")
- public ViewConfig(Parcel in) {
- mIndex = in.readInt();
- mType = (ViewType) in.readParcelable(getClass().getClassLoader());
- mDatasetId = in.readString();
- mItemType = (ItemType) in.readParcelable(getClass().getClassLoader());
- mItemHandler = (ItemHandler) in.readParcelable(getClass().getClassLoader());
- mBackImageUrl = in.readString();
- mFilter = in.readString();
- mEmptyViewConfig = (EmptyViewConfig) in.readParcelable(getClass().getClassLoader());
- mHeaderConfig = (HeaderConfig) in.readParcelable(getClass().getClassLoader());
- mFlags = (EnumSet<Flags>) in.readSerializable();
-
- validate();
- }
-
- public ViewConfig(ViewConfig viewConfig) {
- mIndex = viewConfig.mIndex;
- mType = viewConfig.mType;
- mDatasetId = viewConfig.mDatasetId;
- mItemType = viewConfig.mItemType;
- mItemHandler = viewConfig.mItemHandler;
- mBackImageUrl = viewConfig.mBackImageUrl;
- mFilter = viewConfig.mFilter;
- mEmptyViewConfig = viewConfig.mEmptyViewConfig;
- mHeaderConfig = viewConfig.mHeaderConfig;
- mFlags = viewConfig.mFlags.clone();
-
- validate();
- }
-
- private void validate() {
- if (mType == null) {
- throw new IllegalArgumentException("Can't create ViewConfig with null type");
- }
-
- if (TextUtils.isEmpty(mDatasetId)) {
- throw new IllegalArgumentException("Can't create ViewConfig with empty dataset ID");
- }
-
- if (mItemType == null) {
- throw new IllegalArgumentException("Can't create ViewConfig with null item type");
- }
-
- if (mItemHandler == null) {
- throw new IllegalArgumentException("Can't create ViewConfig with null item handler");
- }
-
- if (mFlags == null) {
- throw new IllegalArgumentException("Can't create ViewConfig with null flags");
- }
- }
-
- public int getIndex() {
- return mIndex;
- }
-
- public ViewType getType() {
- return mType;
- }
-
- public String getDatasetId() {
- return mDatasetId;
- }
-
- public ItemType getItemType() {
- return mItemType;
- }
-
- public ItemHandler getItemHandler() {
- return mItemHandler;
- }
-
- public String getBackImageUrl() {
- return mBackImageUrl;
- }
-
- public String getFilter() {
- return mFilter;
- }
-
- public EmptyViewConfig getEmptyViewConfig() {
- return mEmptyViewConfig;
- }
-
- public HeaderConfig getHeaderConfig() {
- return mHeaderConfig;
- }
-
- public boolean hasHeaderConfig() {
- return mHeaderConfig != null;
- }
-
- public boolean isRefreshEnabled() {
- return mFlags.contains(Flags.REFRESH_ENABLED);
- }
-
- public JSONObject toJSON() throws JSONException {
- final JSONObject json = new JSONObject();
-
- json.put(JSON_KEY_TYPE, mType.toString());
- json.put(JSON_KEY_DATASET, mDatasetId);
- json.put(JSON_KEY_ITEM_TYPE, mItemType.toString());
- json.put(JSON_KEY_ITEM_HANDLER, mItemHandler.toString());
-
- if (!TextUtils.isEmpty(mBackImageUrl)) {
- json.put(JSON_KEY_BACK_IMAGE_URL, mBackImageUrl);
- }
-
- if (!TextUtils.isEmpty(mFilter)) {
- json.put(JSON_KEY_FILTER, mFilter);
- }
-
- if (mEmptyViewConfig != null) {
- json.put(JSON_KEY_EMPTY, mEmptyViewConfig.toJSON());
- }
-
- if (mHeaderConfig != null) {
- json.put(JSON_KEY_HEADER, mHeaderConfig.toJSON());
- }
-
- if (mFlags.contains(Flags.REFRESH_ENABLED)) {
- json.put(JSON_KEY_REFRESH_ENABLED, true);
- }
-
- return json;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mIndex);
- dest.writeParcelable(mType, 0);
- dest.writeString(mDatasetId);
- dest.writeParcelable(mItemType, 0);
- dest.writeParcelable(mItemHandler, 0);
- dest.writeString(mBackImageUrl);
- dest.writeString(mFilter);
- dest.writeParcelable(mEmptyViewConfig, 0);
- dest.writeParcelable(mHeaderConfig, 0);
- dest.writeSerializable(mFlags);
- }
-
- public static final Creator<ViewConfig> CREATOR = new Creator<ViewConfig>() {
- @Override
- public ViewConfig createFromParcel(final Parcel in) {
- return new ViewConfig(in);
- }
-
- @Override
- public ViewConfig[] newArray(final int size) {
- return new ViewConfig[size];
- }
- };
- }
-
- public static class EmptyViewConfig implements Parcelable {
- private final String mText;
- private final String mImageUrl;
-
- static final String JSON_KEY_TEXT = "text";
- static final String JSON_KEY_IMAGE_URL = "imageUrl";
-
- public EmptyViewConfig(JSONObject json) throws JSONException, IllegalArgumentException {
- mText = json.optString(JSON_KEY_TEXT, null);
- mImageUrl = json.optString(JSON_KEY_IMAGE_URL, null);
- }
-
- public EmptyViewConfig(Parcel in) {
- mText = in.readString();
- mImageUrl = in.readString();
- }
-
- public EmptyViewConfig(EmptyViewConfig emptyViewConfig) {
- mText = emptyViewConfig.mText;
- mImageUrl = emptyViewConfig.mImageUrl;
- }
-
- public EmptyViewConfig(String text, String imageUrl) {
- mText = text;
- mImageUrl = imageUrl;
- }
-
- public String getText() {
- return mText;
- }
-
- public String getImageUrl() {
- return mImageUrl;
- }
-
- public JSONObject toJSON() throws JSONException {
- final JSONObject json = new JSONObject();
-
- json.put(JSON_KEY_TEXT, mText);
- json.put(JSON_KEY_IMAGE_URL, mImageUrl);
-
- return json;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mText);
- dest.writeString(mImageUrl);
- }
-
- public static final Creator<EmptyViewConfig> CREATOR = new Creator<EmptyViewConfig>() {
- @Override
- public EmptyViewConfig createFromParcel(final Parcel in) {
- return new EmptyViewConfig(in);
- }
-
- @Override
- public EmptyViewConfig[] newArray(final int size) {
- return new EmptyViewConfig[size];
- }
- };
- }
-
- public static class HeaderConfig implements Parcelable {
- static final String JSON_KEY_IMAGE_URL = "image_url";
- static final String JSON_KEY_URL = "url";
-
- private final String mImageUrl;
- private final String mUrl;
-
- public HeaderConfig(JSONObject json) {
- mImageUrl = json.optString(JSON_KEY_IMAGE_URL);
- mUrl = json.optString(JSON_KEY_URL);
- }
-
- public HeaderConfig(Parcel in) {
- mImageUrl = in.readString();
- mUrl = in.readString();
- }
-
- public String getImageUrl() {
- return mImageUrl;
- }
-
- public String getUrl() {
- return mUrl;
- }
-
- public JSONObject toJSON() throws JSONException {
- JSONObject json = new JSONObject();
-
- json.put(JSON_KEY_IMAGE_URL, mImageUrl);
- json.put(JSON_KEY_URL, mUrl);
-
- return json;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mImageUrl);
- dest.writeString(mUrl);
- }
-
- public static final Creator<HeaderConfig> CREATOR = new Creator<HeaderConfig>() {
- @Override
- public HeaderConfig createFromParcel(Parcel source) {
- return new HeaderConfig(source);
- }
-
- @Override
- public HeaderConfig[] newArray(int size) {
- return new HeaderConfig[size];
- }
- };
- }
-
- public static class AuthConfig implements Parcelable {
- private final String mMessageText;
- private final String mButtonText;
- private final String mImageUrl;
-
- static final String JSON_KEY_MESSAGE_TEXT = "messageText";
- static final String JSON_KEY_BUTTON_TEXT = "buttonText";
- static final String JSON_KEY_IMAGE_URL = "imageUrl";
-
- public AuthConfig(JSONObject json) throws JSONException, IllegalArgumentException {
- mMessageText = json.optString(JSON_KEY_MESSAGE_TEXT);
- mButtonText = json.optString(JSON_KEY_BUTTON_TEXT);
- mImageUrl = json.optString(JSON_KEY_IMAGE_URL, null);
- }
-
- public AuthConfig(Parcel in) {
- mMessageText = in.readString();
- mButtonText = in.readString();
- mImageUrl = in.readString();
-
- validate();
- }
-
- public AuthConfig(AuthConfig authConfig) {
- mMessageText = authConfig.mMessageText;
- mButtonText = authConfig.mButtonText;
- mImageUrl = authConfig.mImageUrl;
-
- validate();
- }
-
- public AuthConfig(String messageText, String buttonText, String imageUrl) {
- mMessageText = messageText;
- mButtonText = buttonText;
- mImageUrl = imageUrl;
-
- validate();
- }
-
- private void validate() {
- if (mMessageText == null) {
- throw new IllegalArgumentException("Can't create AuthConfig with null message text");
- }
-
- if (mButtonText == null) {
- throw new IllegalArgumentException("Can't create AuthConfig with null button text");
- }
- }
-
- public String getMessageText() {
- return mMessageText;
- }
-
- public String getButtonText() {
- return mButtonText;
- }
-
- public String getImageUrl() {
- return mImageUrl;
- }
-
- public JSONObject toJSON() throws JSONException {
- final JSONObject json = new JSONObject();
-
- json.put(JSON_KEY_MESSAGE_TEXT, mMessageText);
- json.put(JSON_KEY_BUTTON_TEXT, mButtonText);
- json.put(JSON_KEY_IMAGE_URL, mImageUrl);
-
- return json;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mMessageText);
- dest.writeString(mButtonText);
- dest.writeString(mImageUrl);
- }
-
- public static final Creator<AuthConfig> CREATOR = new Creator<AuthConfig>() {
- @Override
- public AuthConfig createFromParcel(final Parcel in) {
- return new AuthConfig(in);
- }
-
- @Override
- public AuthConfig[] newArray(final int size) {
- return new AuthConfig[size];
- }
- };
- }
- /**
- * Immutable representation of the current state of {@code HomeConfig}.
- * This is what HomeConfig returns from a load() call and takes as
- * input to save a new state.
- *
- * Users of {@code State} should use an {@code Iterator} to iterate
- * through the contained {@code PanelConfig} instances.
- *
- * {@code State} is immutable i.e. you can't add, remove, or update
- * contained elements directly. You have to use an {@code Editor} to
- * change the state, which can be created through the {@code edit()}
- * method.
- */
- public static class State implements Iterable<PanelConfig> {
- private HomeConfig mHomeConfig;
- private final List<PanelConfig> mPanelConfigs;
- private final boolean mIsDefault;
-
- State(List<PanelConfig> panelConfigs, boolean isDefault) {
- this(null, panelConfigs, isDefault);
- }
-
- private State(HomeConfig homeConfig, List<PanelConfig> panelConfigs, boolean isDefault) {
- mHomeConfig = homeConfig;
- mPanelConfigs = Collections.unmodifiableList(panelConfigs);
- mIsDefault = isDefault;
- }
-
- private void setHomeConfig(HomeConfig homeConfig) {
- if (mHomeConfig != null) {
- throw new IllegalStateException("Can't set HomeConfig more than once");
- }
-
- mHomeConfig = homeConfig;
- }
-
- @Override
- public Iterator<PanelConfig> iterator() {
- return mPanelConfigs.iterator();
- }
-
- /**
- * Returns whether this {@code State} instance represents the default
- * {@code HomeConfig} configuration or not.
- */
- public boolean isDefault() {
- return mIsDefault;
- }
-
- /**
- * Creates an {@code Editor} for this state.
- */
- public Editor edit() {
- return new Editor(mHomeConfig, this);
- }
- }
-
- /**
- * {@code Editor} allows you to make changes to a {@code State}. You
- * can create {@code Editor} by calling {@code edit()} on the target
- * {@code State} instance.
- *
- * {@code Editor} works on a copy of the {@code State} that originated
- * it. This means that adding, removing, or updating panels in an
- * {@code Editor} will never change the {@code State} which you
- * created the {@code Editor} from. Calling {@code commit()} or
- * {@code apply()} will cause the new {@code State} instance to be
- * created and saved using the {@code HomeConfig} instance that
- * created the source {@code State}.
- *
- * {@code Editor} is *not* thread-safe. You can only make calls on it
- * from the thread where it was originally created. It will throw an
- * exception if you don't follow this invariant.
- */
- public static class Editor implements Iterable<PanelConfig> {
- private final HomeConfig mHomeConfig;
- private final Map<String, PanelConfig> mConfigMap;
- private final List<String> mConfigOrder;
- private final Thread mOriginalThread;
-
- // Each Pair represents parameters to a GeckoAppShell.notifyObservers call;
- // the first String is the observer topic and the second string is the notification data.
- private List<Pair<String, String>> mNotificationQueue;
- private PanelConfig mDefaultPanel;
- private int mEnabledCount;
-
- private boolean mHasChanged;
- private final boolean mIsFromDefault;
-
- private Editor(HomeConfig homeConfig, State configState) {
- mHomeConfig = homeConfig;
- mOriginalThread = Thread.currentThread();
- mConfigMap = new HashMap<String, PanelConfig>();
- mConfigOrder = new LinkedList<String>();
- mNotificationQueue = new ArrayList<>();
-
- mIsFromDefault = configState.isDefault();
-
- initFromState(configState);
- }
-
- /**
- * Initialize the initial state of the editor from the given
- * {@sode State}. A HashMap is used to represent the list of
- * panels as it provides fast access, and a LinkedList is used to
- * keep track of order. We keep a reference to the default panel
- * and the number of enabled panels to avoid iterating through the
- * map every time we need those.
- *
- * @param configState The source State to load the editor from.
- */
- private void initFromState(State configState) {
- for (PanelConfig panelConfig : configState) {
- final PanelConfig panelCopy = new PanelConfig(panelConfig);
-
- if (!panelCopy.isDisabled()) {
- mEnabledCount++;
- }
-
- if (panelCopy.isDefault()) {
- if (mDefaultPanel == null) {
- mDefaultPanel = panelCopy;
- } else {
- throw new IllegalStateException("Multiple default panels in HomeConfig state");
- }
- }
-
- final String panelId = panelConfig.getId();
- mConfigOrder.add(panelId);
- mConfigMap.put(panelId, panelCopy);
- }
-
- // We should always have a defined default panel if there's
- // at least one enabled panel around.
- if (mEnabledCount > 0 && mDefaultPanel == null) {
- throw new IllegalStateException("Default panel in HomeConfig state is undefined");
- }
- }
-
- private PanelConfig getPanelOrThrow(String panelId) {
- final PanelConfig panelConfig = mConfigMap.get(panelId);
- if (panelConfig == null) {
- throw new IllegalStateException("Tried to access non-existing panel: " + panelId);
- }
-
- return panelConfig;
- }
-
- private boolean isCurrentDefaultPanel(PanelConfig panelConfig) {
- if (mDefaultPanel == null) {
- return false;
- }
-
- return mDefaultPanel.equals(panelConfig);
- }
-
- private void findNewDefault() {
- // Pick the first panel that is neither disabled nor currently
- // set as default.
- for (PanelConfig panelConfig : makeOrderedCopy(false)) {
- if (!panelConfig.isDefault() && !panelConfig.isDisabled()) {
- setDefault(panelConfig.getId());
- return;
- }
- }
-
- mDefaultPanel = null;
- }
-
- /**
- * Makes an ordered list of PanelConfigs that can be references
- * or deep copied objects.
- *
- * @param deepCopy true to make deep-copied objects
- * @return ordered List of PanelConfigs
- */
- private List<PanelConfig> makeOrderedCopy(boolean deepCopy) {
- final List<PanelConfig> copiedList = new ArrayList<PanelConfig>(mConfigOrder.size());
- for (String panelId : mConfigOrder) {
- PanelConfig panelConfig = mConfigMap.get(panelId);
- if (deepCopy) {
- panelConfig = new PanelConfig(panelConfig);
- }
- copiedList.add(panelConfig);
- }
-
- return copiedList;
- }
-
- private void setPanelIsDisabled(PanelConfig panelConfig, boolean disabled) {
- if (panelConfig.isDisabled() == disabled) {
- return;
- }
-
- panelConfig.setIsDisabled(disabled);
- mEnabledCount += (disabled ? -1 : 1);
- }
-
- /**
- * Gets the ID of the current default panel.
- */
- public String getDefaultPanelId() {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- if (mDefaultPanel == null) {
- return null;
- }
-
- return mDefaultPanel.getId();
- }
-
- /**
- * Set a new default panel.
- *
- * @param panelId the ID of the new default panel.
- */
- public void setDefault(String panelId) {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- final PanelConfig panelConfig = getPanelOrThrow(panelId);
- if (isCurrentDefaultPanel(panelConfig)) {
- return;
- }
-
- if (mDefaultPanel != null) {
- mDefaultPanel.setIsDefault(false);
- }
-
- panelConfig.setIsDefault(true);
- setPanelIsDisabled(panelConfig, false);
-
- mDefaultPanel = panelConfig;
- mHasChanged = true;
- }
-
- /**
- * Toggles disabled state for a panel.
- *
- * @param panelId the ID of the target panel.
- * @param disabled true to disable the panel.
- */
- public void setDisabled(String panelId, boolean disabled) {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- final PanelConfig panelConfig = getPanelOrThrow(panelId);
- if (panelConfig.isDisabled() == disabled) {
- return;
- }
-
- setPanelIsDisabled(panelConfig, disabled);
-
- if (disabled) {
- if (isCurrentDefaultPanel(panelConfig)) {
- panelConfig.setIsDefault(false);
- findNewDefault();
- }
- } else if (mEnabledCount == 1) {
- setDefault(panelId);
- }
-
- mHasChanged = true;
- }
-
- /**
- * Adds a new {@code PanelConfig}. It will do nothing if the
- * {@code Editor} already contains a panel with the same ID.
- *
- * @param panelConfig the {@code PanelConfig} instance to be added.
- * @return true if the item has been added.
- */
- public boolean install(PanelConfig panelConfig) {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- if (panelConfig == null) {
- throw new IllegalStateException("Can't install a null panel");
- }
-
- if (!panelConfig.isDynamic()) {
- throw new IllegalStateException("Can't install a built-in panel: " + panelConfig.getId());
- }
-
- if (panelConfig.isDisabled()) {
- throw new IllegalStateException("Can't install a disabled panel: " + panelConfig.getId());
- }
-
- boolean installed = false;
-
- final String id = panelConfig.getId();
- if (!mConfigMap.containsKey(id)) {
- mConfigMap.put(id, panelConfig);
-
- final int position = panelConfig.getPosition();
- if (position < 0 || position >= mConfigOrder.size()) {
- mConfigOrder.add(id);
- } else {
- mConfigOrder.add(position, id);
- }
-
- mEnabledCount++;
- if (mEnabledCount == 1 || panelConfig.isDefault()) {
- setDefault(panelConfig.getId());
- }
-
- installed = true;
-
- // Add an event to the queue if a new panel is successfully installed.
- mNotificationQueue.add(new Pair<String, String>(
- "HomePanels:Installed", panelConfig.getId()));
- }
-
- mHasChanged = true;
- return installed;
- }
-
- /**
- * Removes an existing panel.
- *
- * @return true if the item has been removed.
- */
- public boolean uninstall(String panelId) {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- final PanelConfig panelConfig = mConfigMap.get(panelId);
- if (panelConfig == null) {
- return false;
- }
-
- if (!panelConfig.isDynamic()) {
- throw new IllegalStateException("Can't uninstall a built-in panel: " + panelConfig.getId());
- }
-
- mConfigMap.remove(panelId);
- mConfigOrder.remove(panelId);
-
- if (!panelConfig.isDisabled()) {
- mEnabledCount--;
- }
-
- if (isCurrentDefaultPanel(panelConfig)) {
- findNewDefault();
- }
-
- // Add an event to the queue if a panel is successfully uninstalled.
- mNotificationQueue.add(new Pair<String, String>("HomePanels:Uninstalled", panelId));
-
- mHasChanged = true;
- return true;
- }
-
- /**
- * Moves panel associated with panelId to the specified position.
- *
- * @param panelId Id of panel
- * @param destIndex Destination position
- * @return true if move succeeded
- */
- public boolean moveTo(String panelId, int destIndex) {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- if (!mConfigOrder.contains(panelId)) {
- return false;
- }
-
- mConfigOrder.remove(panelId);
- mConfigOrder.add(destIndex, panelId);
- mHasChanged = true;
-
- return true;
- }
-
- /**
- * Replaces an existing panel with a new {@code PanelConfig} instance.
- *
- * @return true if the item has been updated.
- */
- public boolean update(PanelConfig panelConfig) {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- if (panelConfig == null) {
- throw new IllegalStateException("Can't update a null panel");
- }
-
- boolean updated = false;
-
- final String id = panelConfig.getId();
- if (mConfigMap.containsKey(id)) {
- final PanelConfig oldPanelConfig = mConfigMap.put(id, panelConfig);
-
- // The disabled and default states can't never be
- // changed by an update operation.
- panelConfig.setIsDefault(oldPanelConfig.isDefault());
- panelConfig.setIsDisabled(oldPanelConfig.isDisabled());
-
- updated = true;
- }
-
- mHasChanged = true;
- return updated;
- }
-
- /**
- * Saves the current {@code Editor} state asynchronously in the
- * background thread.
- *
- * @return the resulting {@code State} instance.
- */
- public State apply() {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- // We're about to save the current state in the background thread
- // so we should use a deep copy of the PanelConfig instances to
- // avoid saving corrupted state.
- final State newConfigState =
- new State(mHomeConfig, makeOrderedCopy(true), isDefault());
-
- // Copy the event queue to a new list, so that we only modify mNotificationQueue on
- // the original thread where it was created.
- final List<Pair<String, String>> copiedQueue = mNotificationQueue;
- mNotificationQueue = new ArrayList<>();
-
- ThreadUtils.getBackgroundHandler().post(new Runnable() {
- @Override
- public void run() {
- mHomeConfig.save(newConfigState);
-
- // Send pending events after the new config is saved.
- sendNotificationsToGecko(copiedQueue);
- }
- });
-
- return newConfigState;
- }
-
- /**
- * Saves the current {@code Editor} state synchronously in the
- * current thread.
- *
- * @return the resulting {@code State} instance.
- */
- public State commit() {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- final State newConfigState =
- new State(mHomeConfig, makeOrderedCopy(false), isDefault());
-
- // This is a synchronous blocking operation, hence no
- // need to deep copy the current PanelConfig instances.
- mHomeConfig.save(newConfigState);
-
- // Send pending events after the new config is saved.
- sendNotificationsToGecko(mNotificationQueue);
- mNotificationQueue.clear();
-
- return newConfigState;
- }
-
- /**
- * Returns whether the {@code Editor} represents the default
- * {@code HomeConfig} configuration without any unsaved changes.
- */
- public boolean isDefault() {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- return (!mHasChanged && mIsFromDefault);
- }
-
- public boolean isEmpty() {
- return mConfigMap.isEmpty();
- }
-
- private void sendNotificationsToGecko(List<Pair<String, String>> notifications) {
- for (Pair<String, String> p : notifications) {
- GeckoAppShell.notifyObservers(p.first, p.second);
- }
- }
-
- private class EditorIterator implements Iterator<PanelConfig> {
- private final Iterator<String> mOrderIterator;
-
- public EditorIterator() {
- mOrderIterator = mConfigOrder.iterator();
- }
-
- @Override
- public boolean hasNext() {
- return mOrderIterator.hasNext();
- }
-
- @Override
- public PanelConfig next() {
- final String panelId = mOrderIterator.next();
- return mConfigMap.get(panelId);
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException("Can't 'remove' from on Editor iterator.");
- }
- }
-
- @Override
- public Iterator<PanelConfig> iterator() {
- ThreadUtils.assertOnThread(mOriginalThread);
-
- return new EditorIterator();
- }
- }
-
- public interface OnReloadListener {
- public void onReload();
- }
-
- public interface HomeConfigBackend {
- public State load();
- public void save(State configState);
- public String getLocale();
- public void setOnReloadListener(OnReloadListener listener);
- }
-
- // UUIDs used to create PanelConfigs for default built-in panels. These are
- // public because they can be used in "about:home?panel=UUID" query strings
- // to open specific panels without querying the active Home Panel
- // configuration. Because they don't consider the active configuration, it
- // is only sensible to do this for built-in panels (and not for dynamic
- // panels).
- private static final String TOP_SITES_PANEL_ID = "4becc86b-41eb-429a-a042-88fe8b5a094e";
- private static final String BOOKMARKS_PANEL_ID = "7f6d419a-cd6c-4e34-b26f-f68b1b551907";
- private static final String HISTORY_PANEL_ID = "f134bf20-11f7-4867-ab8b-e8e705d7fbe8";
- private static final String COMBINED_HISTORY_PANEL_ID = "4d716ce2-e063-486d-9e7c-b190d7b04dc6";
- private static final String RECENT_TABS_PANEL_ID = "5c2601a5-eedc-4477-b297-ce4cef52adf8";
- private static final String REMOTE_TABS_PANEL_ID = "72429afd-8d8b-43d8-9189-14b779c563d0";
- private static final String DEPRECATED_READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b";
-
- private final HomeConfigBackend mBackend;
-
- public HomeConfig(HomeConfigBackend backend) {
- mBackend = backend;
- }
-
- public State load() {
- final State configState = mBackend.load();
- configState.setHomeConfig(this);
-
- return configState;
- }
-
- public String getLocale() {
- return mBackend.getLocale();
- }
-
- public void save(State configState) {
- mBackend.save(configState);
- }
-
- public void setOnReloadListener(OnReloadListener listener) {
- mBackend.setOnReloadListener(listener);
- }
-
- public static PanelConfig createBuiltinPanelConfig(Context context, PanelType panelType) {
- return createBuiltinPanelConfig(context, panelType, EnumSet.noneOf(PanelConfig.Flags.class));
- }
-
- public static int getTitleResourceIdForBuiltinPanelType(PanelType panelType) {
- switch (panelType) {
- case TOP_SITES:
- return R.string.home_top_sites_title;
-
- case BOOKMARKS:
- case DEPRECATED_READING_LIST:
- return R.string.bookmarks_title;
-
- case DEPRECATED_HISTORY:
- case DEPRECATED_REMOTE_TABS:
- case DEPRECATED_RECENT_TABS:
- case COMBINED_HISTORY:
- return R.string.home_history_title;
-
- default:
- throw new IllegalArgumentException("Only for built-in panel types: " + panelType);
- }
- }
-
- public static String getIdForBuiltinPanelType(PanelType panelType) {
- switch (panelType) {
- case TOP_SITES:
- return TOP_SITES_PANEL_ID;
-
- case BOOKMARKS:
- return BOOKMARKS_PANEL_ID;
-
- case DEPRECATED_HISTORY:
- return HISTORY_PANEL_ID;
-
- case COMBINED_HISTORY:
- return COMBINED_HISTORY_PANEL_ID;
-
- case DEPRECATED_REMOTE_TABS:
- return REMOTE_TABS_PANEL_ID;
-
- case DEPRECATED_READING_LIST:
- return DEPRECATED_READING_LIST_PANEL_ID;
-
- case DEPRECATED_RECENT_TABS:
- return RECENT_TABS_PANEL_ID;
-
- default:
- throw new IllegalArgumentException("Only for built-in panel types: " + panelType);
- }
- }
-
- public static PanelConfig createBuiltinPanelConfig(Context context, PanelType panelType, EnumSet<PanelConfig.Flags> flags) {
- final int titleId = getTitleResourceIdForBuiltinPanelType(panelType);
- final String id = getIdForBuiltinPanelType(panelType);
-
- return new PanelConfig(panelType, context.getString(titleId), id, flags);
- }
-
- public static HomeConfig getDefault(Context context) {
- return new HomeConfig(new HomeConfigPrefsBackend(context));
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeConfigLoader.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeConfigLoader.java
deleted file mode 100644
index 914d0fdd1..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeConfigLoader.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.home.HomeConfig.OnReloadListener;
-
-import android.content.Context;
-import android.support.v4.content.AsyncTaskLoader;
-
-public class HomeConfigLoader extends AsyncTaskLoader<HomeConfig.State> {
- private final HomeConfig mConfig;
- private HomeConfig.State mConfigState;
-
- private final Context mContext;
-
- public HomeConfigLoader(Context context, HomeConfig homeConfig) {
- super(context);
- mContext = context;
- mConfig = homeConfig;
- }
-
- @Override
- public HomeConfig.State loadInBackground() {
- return mConfig.load();
- }
-
- @Override
- public void deliverResult(HomeConfig.State configState) {
- if (isReset()) {
- mConfigState = null;
- return;
- }
-
- mConfigState = configState;
- mConfig.setOnReloadListener(new ForceReloadListener());
-
- if (isStarted()) {
- super.deliverResult(configState);
- }
- }
-
- @Override
- protected void onStartLoading() {
- if (mConfigState != null) {
- deliverResult(mConfigState);
- }
-
- if (takeContentChanged() || mConfigState == null) {
- forceLoad();
- }
- }
-
- @Override
- protected void onStopLoading() {
- cancelLoad();
- }
-
- @Override
- public void onCanceled(HomeConfig.State configState) {
- mConfigState = null;
- }
-
- @Override
- protected void onReset() {
- super.onReset();
-
- // Ensure the loader is stopped.
- onStopLoading();
-
- mConfigState = null;
- mConfig.setOnReloadListener(null);
- }
-
- private class ForceReloadListener implements OnReloadListener {
- @Override
- public void onReload() {
- onContentChanged();
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeConfigPrefsBackend.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeConfigPrefsBackend.java
deleted file mode 100644
index a2d80788c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeConfigPrefsBackend.java
+++ /dev/null
@@ -1,663 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import static org.mozilla.gecko.home.HomeConfig.createBuiltinPanelConfig;
-
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.Locale;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.home.HomeConfig.HomeConfigBackend;
-import org.mozilla.gecko.home.HomeConfig.OnReloadListener;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.home.HomeConfig.PanelType;
-import org.mozilla.gecko.home.HomeConfig.State;
-import org.mozilla.gecko.util.HardwareUtils;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.support.annotation.CheckResult;
-import android.support.annotation.VisibleForTesting;
-import android.support.v4.content.LocalBroadcastManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-public class HomeConfigPrefsBackend implements HomeConfigBackend {
- private static final String LOGTAG = "GeckoHomeConfigBackend";
-
- // Increment this to trigger a migration.
- @VisibleForTesting
- static final int VERSION = 8;
-
- // This key was originally used to store only an array of panel configs.
- public static final String PREFS_CONFIG_KEY_OLD = "home_panels";
-
- // This key is now used to store a version number with the array of panel configs.
- public static final String PREFS_CONFIG_KEY = "home_panels_with_version";
-
- // Keys used with JSON object stored in prefs.
- private static final String JSON_KEY_PANELS = "panels";
- private static final String JSON_KEY_VERSION = "version";
-
- private static final String PREFS_LOCALE_KEY = "home_locale";
-
- private static final String RELOAD_BROADCAST = "HomeConfigPrefsBackend:Reload";
-
- private final Context mContext;
- private ReloadBroadcastReceiver mReloadBroadcastReceiver;
- private OnReloadListener mReloadListener;
-
- private static boolean sMigrationDone;
-
- public HomeConfigPrefsBackend(Context context) {
- mContext = context;
- }
-
- private SharedPreferences getSharedPreferences() {
- return GeckoSharedPrefs.forProfile(mContext);
- }
-
- private State loadDefaultConfig() {
- final ArrayList<PanelConfig> panelConfigs = new ArrayList<PanelConfig>();
-
- panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.TOP_SITES,
- EnumSet.of(PanelConfig.Flags.DEFAULT_PANEL)));
-
- panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.BOOKMARKS));
- panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.COMBINED_HISTORY));
-
-
- return new State(panelConfigs, true);
- }
-
- /**
- * Iterate through the panels to check if they are all disabled.
- */
- private static boolean allPanelsAreDisabled(JSONArray jsonPanels) throws JSONException {
- final int count = jsonPanels.length();
- for (int i = 0; i < count; i++) {
- final JSONObject jsonPanelConfig = jsonPanels.getJSONObject(i);
-
- if (!jsonPanelConfig.optBoolean(PanelConfig.JSON_KEY_DISABLED, false)) {
- return false;
- }
- }
-
- return true;
- }
-
- protected enum Position {
- NONE, // Not present.
- FRONT, // At the front of the list of panels.
- BACK, // At the back of the list of panels.
- }
-
- /**
- * Create and insert a built-in panel configuration.
- *
- * @param context Android context.
- * @param jsonPanels array of JSON panels to update in place.
- * @param panelType to add.
- * @param positionOnPhones where to place the new panel on phones.
- * @param positionOnTablets where to place the new panel on tablets.
- * @throws JSONException
- */
- protected static void addBuiltinPanelConfig(Context context, JSONArray jsonPanels,
- PanelType panelType, Position positionOnPhones, Position positionOnTablets) throws JSONException {
- // Add the new panel.
- final JSONObject jsonPanelConfig =
- createBuiltinPanelConfig(context, panelType).toJSON();
-
- // If any panel is enabled, then we should make the new panel enabled.
- jsonPanelConfig.put(PanelConfig.JSON_KEY_DISABLED,
- allPanelsAreDisabled(jsonPanels));
-
- final boolean isTablet = HardwareUtils.isTablet();
- final boolean isPhone = !isTablet;
-
- // Maybe add the new panel to the front of the array.
- if ((isPhone && positionOnPhones == Position.FRONT) ||
- (isTablet && positionOnTablets == Position.FRONT)) {
- // This is an inefficient way to stretch [a, b, c] to [a, a, b, c].
- for (int i = jsonPanels.length(); i >= 1; i--) {
- jsonPanels.put(i, jsonPanels.get(i - 1));
- }
- // And this inserts [d, a, b, c].
- jsonPanels.put(0, jsonPanelConfig);
- }
-
- // Maybe add the new panel to the back of the array.
- if ((isPhone && positionOnPhones == Position.BACK) ||
- (isTablet && positionOnTablets == Position.BACK)) {
- jsonPanels.put(jsonPanelConfig);
- }
- }
-
- /**
- * Updates the panels to combine the History and Sync panels into the (Combined) History panel.
- *
- * Tries to replace the History panel with the Combined History panel if visible, or falls back to
- * replacing the Sync panel if it's visible. That way, we minimize panel reordering during a migration.
- * @param context Android context
- * @param jsonPanels array of original JSON panels
- * @return new array of updated JSON panels
- * @throws JSONException
- */
- private static JSONArray combineHistoryAndSyncPanels(Context context, JSONArray jsonPanels) throws JSONException {
- EnumSet<PanelConfig.Flags> historyFlags = null;
- EnumSet<PanelConfig.Flags> syncFlags = null;
-
- int historyIndex = -1;
- int syncIndex = -1;
-
- // Determine state and location of History and Sync panels.
- for (int i = 0; i < jsonPanels.length(); i++) {
- JSONObject panelObj = jsonPanels.getJSONObject(i);
- final PanelConfig panelConfig = new PanelConfig(panelObj);
- final PanelType type = panelConfig.getType();
- if (type == PanelType.DEPRECATED_HISTORY) {
- historyIndex = i;
- historyFlags = panelConfig.getFlags();
- } else if (type == PanelType.DEPRECATED_REMOTE_TABS) {
- syncIndex = i;
- syncFlags = panelConfig.getFlags();
- } else if (type == PanelType.COMBINED_HISTORY) {
- // Partial landing of bug 1220928 combined the History and Sync panels of users who didn't
- // have home panel customizations (including new users), thus they don't this migration.
- return jsonPanels;
- }
- }
-
- if (historyIndex == -1 || syncIndex == -1) {
- throw new IllegalArgumentException("Expected both History and Sync panels to be present prior to Combined History.");
- }
-
- PanelConfig newPanel;
- int replaceIndex;
- int removeIndex;
-
- if (historyFlags.contains(PanelConfig.Flags.DISABLED_PANEL) && !syncFlags.contains(PanelConfig.Flags.DISABLED_PANEL)) {
- // Replace the Sync panel if it's visible and the History panel is disabled.
- replaceIndex = syncIndex;
- removeIndex = historyIndex;
- newPanel = createBuiltinPanelConfig(context, PanelType.COMBINED_HISTORY, syncFlags);
- } else {
- // Otherwise, just replace the History panel.
- replaceIndex = historyIndex;
- removeIndex = syncIndex;
- newPanel = createBuiltinPanelConfig(context, PanelType.COMBINED_HISTORY, historyFlags);
- }
-
- // Copy the array with updated panel and removed panel.
- final JSONArray newArray = new JSONArray();
- for (int i = 0; i < jsonPanels.length(); i++) {
- if (i == replaceIndex) {
- newArray.put(newPanel.toJSON());
- } else if (i == removeIndex) {
- continue;
- } else {
- newArray.put(jsonPanels.get(i));
- }
- }
-
- return newArray;
- }
-
- /**
- * Iterate over all homepanels to verify that there is at least one default panel. If there is
- * no default panel, set History as the default panel. (This is only relevant for two botched
- * migrations where the history panel should have been made the default panel, but wasn't.)
- */
- private static void ensureDefaultPanelForV5orV8(Context context, JSONArray jsonPanels) throws JSONException {
- int historyIndex = -1;
-
- // If all panels are disabled, there is no default panel - this is the only valid state
- // that has no default. We can use this flag to track whether any visible panels have been
- // found.
- boolean enabledPanelsFound = false;
-
- for (int i = 0; i < jsonPanels.length(); i++) {
- final PanelConfig panelConfig = new PanelConfig(jsonPanels.getJSONObject(i));
- if (panelConfig.isDefault()) {
- return;
- }
-
- if (!panelConfig.isDisabled()) {
- enabledPanelsFound = true;
- }
-
- if (panelConfig.getType() == PanelType.COMBINED_HISTORY) {
- historyIndex = i;
- }
- }
-
- if (!enabledPanelsFound) {
- // No panels are enabled, hence there can be no default (see noEnabledPanelsFound declaration
- // for more information).
- return;
- }
-
- // Make the History panel default. We can't modify existing PanelConfigs, so make a new one.
- final PanelConfig historyPanelConfig = createBuiltinPanelConfig(context, PanelType.COMBINED_HISTORY, EnumSet.of(PanelConfig.Flags.DEFAULT_PANEL));
- jsonPanels.put(historyIndex, historyPanelConfig.toJSON());
- }
-
- /**
- * Removes a panel from the home panel config.
- * If the removed panel was set as the default home panel, we provide a replacement for it.
- *
- * @param context Android context
- * @param jsonPanels array of original JSON panels
- * @param panelToRemove The home panel to be removed.
- * @param replacementPanel The panel which will replace it if the removed panel
- * was the default home panel.
- * @param alwaysUnhide If true, the replacement panel will always be unhidden,
- * otherwise only if we turn it into the new default panel.
- * @return new array of updated JSON panels
- * @throws JSONException
- */
- private static JSONArray removePanel(Context context, JSONArray jsonPanels,
- PanelType panelToRemove, PanelType replacementPanel, boolean alwaysUnhide) throws JSONException {
- boolean wasDefault = false;
- boolean wasDisabled = false;
- int replacementPanelIndex = -1;
- boolean replacementWasDefault = false;
-
- // JSONArrary doesn't provide remove() for API < 19, therefore we need to manually copy all
- // the items we don't want deleted into a new array.
- final JSONArray newJSONPanels = new JSONArray();
-
- for (int i = 0; i < jsonPanels.length(); i++) {
- final JSONObject panelJSON = jsonPanels.getJSONObject(i);
- final PanelConfig panelConfig = new PanelConfig(panelJSON);
-
- if (panelConfig.getType() == panelToRemove) {
- // If this panel was the default we'll need to assign a new default:
- wasDefault = panelConfig.isDefault();
- wasDisabled = panelConfig.isDisabled();
- } else {
- if (panelConfig.getType() == replacementPanel) {
- replacementPanelIndex = newJSONPanels.length();
- if (panelConfig.isDefault()) {
- replacementWasDefault = true;
- }
- }
-
- newJSONPanels.put(panelJSON);
- }
- }
-
- // Unless alwaysUnhide is true, we make the replacement panel visible only if it is going
- // to be the new default panel, since a hidden default panel doesn't make sense.
- // This is to allow preserving the behaviour of the original reading list migration function.
- if ((wasDefault || alwaysUnhide) && !wasDisabled) {
- final JSONObject replacementPanelConfig;
- if (wasDefault) {
- // If the removed panel was the default, the replacement has to be made the new default
- replacementPanelConfig = createBuiltinPanelConfig(context, replacementPanel, EnumSet.of(PanelConfig.Flags.DEFAULT_PANEL)).toJSON();
- } else {
- final EnumSet<HomeConfig.PanelConfig.Flags> flags;
- if (replacementWasDefault) {
- // However if the replacement panel was already default, we need to preserve it's default status
- // (By rewriting the PanelConfig, we lose all existing flags, so we need to make sure desired
- // flags are retained - in this case there's only DEFAULT_PANEL, which is mutually
- // exclusive with the DISABLE_PANEL case).
- flags = EnumSet.of(PanelConfig.Flags.DEFAULT_PANEL);
- } else {
- flags = EnumSet.noneOf(PanelConfig.Flags.class);
- }
-
- // The panel is visible since we don't set Flags.DISABLED_PANEL.
- replacementPanelConfig = createBuiltinPanelConfig(context, replacementPanel, flags).toJSON();
- }
-
- if (replacementPanelIndex != -1) {
- newJSONPanels.put(replacementPanelIndex, replacementPanelConfig);
- } else {
- newJSONPanels.put(replacementPanelConfig);
- }
- }
-
- return newJSONPanels;
- }
-
- /**
- * Checks to see if the reading list panel already exists.
- *
- * @param jsonPanels JSONArray array representing the curent set of panel configs.
- *
- * @return boolean Whether or not the reading list panel exists.
- */
- private static boolean readingListPanelExists(JSONArray jsonPanels) {
- final int count = jsonPanels.length();
- for (int i = 0; i < count; i++) {
- try {
- final JSONObject jsonPanelConfig = jsonPanels.getJSONObject(i);
- final PanelConfig panelConfig = new PanelConfig(jsonPanelConfig);
- if (panelConfig.getType() == PanelType.DEPRECATED_READING_LIST) {
- return true;
- }
- } catch (Exception e) {
- // It's okay to ignore this exception, since an invalid reading list
- // panel config is equivalent to no reading list panel.
- Log.e(LOGTAG, "Exception loading PanelConfig from JSON", e);
- }
- }
- return false;
- }
-
- @CheckResult
- static synchronized JSONArray migratePrefsFromVersionToVersion(final Context context, final int currentVersion, final int newVersion,
- final JSONArray jsonPanelsIn, final SharedPreferences.Editor prefsEditor) throws JSONException {
-
- JSONArray jsonPanels = jsonPanelsIn;
-
- for (int v = currentVersion + 1; v <= newVersion; v++) {
- Log.d(LOGTAG, "Migrating to version = " + v);
-
- switch (v) {
- case 1:
- // Add "Recent Tabs" panel.
- addBuiltinPanelConfig(context, jsonPanels,
- PanelType.DEPRECATED_RECENT_TABS, Position.FRONT, Position.BACK);
-
- // Remove the old pref key.
- prefsEditor.remove(PREFS_CONFIG_KEY_OLD);
- break;
-
- case 2:
- // Add "Remote Tabs"/"Synced Tabs" panel.
- addBuiltinPanelConfig(context, jsonPanels,
- PanelType.DEPRECATED_REMOTE_TABS, Position.FRONT, Position.BACK);
- break;
-
- case 3:
- // Add the "Reading List" panel if it does not exist. At one time,
- // the Reading List panel was shown only to devices that were not
- // considered "low memory". Now, we expose the panel to all devices.
- // This migration should only occur for "low memory" devices.
- // Note: This will not agree with the default configuration, which
- // has DEPRECATED_REMOTE_TABS after DEPRECATED_READING_LIST on some devices.
- if (!readingListPanelExists(jsonPanels)) {
- addBuiltinPanelConfig(context, jsonPanels,
- PanelType.DEPRECATED_READING_LIST, Position.BACK, Position.BACK);
- }
- break;
-
- case 4:
- // Combine the History and Sync panels. In order to minimize an unexpected reordering
- // of panels, we try to replace the History panel if it's visible, and fall back to
- // the Sync panel if that's visible.
- jsonPanels = combineHistoryAndSyncPanels(context, jsonPanels);
- break;
-
- case 5:
- // This is the fix for bug 1264136 where we lost track of the default panel during some migrations.
- ensureDefaultPanelForV5orV8(context, jsonPanels);
- break;
-
- case 6:
- jsonPanels = removePanel(context, jsonPanels,
- PanelType.DEPRECATED_READING_LIST, PanelType.BOOKMARKS, false);
- break;
-
- case 7:
- jsonPanels = removePanel(context, jsonPanels,
- PanelType.DEPRECATED_RECENT_TABS, PanelType.COMBINED_HISTORY, true);
- break;
-
- case 8:
- // Similar to "case 5" above, this time 1304777 - once again we lost track
- // of the history panel
- ensureDefaultPanelForV5orV8(context, jsonPanels);
- break;
- }
- }
-
- return jsonPanels;
- }
-
- /**
- * Migrates JSON config data storage.
- *
- * @param context Context used to get shared preferences and create built-in panel.
- * @param jsonString String currently stored in preferences.
- *
- * @return JSONArray array representing new set of panel configs.
- */
- private static synchronized JSONArray maybePerformMigration(Context context, String jsonString) throws JSONException {
- // If the migration is already done, we're at the current version.
- if (sMigrationDone) {
- final JSONObject json = new JSONObject(jsonString);
- return json.getJSONArray(JSON_KEY_PANELS);
- }
-
- // Make sure we only do this version check once.
- sMigrationDone = true;
-
- JSONArray jsonPanels;
- final int version;
-
- final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
- if (prefs.contains(PREFS_CONFIG_KEY_OLD)) {
- // Our original implementation did not contain versioning, so this is implicitly version 0.
- jsonPanels = new JSONArray(jsonString);
- version = 0;
- } else {
- final JSONObject json = new JSONObject(jsonString);
- jsonPanels = json.getJSONArray(JSON_KEY_PANELS);
- version = json.getInt(JSON_KEY_VERSION);
- }
-
- if (version == VERSION) {
- return jsonPanels;
- }
-
- Log.d(LOGTAG, "Performing migration");
-
- final SharedPreferences.Editor prefsEditor = prefs.edit();
-
- jsonPanels = migratePrefsFromVersionToVersion(context, version, VERSION, jsonPanels, prefsEditor);
-
- // Save the new panel config and the new version number.
- final JSONObject newJson = new JSONObject();
- newJson.put(JSON_KEY_PANELS, jsonPanels);
- newJson.put(JSON_KEY_VERSION, VERSION);
-
- prefsEditor.putString(PREFS_CONFIG_KEY, newJson.toString());
- prefsEditor.apply();
-
- return jsonPanels;
- }
-
- private State loadConfigFromString(String jsonString) {
- final JSONArray jsonPanelConfigs;
- try {
- jsonPanelConfigs = maybePerformMigration(mContext, jsonString);
- updatePrefsFromConfig(jsonPanelConfigs);
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error loading the list of home panels from JSON prefs", e);
-
- // Fallback to default config
- return loadDefaultConfig();
- }
-
- final ArrayList<PanelConfig> panelConfigs = new ArrayList<PanelConfig>();
-
- final int count = jsonPanelConfigs.length();
- for (int i = 0; i < count; i++) {
- try {
- final JSONObject jsonPanelConfig = jsonPanelConfigs.getJSONObject(i);
- final PanelConfig panelConfig = new PanelConfig(jsonPanelConfig);
- panelConfigs.add(panelConfig);
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception loading PanelConfig from JSON", e);
- }
- }
-
- return new State(panelConfigs, false);
- }
-
- @Override
- public State load() {
- final SharedPreferences prefs = getSharedPreferences();
-
- final String key = (prefs.contains(PREFS_CONFIG_KEY_OLD) ? PREFS_CONFIG_KEY_OLD : PREFS_CONFIG_KEY);
- final String jsonString = prefs.getString(key, null);
-
- final State configState;
- if (TextUtils.isEmpty(jsonString)) {
- configState = loadDefaultConfig();
- } else {
- configState = loadConfigFromString(jsonString);
- }
-
- return configState;
- }
-
- @Override
- public void save(State configState) {
- final SharedPreferences prefs = getSharedPreferences();
- final SharedPreferences.Editor editor = prefs.edit();
-
- // No need to save the state to disk if it represents the default
- // HomeConfig configuration. Simply force all existing HomeConfigLoader
- // instances to refresh their contents.
- if (!configState.isDefault()) {
- final JSONArray jsonPanelConfigs = new JSONArray();
-
- for (PanelConfig panelConfig : configState) {
- try {
- final JSONObject jsonPanelConfig = panelConfig.toJSON();
- jsonPanelConfigs.put(jsonPanelConfig);
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception converting PanelConfig to JSON", e);
- }
- }
-
- try {
- final JSONObject json = new JSONObject();
- json.put(JSON_KEY_PANELS, jsonPanelConfigs);
- json.put(JSON_KEY_VERSION, VERSION);
-
- editor.putString(PREFS_CONFIG_KEY, json.toString());
- } catch (JSONException e) {
- Log.e(LOGTAG, "Exception saving PanelConfig state", e);
- }
- }
-
- editor.putString(PREFS_LOCALE_KEY, Locale.getDefault().toString());
- editor.apply();
-
- // Trigger reload listeners on all live backend instances
- sendReloadBroadcast();
- }
-
- @Override
- public String getLocale() {
- final SharedPreferences prefs = getSharedPreferences();
-
- String locale = prefs.getString(PREFS_LOCALE_KEY, null);
- if (locale == null) {
- // Initialize config with the current locale
- final String currentLocale = Locale.getDefault().toString();
-
- final SharedPreferences.Editor editor = prefs.edit();
- editor.putString(PREFS_LOCALE_KEY, currentLocale);
- editor.apply();
-
- // If the user has saved HomeConfig before, return null this
- // one time to trigger a refresh and ensure we use the
- // correct locale for the saved state. For more context,
- // see HomePanelsManager.onLocaleReady().
- if (!prefs.contains(PREFS_CONFIG_KEY)) {
- locale = currentLocale;
- }
- }
-
- return locale;
- }
-
- @Override
- public void setOnReloadListener(OnReloadListener listener) {
- if (mReloadListener != null) {
- unregisterReloadReceiver();
- mReloadBroadcastReceiver = null;
- }
-
- mReloadListener = listener;
-
- if (mReloadListener != null) {
- mReloadBroadcastReceiver = new ReloadBroadcastReceiver();
- registerReloadReceiver();
- }
- }
-
- /**
- * Update prefs that depend on home panels state.
- *
- * This includes the prefs that keep track of whether bookmarks or history are enabled, which are
- * used to control the visibility of the corresponding menu items.
- */
- private void updatePrefsFromConfig(JSONArray panelsArray) {
- final SharedPreferences prefs = GeckoSharedPrefs.forProfile(mContext);
- if (!prefs.contains(HomeConfig.PREF_KEY_BOOKMARKS_PANEL_ENABLED)
- || !prefs.contains(HomeConfig.PREF_KEY_HISTORY_PANEL_ENABLED)) {
-
- final String bookmarkType = PanelType.BOOKMARKS.toString();
- final String historyType = PanelType.COMBINED_HISTORY.toString();
- try {
- for (int i = 0; i < panelsArray.length(); i++) {
- final JSONObject panelObj = panelsArray.getJSONObject(i);
- final String panelType = panelObj.optString(PanelConfig.JSON_KEY_TYPE, null);
- if (panelType == null) {
- break;
- }
- final boolean isDisabled = panelObj.optBoolean(PanelConfig.JSON_KEY_DISABLED, false);
- if (bookmarkType.equals(panelType)) {
- prefs.edit().putBoolean(HomeConfig.PREF_KEY_BOOKMARKS_PANEL_ENABLED, !isDisabled).apply();
- } else if (historyType.equals(panelType)) {
- prefs.edit().putBoolean(HomeConfig.PREF_KEY_HISTORY_PANEL_ENABLED, !isDisabled).apply();
- }
- }
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error fetching panel from config to update prefs");
- }
- }
- }
-
-
- private void sendReloadBroadcast() {
- final LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(mContext);
- final Intent reloadIntent = new Intent(RELOAD_BROADCAST);
- lbm.sendBroadcast(reloadIntent);
- }
-
- private void registerReloadReceiver() {
- final LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(mContext);
- lbm.registerReceiver(mReloadBroadcastReceiver, new IntentFilter(RELOAD_BROADCAST));
- }
-
- private void unregisterReloadReceiver() {
- final LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(mContext);
- lbm.unregisterReceiver(mReloadBroadcastReceiver);
- }
-
- private class ReloadBroadcastReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- mReloadListener.onReload();
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeContextMenuInfo.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeContextMenuInfo.java
deleted file mode 100644
index cefa0329d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeContextMenuInfo.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.util.StringUtils;
-
-import android.database.Cursor;
-import android.text.TextUtils;
-import android.view.View;
-import android.widget.AdapterView.AdapterContextMenuInfo;
-import android.widget.ExpandableListAdapter;
-import android.widget.ListAdapter;
-
-/**
- * A ContextMenuInfo for HomeListView
- */
-public class HomeContextMenuInfo extends AdapterContextMenuInfo {
-
- public String url;
- public String title;
- public boolean isFolder;
- public int historyId = -1;
- public int bookmarkId = -1;
- public RemoveItemType itemType = null;
-
- // Item type to be handled with "Remove" selection.
- public static enum RemoveItemType {
- BOOKMARKS, COMBINED, HISTORY
- }
-
- public HomeContextMenuInfo(View targetView, int position, long id) {
- super(targetView, position, id);
- }
-
- public boolean hasBookmarkId() {
- return bookmarkId > -1;
- }
-
- public boolean hasHistoryId() {
- return historyId > -1;
- }
-
- public boolean hasPartnerBookmarkId() {
- return bookmarkId <= BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START;
- }
-
- public boolean canRemove() {
- return hasBookmarkId() || hasHistoryId() || hasPartnerBookmarkId();
- }
-
- public String getDisplayTitle() {
- if (!TextUtils.isEmpty(title)) {
- return title;
- }
- return StringUtils.stripCommonSubdomains(StringUtils.stripScheme(url, StringUtils.UrlFlags.STRIP_HTTPS));
- }
-
- /**
- * Interface for creating ContextMenuInfo instances from cursors.
- */
- public interface Factory {
- public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor);
- }
-
- /**
- * Interface for creating ContextMenuInfo instances from ListAdapters.
- */
- public interface ListFactory extends Factory {
- public HomeContextMenuInfo makeInfoForAdapter(View view, int position, long id, ListAdapter adapter);
- }
-
- /**
- * Interface for creating ContextMenuInfo instances from ExpandableListAdapters.
- */
- public interface ExpandableFactory {
- public HomeContextMenuInfo makeInfoForAdapter(View view, int position, long id, ExpandableListAdapter adapter);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeExpandableListView.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeExpandableListView.java
deleted file mode 100644
index 7badd6929..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeExpandableListView.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemLongClickListener;
-import android.widget.ExpandableListView;
-
-/**
- * <code>HomeExpandableListView</code> is a custom extension of
- * <code>ExpandableListView<code>, that packs a <code>HomeContextMenuInfo</code>
- * when any of its rows is long pressed.
- * <p>
- * This is the <code>ExpandableListView</code> equivalent of
- * <code>HomeListView</code>.
- */
-public class HomeExpandableListView extends ExpandableListView
- implements OnItemLongClickListener {
-
- // ContextMenuInfo associated with the currently long pressed list item.
- private HomeContextMenuInfo mContextMenuInfo;
-
- // ContextMenuInfo factory.
- private HomeContextMenuInfo.ExpandableFactory mContextMenuInfoFactory;
-
- public HomeExpandableListView(Context context) {
- this(context, null);
- }
-
- public HomeExpandableListView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public HomeExpandableListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- setOnItemLongClickListener(this);
- }
-
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
- if (mContextMenuInfoFactory == null) {
- return false;
- }
-
- // HomeExpandableListView items can correspond to groups and children.
- // The factory can determine whether to add context menu for either,
- // both, or none by unpacking the given position.
- mContextMenuInfo = mContextMenuInfoFactory.makeInfoForAdapter(view, position, id, getExpandableListAdapter());
- return showContextMenuForChild(HomeExpandableListView.this);
- }
-
- @Override
- public ContextMenuInfo getContextMenuInfo() {
- return mContextMenuInfo;
- }
-
- public void setContextMenuInfoFactory(final HomeContextMenuInfo.ExpandableFactory factory) {
- mContextMenuInfoFactory = factory;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
deleted file mode 100644
index da6e9b703..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
+++ /dev/null
@@ -1,498 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.EnumSet;
-
-import org.mozilla.gecko.EditBookmarkDialog;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.IntentHelper;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.SnackbarBuilder;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
-import org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy;
-import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
-import org.mozilla.gecko.reader.SavedReaderViewHelper;
-import org.mozilla.gecko.reader.ReadingListHelper;
-import org.mozilla.gecko.restrictions.Restrictable;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.util.Clipboard;
-import org.mozilla.gecko.util.StringUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.UIAsyncTask;
-
-import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.support.design.widget.Snackbar;
-import android.support.v4.app.Fragment;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-
-/**
- * HomeFragment is an empty fragment that can be added to the HomePager.
- * Subclasses can add their own views.
- * <p>
- * The containing activity <b>must</b> implement {@link OnUrlOpenListener}.
- */
-public abstract class HomeFragment extends Fragment {
- // Log Tag.
- private static final String LOGTAG = "GeckoHomeFragment";
-
- // Share MIME type.
- protected static final String SHARE_MIME_TYPE = "text/plain";
-
- // Default value for "can load" hint
- static final boolean DEFAULT_CAN_LOAD_HINT = false;
-
- // Whether the fragment can load its content or not
- // This is used to defer data loading until the editing
- // mode animation ends.
- private boolean mCanLoadHint;
-
- // Whether the fragment has loaded its content
- private boolean mIsLoaded;
-
- // On URL open listener
- protected OnUrlOpenListener mUrlOpenListener;
-
- // Helper for opening a tab in the background.
- protected OnUrlOpenInBackgroundListener mUrlOpenInBackgroundListener;
-
- protected PanelStateChangeListener mPanelStateChangeListener = null;
-
- /**
- * Listener to notify when a home panels' state has changed in a way that needs to be stored
- * for history/restoration. E.g. when a folder is opened/closed in bookmarks.
- */
- public interface PanelStateChangeListener {
-
- /**
- * @param bundle Data that should be persisted, and passed to this panel if restored at a later
- * stage.
- */
- void onStateChanged(Bundle bundle);
-
- void setCachedRecentTabsCount(int count);
-
- int getCachedRecentTabsCount();
- }
-
- public void restoreData(Bundle data) {
- // Do nothing
- }
-
- public void setPanelStateChangeListener(
- PanelStateChangeListener mPanelStateChangeListener) {
- this.mPanelStateChangeListener = mPanelStateChangeListener;
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
-
- try {
- mUrlOpenListener = (OnUrlOpenListener) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException(activity.toString()
- + " must implement HomePager.OnUrlOpenListener");
- }
-
- try {
- mUrlOpenInBackgroundListener = (OnUrlOpenInBackgroundListener) activity;
- } catch (ClassCastException e) {
- throw new ClassCastException(activity.toString()
- + " must implement HomePager.OnUrlOpenInBackgroundListener");
- }
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- mUrlOpenListener = null;
- mUrlOpenInBackgroundListener = null;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final Bundle args = getArguments();
- if (args != null) {
- mCanLoadHint = args.getBoolean(HomePager.CAN_LOAD_ARG, DEFAULT_CAN_LOAD_HINT);
- } else {
- mCanLoadHint = DEFAULT_CAN_LOAD_HINT;
- }
-
- mIsLoaded = false;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
-
- GeckoApplication.watchReference(getActivity(), this);
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
- if (!(menuInfo instanceof HomeContextMenuInfo)) {
- return;
- }
-
- HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
-
- // Don't show the context menu for folders.
- if (info.isFolder) {
- return;
- }
-
- MenuInflater inflater = new MenuInflater(view.getContext());
- inflater.inflate(R.menu.home_contextmenu, menu);
-
- menu.setHeaderTitle(info.getDisplayTitle());
-
- // Hide unused menu items.
- menu.findItem(R.id.top_sites_edit).setVisible(false);
- menu.findItem(R.id.top_sites_pin).setVisible(false);
- menu.findItem(R.id.top_sites_unpin).setVisible(false);
-
- // Hide the "Edit" menuitem if this item isn't a bookmark,
- // or if this is a reading list item.
- if (!info.hasBookmarkId()) {
- menu.findItem(R.id.home_edit_bookmark).setVisible(false);
- }
-
- // Hide the "Remove" menuitem if this item not removable.
- if (!info.canRemove()) {
- menu.findItem(R.id.home_remove).setVisible(false);
- }
-
- if (!StringUtils.isShareableUrl(info.url) || GeckoProfile.get(getActivity()).inGuestMode()) {
- menu.findItem(R.id.home_share).setVisible(false);
- }
-
- if (!Restrictions.isAllowed(view.getContext(), Restrictable.PRIVATE_BROWSING)) {
- menu.findItem(R.id.home_open_private_tab).setVisible(false);
- }
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- // onContextItemSelected() is first dispatched to the activity and
- // then dispatched to its fragments. Since fragments cannot "override"
- // menu item selection handling, it's better to avoid menu id collisions
- // between the activity and its fragments.
-
- ContextMenuInfo menuInfo = item.getMenuInfo();
- if (!(menuInfo instanceof HomeContextMenuInfo)) {
- return false;
- }
-
- final HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
- final Context context = getActivity();
-
- final int itemId = item.getItemId();
-
- // Track the menu action. We don't know much about the context, but we can use this to determine
- // the frequency of use for various actions.
- String extras = getResources().getResourceEntryName(itemId);
- if (TextUtils.equals(extras, "home_open_private_tab")) {
- // Mask private browsing
- extras = "home_open_new_tab";
- }
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.CONTEXT_MENU, extras);
-
- if (itemId == R.id.home_copyurl) {
- if (info.url == null) {
- Log.e(LOGTAG, "Can't copy address because URL is null");
- return false;
- }
-
- Clipboard.setText(info.url);
- return true;
- }
-
- if (itemId == R.id.home_share) {
- if (info.url == null) {
- Log.e(LOGTAG, "Can't share because URL is null");
- return false;
- } else {
- IntentHelper.openUriExternal(info.url, SHARE_MIME_TYPE, "", "",
- Intent.ACTION_SEND, info.getDisplayTitle(), false);
-
- // Context: Sharing via chrome homepage contextmenu list (home session should be active)
- Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "home_contextmenu");
- return true;
- }
- }
-
- if (itemId == R.id.home_add_to_launcher) {
- if (info.url == null) {
- Log.e(LOGTAG, "Can't add to home screen because URL is null");
- return false;
- }
-
- // Fetch an icon big enough for use as a home screen icon.
- final String displayTitle = info.getDisplayTitle();
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- GeckoAppShell.createShortcut(displayTitle, info.url);
-
- }
- });
-
- return true;
- }
-
- if (itemId == R.id.home_open_private_tab || itemId == R.id.home_open_new_tab) {
- if (info.url == null) {
- Log.e(LOGTAG, "Can't open in new tab because URL is null");
- return false;
- }
-
- // Some pinned site items have "user-entered" urls. URLs entered in
- // the PinSiteDialog are wrapped in a special URI until we can get a
- // valid URL. If the url is a user-entered url, decode the URL
- // before loading it.
- final String url = StringUtils.decodeUserEnteredUrl(info.url);
-
- final EnumSet<OnUrlOpenInBackgroundListener.Flags> flags = EnumSet.noneOf(OnUrlOpenInBackgroundListener.Flags.class);
- if (item.getItemId() == R.id.home_open_private_tab) {
- flags.add(OnUrlOpenInBackgroundListener.Flags.PRIVATE);
- }
-
- mUrlOpenInBackgroundListener.onUrlOpenInBackground(url, flags);
-
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.CONTEXT_MENU);
-
- return true;
- }
-
- if (itemId == R.id.home_edit_bookmark) {
- // UI Dialog associates to the activity context, not the applications'.
- new EditBookmarkDialog(context).show(info.url);
- return true;
- }
-
- if (itemId == R.id.home_remove) {
- // For Top Sites grid items, position is required in case item is Pinned.
- final int position = info instanceof TopSitesGridContextMenuInfo ? info.position : -1;
-
- if (info.hasPartnerBookmarkId()) {
- new RemovePartnerBookmarkTask(context, info.bookmarkId).execute();
- } else {
- new RemoveItemByUrlTask(context, info.url, info.itemType, position).execute();
- }
- return true;
- }
-
- return false;
- }
-
- @Override
- public void setUserVisibleHint (boolean isVisibleToUser) {
- if (isVisibleToUser == getUserVisibleHint()) {
- return;
- }
-
- super.setUserVisibleHint(isVisibleToUser);
- loadIfVisible();
- }
-
- /**
- * Handle a configuration change by detaching and re-attaching.
- * <p>
- * A HomeFragment only needs to handle onConfiguration change (i.e.,
- * re-attach) if its UI needs to change (i.e., re-inflate layouts, use
- * different styles, etc) for different device orientations. Handling
- * configuration changes in all HomeFragments will simply cause some
- * redundant re-inflations on device rotation. This slight inefficiency
- * avoids potentially not handling a needed onConfigurationChanged in a
- * subclass.
- */
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
- // Reattach the fragment, forcing a re-inflation of its view.
- // We use commitAllowingStateLoss() instead of commit() here to avoid
- // an IllegalStateException. If the phone is rotated while Fennec
- // is in the background, onConfigurationChanged() is fired.
- // onConfigurationChanged() is called before onResume(), so
- // using commit() would throw an IllegalStateException since it can't
- // be used between the Activity's onSaveInstanceState() and
- // onResume().
- if (isVisible()) {
- getFragmentManager().beginTransaction()
- .detach(this)
- .attach(this)
- .commitAllowingStateLoss();
- }
- }
-
- void setCanLoadHint(boolean canLoadHint) {
- if (mCanLoadHint == canLoadHint) {
- return;
- }
-
- mCanLoadHint = canLoadHint;
- loadIfVisible();
- }
-
- boolean getCanLoadHint() {
- return mCanLoadHint;
- }
-
- protected abstract void load();
-
- protected boolean canLoad() {
- return (mCanLoadHint && isVisible() && getUserVisibleHint());
- }
-
- protected void loadIfVisible() {
- if (!canLoad() || mIsLoaded) {
- return;
- }
-
- load();
- mIsLoaded = true;
- }
-
- protected static class RemoveItemByUrlTask extends UIAsyncTask.WithoutParams<Void> {
- private final Context mContext;
- private final String mUrl;
- private final RemoveItemType mType;
- private final int mPosition;
- private final BrowserDB mDB;
-
- /**
- * Remove bookmark/history/reading list type item by url, and also unpin the
- * Top Sites grid item at index <code>position</code>.
- */
- public RemoveItemByUrlTask(Context context, String url, RemoveItemType type, int position) {
- super(ThreadUtils.getBackgroundHandler());
-
- mContext = context;
- mUrl = url;
- mType = type;
- mPosition = position;
- mDB = BrowserDB.from(context);
- }
-
- @Override
- public Void doInBackground() {
- ContentResolver cr = mContext.getContentResolver();
-
- if (mPosition > -1) {
- mDB.unpinSite(cr, mPosition);
- if (mDB.hideSuggestedSite(mUrl)) {
- cr.notifyChange(SuggestedSites.CONTENT_URI, null);
- }
- }
-
- switch (mType) {
- case BOOKMARKS:
- removeBookmark(cr);
- break;
-
- case HISTORY:
- removeHistory(cr);
- break;
-
- case COMBINED:
- removeBookmark(cr);
- removeHistory(cr);
- break;
-
- default:
- Log.e(LOGTAG, "Can't remove item type " + mType.toString());
- break;
- }
- return null;
- }
-
- @Override
- public void onPostExecute(Void result) {
- SnackbarBuilder.builder((Activity) mContext)
- .message(R.string.page_removed)
- .duration(Snackbar.LENGTH_LONG)
- .buildAndShow();
- }
-
- private void removeBookmark(ContentResolver cr) {
- SavedReaderViewHelper rch = SavedReaderViewHelper.getSavedReaderViewHelper(mContext);
- final boolean isReaderViewPage = rch.isURLCached(mUrl);
-
- final String extra;
- if (isReaderViewPage) {
- extra = "bookmark_reader";
- } else {
- extra = "bookmark";
- }
-
- Telemetry.sendUIEvent(TelemetryContract.Event.UNSAVE, TelemetryContract.Method.CONTEXT_MENU, extra);
- mDB.removeBookmarksWithURL(cr, mUrl);
-
- if (isReaderViewPage) {
- ReadingListHelper.removeCachedReaderItem(mUrl, mContext);
- }
- }
-
- private void removeHistory(ContentResolver cr) {
- mDB.removeHistoryEntry(cr, mUrl);
- }
- }
-
- private static class RemovePartnerBookmarkTask extends UIAsyncTask.WithoutParams<Void> {
- private Context context;
- private long bookmarkId;
-
- public RemovePartnerBookmarkTask(Context context, long bookmarkId) {
- super(ThreadUtils.getBackgroundHandler());
-
- this.context = context;
- this.bookmarkId = bookmarkId;
- }
-
- @Override
- protected Void doInBackground() {
- context.getContentResolver().delete(
- PartnerBookmarksProviderProxy.getUriForBookmark(context, bookmarkId),
- null,
- null
- );
-
- return null;
- }
-
- @Override
- protected void onPostExecute(Void aVoid) {
- SnackbarBuilder.builder((Activity) context)
- .message(R.string.page_removed)
- .duration(Snackbar.LENGTH_LONG)
- .buildAndShow();
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeListView.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeListView.java
deleted file mode 100644
index d179a27ce..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeListView.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.database.Cursor;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemLongClickListener;
-import android.widget.ListView;
-
-/**
- * HomeListView is a custom extension of ListView, that packs a HomeContextMenuInfo
- * when any of its rows is long pressed.
- */
-public class HomeListView extends ListView
- implements OnItemLongClickListener {
-
- // ContextMenuInfo associated with the currently long pressed list item.
- private HomeContextMenuInfo mContextMenuInfo;
-
- // On URL open listener
- protected OnUrlOpenListener mUrlOpenListener;
-
- // Top divider
- private final boolean mShowTopDivider;
-
- // ContextMenuInfo maker
- private HomeContextMenuInfo.Factory mContextMenuInfoFactory;
-
- public HomeListView(Context context) {
- this(context, null);
- }
-
- public HomeListView(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.homeListViewStyle);
- }
-
- public HomeListView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HomeListView, defStyle, 0);
- mShowTopDivider = a.getBoolean(R.styleable.HomeListView_topDivider, false);
- a.recycle();
-
- setOnItemLongClickListener(this);
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- final Drawable divider = getDivider();
- if (mShowTopDivider && divider != null) {
- final int dividerHeight = getDividerHeight();
- final View view = new View(getContext());
- view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, dividerHeight));
- addHeaderView(view);
- }
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- mUrlOpenListener = null;
- }
-
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
- Object item = parent.getItemAtPosition(position);
-
- // HomeListView could hold headers too. Add a context menu info only for its children.
- if (item instanceof Cursor) {
- Cursor cursor = (Cursor) item;
- if (cursor == null || mContextMenuInfoFactory == null) {
- mContextMenuInfo = null;
- return false;
- }
-
- mContextMenuInfo = mContextMenuInfoFactory.makeInfoForCursor(view, position, id, cursor);
- return showContextMenuForChild(HomeListView.this);
-
- } else if (mContextMenuInfoFactory instanceof HomeContextMenuInfo.ListFactory) {
- mContextMenuInfo = ((HomeContextMenuInfo.ListFactory) mContextMenuInfoFactory).makeInfoForAdapter(view, position, id, getAdapter());
- return showContextMenuForChild(HomeListView.this);
- } else {
- mContextMenuInfo = null;
- return false;
- }
- }
-
- @Override
- public ContextMenuInfo getContextMenuInfo() {
- return mContextMenuInfo;
- }
-
- @Override
- public void setOnItemClickListener(final AdapterView.OnItemClickListener listener) {
- if (listener == null) {
- super.setOnItemClickListener(null);
- return;
- }
-
- super.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- if (mShowTopDivider) {
- position--;
- }
-
- listener.onItemClick(parent, view, position, id);
- }
- });
- }
-
- public void setContextMenuInfoFactory(final HomeContextMenuInfo.Factory factory) {
- mContextMenuInfoFactory = factory;
- }
-
- public OnUrlOpenListener getOnUrlOpenListener() {
- return mUrlOpenListener;
- }
-
- public void setOnUrlOpenListener(OnUrlOpenListener listener) {
- mUrlOpenListener = listener;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomePager.java b/mobile/android/base/java/org/mozilla/gecko/home/HomePager.java
deleted file mode 100644
index 4915f0c91..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomePager.java
+++ /dev/null
@@ -1,564 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.List;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.animation.ViewHelper;
-import org.mozilla.gecko.home.HomeAdapter.OnAddPanelListener;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.app.LoaderManager.LoaderCallbacks;
-import android.support.v4.content.Loader;
-import android.support.v4.view.ViewPager;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-
-public class HomePager extends ViewPager implements HomeScreen {
-
- @Override
- public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
- return super.requestFocus(direction, previouslyFocusedRect);
- }
-
- private static final int LOADER_ID_CONFIG = 0;
-
- private final Context mContext;
- private volatile boolean mVisible;
- private Decor mDecor;
- private View mTabStrip;
- private HomeBanner mHomeBanner;
- private int mDefaultPageIndex = -1;
-
- private final OnAddPanelListener mAddPanelListener;
-
- private final HomeConfig mConfig;
- private final ConfigLoaderCallbacks mConfigLoaderCallbacks;
-
- private String mInitialPanelId;
- private Bundle mRestoreData;
-
- // Cached original ViewPager background.
- private final Drawable mOriginalBackground;
-
- // Telemetry session for current panel.
- private TelemetryContract.Session mCurrentPanelSession;
- private String mCurrentPanelSessionSuffix;
-
- // Current load state of HomePager.
- private LoadState mLoadState;
-
- // Listens for when the current panel changes.
- private OnPanelChangeListener mPanelChangedListener;
-
- private HomeFragment.PanelStateChangeListener mPanelStateChangeListener;
-
- // This is mostly used by UI tests to easily fetch
- // specific list views at runtime.
- public static final String LIST_TAG_HISTORY = "history";
- public static final String LIST_TAG_BOOKMARKS = "bookmarks";
- public static final String LIST_TAG_TOP_SITES = "top_sites";
- public static final String LIST_TAG_RECENT_TABS = "recent_tabs";
- public static final String LIST_TAG_BROWSER_SEARCH = "browser_search";
- public static final String LIST_TAG_REMOTE_TABS = "remote_tabs";
-
- public interface OnUrlOpenListener {
- public enum Flags {
- ALLOW_SWITCH_TO_TAB,
- OPEN_WITH_INTENT,
- /**
- * Ensure that the raw URL is opened. If not set, then the reader view version of the page
- * might be opened if the URL is stored as an offline reader-view bookmark.
- */
- NO_READER_VIEW
- }
-
- public void onUrlOpen(String url, EnumSet<Flags> flags);
- }
-
- /**
- * Interface for requesting a new tab be opened in the background.
- * <p>
- * This is the <code>HomeFragment</code> equivalent of opening a new tab by
- * long clicking a link and selecting the "Open new [private] tab" context
- * menu option.
- */
- public interface OnUrlOpenInBackgroundListener {
- public enum Flags {
- PRIVATE,
- }
-
- /**
- * Open a new tab with the given URL
- *
- * @param url to open.
- * @param flags to open new tab with.
- */
- public void onUrlOpenInBackground(String url, EnumSet<Flags> flags);
- }
-
- /**
- * Special type of child views that could be added as pager decorations by default.
- */
- public interface Decor {
- void onAddPagerView(String title);
- void removeAllPagerViews();
- void onPageSelected(int position);
- void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
- void setOnTitleClickListener(TabMenuStrip.OnTitleClickListener onTitleClickListener);
- }
-
- /**
- * State of HomePager with respect to loading its configuration.
- */
- private enum LoadState {
- UNLOADED,
- LOADING,
- LOADED
- }
-
- public static final String CAN_LOAD_ARG = "canLoad";
- public static final String PANEL_CONFIG_ARG = "panelConfig";
-
- public HomePager(Context context) {
- this(context, null);
- }
-
- public HomePager(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
-
- mConfig = HomeConfig.getDefault(mContext);
- mConfigLoaderCallbacks = new ConfigLoaderCallbacks();
-
- mAddPanelListener = new OnAddPanelListener() {
- @Override
- public void onAddPanel(String title) {
- if (mDecor != null) {
- mDecor.onAddPagerView(title);
- }
- }
- };
-
- // This is to keep all 4 panels in memory after they are
- // selected in the pager.
- setOffscreenPageLimit(3);
-
- // We can call HomePager.requestFocus to steal focus from the URL bar and drop the soft
- // keyboard. However, if there are no focusable views (e.g. an empty reading list), the
- // URL bar will be refocused. Therefore, we make the HomePager container focusable to
- // ensure there is always a focusable view. This would ordinarily be done via an XML
- // attribute, but it is not working properly.
- setFocusableInTouchMode(true);
-
- mOriginalBackground = getBackground();
- setOnPageChangeListener(new PageChangeListener());
-
- mLoadState = LoadState.UNLOADED;
- }
-
- @Override
- public void addView(View child, int index, ViewGroup.LayoutParams params) {
- if (child instanceof Decor) {
- ((ViewPager.LayoutParams) params).isDecor = true;
- mDecor = (Decor) child;
- mTabStrip = child;
-
- mDecor.setOnTitleClickListener(new TabMenuStrip.OnTitleClickListener() {
- @Override
- public void onTitleClicked(int index) {
- setCurrentItem(index, true);
- }
- });
- }
-
- super.addView(child, index, params);
- }
-
- /**
- * Loads and initializes the pager.
- *
- * @param fm FragmentManager for the adapter
- */
- @Override
- public void load(LoaderManager lm, FragmentManager fm, String panelId, Bundle restoreData, PropertyAnimator animator) {
- mLoadState = LoadState.LOADING;
-
- mVisible = true;
- mInitialPanelId = panelId;
- mRestoreData = restoreData;
-
- // Update the home banner message each time the HomePager is loaded.
- if (mHomeBanner != null) {
- mHomeBanner.update();
- }
-
- // Only animate on post-HC devices, when a non-null animator is given
- final boolean shouldAnimate = animator != null;
-
- final HomeAdapter adapter = new HomeAdapter(mContext, fm);
- adapter.setOnAddPanelListener(mAddPanelListener);
- adapter.setPanelStateChangeListener(mPanelStateChangeListener);
- adapter.setCanLoadHint(true);
- setAdapter(adapter);
-
- // Don't show the tabs strip until we have the
- // list of panels in place.
- mTabStrip.setVisibility(View.INVISIBLE);
-
- // Load list of panels from configuration
- lm.initLoader(LOADER_ID_CONFIG, null, mConfigLoaderCallbacks);
-
- if (shouldAnimate) {
- animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
- @Override
- public void onPropertyAnimationStart() {
- setLayerType(View.LAYER_TYPE_HARDWARE, null);
- }
-
- @Override
- public void onPropertyAnimationEnd() {
- setLayerType(View.LAYER_TYPE_NONE, null);
- }
- });
-
- ViewHelper.setAlpha(this, 0.0f);
-
- animator.attach(this,
- PropertyAnimator.Property.ALPHA,
- 1.0f);
- }
- }
-
- /**
- * Removes all child fragments to free memory.
- */
- @Override
- public void unload() {
- mVisible = false;
- setAdapter(null);
- mLoadState = LoadState.UNLOADED;
-
- // Stop UI Telemetry sessions.
- stopCurrentPanelTelemetrySession();
- }
-
- /**
- * Determines whether the pager is visible.
- *
- * Unlike getVisibility(), this method does not need to be called on the UI
- * thread.
- *
- * @return Whether the pager and its fragments are loaded
- */
- public boolean isVisible() {
- return mVisible;
- }
-
- @Override
- public void setCurrentItem(int item, boolean smoothScroll) {
- super.setCurrentItem(item, smoothScroll);
-
- if (mDecor != null) {
- mDecor.onPageSelected(item);
- }
-
- if (mHomeBanner != null) {
- mHomeBanner.setActive(item == mDefaultPageIndex);
- }
- }
-
- private void restorePanelData(int item, Bundle data) {
- ((HomeAdapter) getAdapter()).setRestoreData(item, data);
- }
-
- /**
- * Shows a home panel. If the given panelId is null,
- * the default panel will be shown. No action will be taken if:
- * * HomePager has not loaded yet
- * * Panel with the given panelId cannot be found
- *
- * If you're trying to open a built-in panel, consider loading the panel url directly with
- * {@link org.mozilla.gecko.AboutPages#getURLForBuiltinPanelType(HomeConfig.PanelType)}.
- *
- * @param panelId of the home panel to be shown.
- */
- @Override
- public void showPanel(String panelId, Bundle restoreData) {
- if (!mVisible) {
- return;
- }
-
- switch (mLoadState) {
- case LOADING:
- mInitialPanelId = panelId;
- mRestoreData = restoreData;
- break;
-
- case LOADED:
- int position = mDefaultPageIndex;
- if (panelId != null) {
- position = ((HomeAdapter) getAdapter()).getItemPosition(panelId);
- }
-
- if (position > -1) {
- setCurrentItem(position);
- if (restoreData != null) {
- restorePanelData(position, restoreData);
- }
- }
- break;
-
- default:
- // Do nothing.
- }
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- // Drop the soft keyboard by stealing focus from the URL bar.
- requestFocus();
- }
-
- return super.onInterceptTouchEvent(event);
- }
-
- public void setBanner(HomeBanner banner) {
- mHomeBanner = banner;
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- if (mHomeBanner != null) {
- mHomeBanner.handleHomeTouch(event);
- }
-
- return super.dispatchTouchEvent(event);
- }
-
- @Override
- public void onToolbarFocusChange(boolean hasFocus) {
- if (mHomeBanner == null) {
- return;
- }
-
- // We should only make the banner active if the toolbar is not focused and we are on the default page
- final boolean active = !hasFocus && getCurrentItem() == mDefaultPageIndex;
- mHomeBanner.setActive(active);
- }
-
- private void updateUiFromConfigState(HomeConfig.State configState) {
- // We only care about the adapter if HomePager is currently
- // loaded, which means it's visible in the activity.
- if (!mVisible) {
- return;
- }
-
- if (mDecor != null) {
- mDecor.removeAllPagerViews();
- }
-
- final HomeAdapter adapter = (HomeAdapter) getAdapter();
-
- // Disable any fragment loading until we have the initial
- // panel selection done.
- adapter.setCanLoadHint(false);
-
- // Destroy any existing panels currently loaded
- // in the pager.
- setAdapter(null);
-
- // Only keep enabled panels.
- final List<PanelConfig> enabledPanels = new ArrayList<PanelConfig>();
-
- for (PanelConfig panelConfig : configState) {
- if (!panelConfig.isDisabled()) {
- enabledPanels.add(panelConfig);
- }
- }
-
- // Update the adapter with the new panel configs
- adapter.update(enabledPanels);
-
- final int count = enabledPanels.size();
- if (count == 0) {
- // Set firefox watermark as background.
- setBackgroundResource(R.drawable.home_pager_empty_state);
- // Hide the tab strip as there are no panels.
- mTabStrip.setVisibility(View.INVISIBLE);
- } else {
- mTabStrip.setVisibility(View.VISIBLE);
- // Restore original background.
- setBackgroundDrawable(mOriginalBackground);
- }
-
- // Re-install the adapter with the final state
- // in the pager.
- setAdapter(adapter);
-
- if (count == 0) {
- mDefaultPageIndex = -1;
-
- // Hide the banner if there are no enabled panels.
- if (mHomeBanner != null) {
- mHomeBanner.setActive(false);
- }
- } else {
- for (int i = 0; i < count; i++) {
- if (enabledPanels.get(i).isDefault()) {
- mDefaultPageIndex = i;
- break;
- }
- }
-
- // Use the default panel if the initial panel wasn't explicitly set by the
- // load() caller, or if the initial panel is not found in the adapter.
- final int itemPosition = (mInitialPanelId == null) ? -1 : adapter.getItemPosition(mInitialPanelId);
- if (itemPosition > -1) {
- setCurrentItem(itemPosition, false);
- if (mRestoreData != null) {
- restorePanelData(itemPosition, mRestoreData);
- mRestoreData = null; // Release data since it's no longer needed
- }
- mInitialPanelId = null;
- } else {
- setCurrentItem(mDefaultPageIndex, false);
- }
- }
-
- // The selection is updated asynchronously so we need to post to
- // UI thread to give the pager time to commit the new page selection
- // internally and load the right initial panel.
- ThreadUtils.getUiHandler().post(new Runnable() {
- @Override
- public void run() {
- adapter.setCanLoadHint(true);
- }
- });
- }
-
- @Override
- public void setOnPanelChangeListener(OnPanelChangeListener listener) {
- mPanelChangedListener = listener;
- }
-
- @Override
- public void setPanelStateChangeListener(HomeFragment.PanelStateChangeListener listener) {
- mPanelStateChangeListener = listener;
-
- HomeAdapter adapter = (HomeAdapter) getAdapter();
- if (adapter != null) {
- adapter.setPanelStateChangeListener(listener);
- }
- }
-
- /**
- * Notify listeners of newly selected panel.
- *
- * @param position of the newly selected panel
- */
- private void notifyPanelSelected(int position) {
- if (mDecor != null) {
- mDecor.onPageSelected(position);
- }
-
- if (mPanelChangedListener != null) {
- final String panelId = ((HomeAdapter) getAdapter()).getPanelIdAtPosition(position);
- mPanelChangedListener.onPanelSelected(panelId);
- }
- }
-
- private class ConfigLoaderCallbacks implements LoaderCallbacks<HomeConfig.State> {
- @Override
- public Loader<HomeConfig.State> onCreateLoader(int id, Bundle args) {
- return new HomeConfigLoader(mContext, mConfig);
- }
-
- @Override
- public void onLoadFinished(Loader<HomeConfig.State> loader, HomeConfig.State configState) {
- mLoadState = LoadState.LOADED;
- updateUiFromConfigState(configState);
- }
-
- @Override
- public void onLoaderReset(Loader<HomeConfig.State> loader) {
- mLoadState = LoadState.UNLOADED;
- }
- }
-
- private class PageChangeListener implements ViewPager.OnPageChangeListener {
- @Override
- public void onPageSelected(int position) {
- notifyPanelSelected(position);
-
- if (mHomeBanner != null) {
- mHomeBanner.setActive(position == mDefaultPageIndex);
- }
-
- // Start a UI telemetry session for the newly selected panel.
- final String newPanelId = ((HomeAdapter) getAdapter()).getPanelIdAtPosition(position);
- startNewPanelTelemetrySession(newPanelId);
- }
-
- @Override
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- if (mDecor != null) {
- mDecor.onPageScrolled(position, positionOffset, positionOffsetPixels);
- }
-
- if (mHomeBanner != null) {
- mHomeBanner.setScrollingPages(positionOffsetPixels != 0);
- }
- }
-
- @Override
- public void onPageScrollStateChanged(int state) { }
- }
-
- /**
- * Start UI telemetry session for the a panel.
- * If there is currently a session open for a panel,
- * it will be stopped before a new one is started.
- *
- * @param panelId of panel to start a session for
- */
- private void startNewPanelTelemetrySession(String panelId) {
- // Stop the current panel's session if we have one.
- stopCurrentPanelTelemetrySession();
-
- mCurrentPanelSession = TelemetryContract.Session.HOME_PANEL;
- mCurrentPanelSessionSuffix = panelId;
- Telemetry.startUISession(mCurrentPanelSession, mCurrentPanelSessionSuffix);
- }
-
- /**
- * Stop the current panel telemetry session if one exists.
- */
- private void stopCurrentPanelTelemetrySession() {
- if (mCurrentPanelSession != null) {
- Telemetry.stopUISession(mCurrentPanelSession, mCurrentPanelSessionSuffix);
- mCurrentPanelSession = null;
- mCurrentPanelSessionSuffix = null;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomePanelsManager.java b/mobile/android/base/java/org/mozilla/gecko/home/HomePanelsManager.java
deleted file mode 100644
index bfd6c5624..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomePanelsManager.java
+++ /dev/null
@@ -1,368 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import static org.mozilla.gecko.home.HomeConfig.createBuiltinPanelConfig;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Queue;
-import java.util.Set;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.db.HomeProvider;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.home.PanelInfoManager.PanelInfo;
-import org.mozilla.gecko.home.PanelInfoManager.RequestCallback;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.os.Handler;
-import android.util.Log;
-
-public class HomePanelsManager implements GeckoEventListener {
- public static final String LOGTAG = "HomePanelsManager";
-
- private static final HomePanelsManager sInstance = new HomePanelsManager();
-
- private static final int INVALIDATION_DELAY_MSEC = 500;
- private static final int PANEL_INFO_TIMEOUT_MSEC = 1000;
-
- private static final String EVENT_HOMEPANELS_INSTALL = "HomePanels:Install";
- private static final String EVENT_HOMEPANELS_UNINSTALL = "HomePanels:Uninstall";
- private static final String EVENT_HOMEPANELS_UPDATE = "HomePanels:Update";
- private static final String EVENT_HOMEPANELS_REFRESH = "HomePanels:RefreshDataset";
-
- private static final String JSON_KEY_PANEL = "panel";
- private static final String JSON_KEY_PANEL_ID = "id";
-
- private enum ChangeType {
- UNINSTALL,
- INSTALL,
- UPDATE,
- REFRESH
- }
-
- private enum InvalidationMode {
- DELAYED,
- IMMEDIATE
- }
-
- private static class ConfigChange {
- private final ChangeType type;
- private final Object target;
-
- public ConfigChange(ChangeType type) {
- this(type, null);
- }
-
- public ConfigChange(ChangeType type, Object target) {
- this.type = type;
- this.target = target;
- }
- }
-
- private Context mContext;
- private HomeConfig mHomeConfig;
- private boolean mInitialized;
-
- private final Queue<ConfigChange> mPendingChanges = new ConcurrentLinkedQueue<ConfigChange>();
- private final Runnable mInvalidationRunnable = new InvalidationRunnable();
-
- public static HomePanelsManager getInstance() {
- return sInstance;
- }
-
- public void init(Context context) {
- if (mInitialized) {
- return;
- }
-
- mContext = context;
- mHomeConfig = HomeConfig.getDefault(context);
-
- EventDispatcher.getInstance().registerGeckoThreadListener(this,
- EVENT_HOMEPANELS_INSTALL,
- EVENT_HOMEPANELS_UNINSTALL,
- EVENT_HOMEPANELS_UPDATE,
- EVENT_HOMEPANELS_REFRESH);
-
- mInitialized = true;
- }
-
- public void onLocaleReady(final String locale) {
- ThreadUtils.getBackgroundHandler().post(new Runnable() {
- @Override
- public void run() {
- final String configLocale = mHomeConfig.getLocale();
- if (configLocale == null || !configLocale.equals(locale)) {
- handleLocaleChange();
- }
- }
- });
- }
-
- @Override
- public void handleMessage(String event, JSONObject message) {
- try {
- if (event.equals(EVENT_HOMEPANELS_INSTALL)) {
- Log.d(LOGTAG, EVENT_HOMEPANELS_INSTALL);
- handlePanelInstall(createPanelConfigFromMessage(message), InvalidationMode.DELAYED);
- } else if (event.equals(EVENT_HOMEPANELS_UNINSTALL)) {
- Log.d(LOGTAG, EVENT_HOMEPANELS_UNINSTALL);
- final String panelId = message.getString(JSON_KEY_PANEL_ID);
- handlePanelUninstall(panelId);
- } else if (event.equals(EVENT_HOMEPANELS_UPDATE)) {
- Log.d(LOGTAG, EVENT_HOMEPANELS_UPDATE);
- handlePanelUpdate(createPanelConfigFromMessage(message));
- } else if (event.equals(EVENT_HOMEPANELS_REFRESH)) {
- Log.d(LOGTAG, EVENT_HOMEPANELS_REFRESH);
- handleDatasetRefresh(message);
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Failed to handle event " + event, e);
- }
- }
-
- private PanelConfig createPanelConfigFromMessage(JSONObject message) throws JSONException {
- final JSONObject json = message.getJSONObject(JSON_KEY_PANEL);
- return new PanelConfig(json);
- }
-
- /**
- * Adds a new PanelConfig to the HomeConfig.
- *
- * This posts the invalidation of HomeConfig immediately.
- *
- * @param panelConfig panel to add
- */
- public void installPanel(PanelConfig panelConfig) {
- Log.d(LOGTAG, "installPanel: " + panelConfig.getTitle());
- handlePanelInstall(panelConfig, InvalidationMode.IMMEDIATE);
- }
-
- /**
- * Runs in the gecko thread.
- */
- private void handlePanelInstall(PanelConfig panelConfig, InvalidationMode mode) {
- mPendingChanges.offer(new ConfigChange(ChangeType.INSTALL, panelConfig));
- Log.d(LOGTAG, "handlePanelInstall: " + mPendingChanges.size());
-
- scheduleInvalidation(mode);
- }
-
- /**
- * Runs in the gecko thread.
- */
- private void handlePanelUninstall(String panelId) {
- mPendingChanges.offer(new ConfigChange(ChangeType.UNINSTALL, panelId));
- Log.d(LOGTAG, "handlePanelUninstall: " + mPendingChanges.size());
-
- scheduleInvalidation(InvalidationMode.DELAYED);
- }
-
- /**
- * Runs in the gecko thread.
- */
- private void handlePanelUpdate(PanelConfig panelConfig) {
- mPendingChanges.offer(new ConfigChange(ChangeType.UPDATE, panelConfig));
- Log.d(LOGTAG, "handlePanelUpdate: " + mPendingChanges.size());
-
- scheduleInvalidation(InvalidationMode.DELAYED);
- }
-
- /**
- * Runs in the background thread.
- */
- private void handleLocaleChange() {
- mPendingChanges.offer(new ConfigChange(ChangeType.REFRESH));
- Log.d(LOGTAG, "handleLocaleChange: " + mPendingChanges.size());
-
- scheduleInvalidation(InvalidationMode.IMMEDIATE);
- }
-
-
- /**
- * Handles a dataset refresh request from Gecko. This is usually
- * triggered by a HomeStorage.save() call in an add-on.
- *
- * Runs in the gecko thread.
- */
- private void handleDatasetRefresh(JSONObject message) {
- final String datasetId;
- try {
- datasetId = message.getString("datasetId");
- } catch (JSONException e) {
- Log.e(LOGTAG, "Failed to handle dataset refresh", e);
- return;
- }
-
- Log.d(LOGTAG, "Refresh request for dataset: " + datasetId);
-
- final ContentResolver cr = mContext.getContentResolver();
- cr.notifyChange(HomeProvider.getDatasetNotificationUri(datasetId), null);
- }
-
- /**
- * Runs in the gecko or main thread.
- */
- private void scheduleInvalidation(InvalidationMode mode) {
- final Handler handler = ThreadUtils.getBackgroundHandler();
-
- handler.removeCallbacks(mInvalidationRunnable);
-
- if (mode == InvalidationMode.IMMEDIATE) {
- handler.post(mInvalidationRunnable);
- } else {
- handler.postDelayed(mInvalidationRunnable, INVALIDATION_DELAY_MSEC);
- }
-
- Log.d(LOGTAG, "scheduleInvalidation: scheduled new invalidation: " + mode);
- }
-
- /**
- * Runs in the background thread.
- */
- private void executePendingChanges(HomeConfig.Editor editor) {
- boolean shouldRefresh = false;
-
- while (!mPendingChanges.isEmpty()) {
- final ConfigChange pendingChange = mPendingChanges.poll();
-
- switch (pendingChange.type) {
- case UNINSTALL: {
- final String panelId = (String) pendingChange.target;
- if (editor.uninstall(panelId)) {
- Log.d(LOGTAG, "executePendingChanges: uninstalled panel " + panelId);
- }
- break;
- }
-
- case INSTALL: {
- final PanelConfig panelConfig = (PanelConfig) pendingChange.target;
- if (editor.install(panelConfig)) {
- Log.d(LOGTAG, "executePendingChanges: added panel " + panelConfig.getId());
- }
- break;
- }
-
- case UPDATE: {
- final PanelConfig panelConfig = (PanelConfig) pendingChange.target;
- if (editor.update(panelConfig)) {
- Log.w(LOGTAG, "executePendingChanges: updated panel " + panelConfig.getId());
- }
- break;
- }
-
- case REFRESH: {
- shouldRefresh = true;
- }
- }
- }
-
- // The editor still represents the default HomeConfig
- // configuration and hasn't been changed by any operation
- // above. No need to refresh as the HomeConfig backend will
- // take of forcing all existing HomeConfigLoader instances to
- // refresh their contents.
- if (shouldRefresh && !editor.isDefault()) {
- executeRefresh(editor);
- }
- }
-
- /**
- * Runs in the background thread.
- */
- private void refreshFromPanelInfos(HomeConfig.Editor editor, List<PanelInfo> panelInfos) {
- Log.d(LOGTAG, "refreshFromPanelInfos");
-
- for (PanelConfig panelConfig : editor) {
- PanelConfig refreshedPanelConfig = null;
-
- if (panelConfig.isDynamic()) {
- for (PanelInfo panelInfo : panelInfos) {
- if (panelInfo.getId().equals(panelConfig.getId())) {
- refreshedPanelConfig = panelInfo.toPanelConfig();
- Log.d(LOGTAG, "refreshFromPanelInfos: refreshing from panel info: " + panelInfo.getId());
- break;
- }
- }
- } else {
- refreshedPanelConfig = createBuiltinPanelConfig(mContext, panelConfig.getType());
- Log.d(LOGTAG, "refreshFromPanelInfos: refreshing built-in panel: " + panelConfig.getId());
- }
-
- if (refreshedPanelConfig == null) {
- Log.d(LOGTAG, "refreshFromPanelInfos: no refreshed panel, falling back: " + panelConfig.getId());
- continue;
- }
-
- Log.d(LOGTAG, "refreshFromPanelInfos: refreshed panel " + refreshedPanelConfig.getId());
- editor.update(refreshedPanelConfig);
- }
- }
-
- /**
- * Runs in the background thread.
- */
- private void executeRefresh(HomeConfig.Editor editor) {
- if (editor.isEmpty()) {
- return;
- }
-
- Log.d(LOGTAG, "executeRefresh");
-
- final Set<String> ids = new HashSet<String>();
- for (PanelConfig panelConfig : editor) {
- ids.add(panelConfig.getId());
- }
-
- final Object panelRequestLock = new Object();
- final List<PanelInfo> latestPanelInfos = new ArrayList<PanelInfo>();
-
- final PanelInfoManager pm = new PanelInfoManager();
- pm.requestPanelsById(ids, new RequestCallback() {
- @Override
- public void onComplete(List<PanelInfo> panelInfos) {
- synchronized (panelRequestLock) {
- latestPanelInfos.addAll(panelInfos);
- Log.d(LOGTAG, "executeRefresh: fetched panel infos: " + panelInfos.size());
-
- panelRequestLock.notifyAll();
- }
- }
- });
-
- try {
- synchronized (panelRequestLock) {
- panelRequestLock.wait(PANEL_INFO_TIMEOUT_MSEC);
-
- Log.d(LOGTAG, "executeRefresh: done fetching panel infos");
- refreshFromPanelInfos(editor, latestPanelInfos);
- }
- } catch (InterruptedException e) {
- Log.e(LOGTAG, "Failed to fetch panels from gecko", e);
- }
- }
-
- /**
- * Runs in the background thread.
- */
- private class InvalidationRunnable implements Runnable {
- @Override
- public void run() {
- final HomeConfig.Editor editor = mHomeConfig.load().edit();
- executePendingChanges(editor);
- editor.apply();
- }
- };
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/HomeScreen.java b/mobile/android/base/java/org/mozilla/gecko/home/HomeScreen.java
deleted file mode 100644
index 1525969a0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeScreen.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.mozilla.gecko.home;
-
-import android.os.Bundle;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.LoaderManager;
-import android.view.View;
-
-import org.mozilla.gecko.animation.PropertyAnimator;
-
-/**
- * Generic interface for any View that can be used as the homescreen.
- *
- * In the past we had the HomePager, which contained the usual homepanels (multiple panels: TopSites,
- * bookmarks, history, etc.), which could be swiped between.
- *
- * This interface allows easily switching between different homepanel implementations. For example
- * the prototype activity-stream panel (which will be a single panel combining the functionality
- * of the previous panels).
- */
-public interface HomeScreen {
- /**
- * Interface for listening into ViewPager panel changes
- */
- public interface OnPanelChangeListener {
- /**
- * Called when a new panel is selected.
- *
- * @param panelId of the newly selected panel
- */
- public void onPanelSelected(String panelId);
- }
-
- // The following two methods are actually methods of View. Since there is no View interface
- // we're forced to do this instead of "extending" View. Any class implementing HomeScreen
- // will have to implement these and pass them through to the underlying View.
- boolean isVisible();
- boolean requestFocus();
-
- void onToolbarFocusChange(boolean hasFocus);
-
- // The following three methods are HomePager specific. The persistence framework might need
- // refactoring/generalising at some point, but it isn't entirely clear what other panels
- // might need so we can leave these as is for now.
- void showPanel(String panelId, Bundle restoreData);
- void setOnPanelChangeListener(OnPanelChangeListener listener);
- void setPanelStateChangeListener(HomeFragment.PanelStateChangeListener listener);
-
- /**
- * Set a banner that may be displayed at the bottom of the HomeScreen. This can be used
- * e.g. to show snippets.
- */
- void setBanner(HomeBanner banner);
-
- void load(LoaderManager lm, FragmentManager fm, String panelId, Bundle restoreData, PropertyAnimator animator);
-
- void unload();
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/ImageLoader.java b/mobile/android/base/java/org/mozilla/gecko/home/ImageLoader.java
deleted file mode 100644
index 2bbd82a8d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/ImageLoader.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.net.Uri;
-import android.util.DisplayMetrics;
-import android.util.Log;
-
-import com.squareup.picasso.LruCache;
-import com.squareup.picasso.Picasso;
-import com.squareup.picasso.Downloader.Response;
-import com.squareup.picasso.UrlConnectionDownloader;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.EnumSet;
-import java.util.Set;
-
-import org.mozilla.gecko.distribution.Distribution;
-
-public class ImageLoader {
- private static final String LOGTAG = "GeckoImageLoader";
-
- private static final String DISTRIBUTION_SCHEME = "gecko.distribution";
- private static final String SUGGESTED_SITES_AUTHORITY = "suggestedsites";
-
- // The order of density factors to try when looking for an image resource
- // in the distribution directory. It looks for an exact match first (1.0) then
- // tries to find images with higher density (2.0 and 1.5). If no image is found,
- // try a lower density (0.5). See loadDistributionImage().
- private static final float[] densityFactors = new float[] { 1.0f, 2.0f, 1.5f, 0.5f };
-
- private static enum Density {
- MDPI,
- HDPI,
- XHDPI,
- XXHDPI;
-
- @Override
- public String toString() {
- return super.toString().toLowerCase();
- }
- }
-
- // Picasso instance and LruCache lrucache are protected by synchronization.
- private static Picasso instance;
- private static LruCache lrucache;
-
- public static synchronized Picasso with(Context context) {
- if (instance == null) {
- lrucache = new LruCache(context);
- Picasso.Builder builder = new Picasso.Builder(context).memoryCache(lrucache);
-
- final Distribution distribution = Distribution.getInstance(context.getApplicationContext());
- builder.downloader(new ImageDownloader(context, distribution));
- instance = builder.build();
- }
-
- return instance;
- }
-
- public static synchronized void clearLruCache() {
- if (lrucache != null) {
- lrucache.evictAll();
- }
- }
-
- /**
- * Custom Downloader built on top of Picasso's UrlConnectionDownloader
- * that supports loading images from custom URIs.
- */
- public static class ImageDownloader extends UrlConnectionDownloader {
- private final Context context;
- private final Distribution distribution;
-
- public ImageDownloader(Context context, Distribution distribution) {
- super(context);
- this.context = context;
- this.distribution = distribution;
- }
-
- private Density getDensity(float factor) {
- final DisplayMetrics dm = context.getResources().getDisplayMetrics();
- final float densityDpi = dm.densityDpi * factor;
-
- if (densityDpi >= DisplayMetrics.DENSITY_XXHIGH) {
- return Density.XXHDPI;
- } else if (densityDpi >= DisplayMetrics.DENSITY_XHIGH) {
- return Density.XHDPI;
- } else if (densityDpi >= DisplayMetrics.DENSITY_HIGH) {
- return Density.HDPI;
- }
-
- // Fallback to mdpi, no need to handle ldpi.
- return Density.MDPI;
- }
-
- @Override
- public Response load(Uri uri, boolean localCacheOnly) throws IOException {
- final String scheme = uri.getScheme();
- if (DISTRIBUTION_SCHEME.equals(scheme)) {
- return loadDistributionImage(uri);
- }
-
- return super.load(uri, localCacheOnly);
- }
-
- private static String getPathForDensity(String basePath, Density density,
- String filename) {
- final File dir = new File(basePath, density.toString());
- return String.format("%s/%s.png", dir.toString(), filename);
- }
-
- /**
- * Handle distribution URIs in Picasso. The expected format is:
- *
- * gecko.distribution://<basepath>/<imagename>
- *
- * Which will look for the following file in the distribution:
- *
- * <distribution-root-dir>/<basepath>/<device-density>/<imagename>.png
- */
- private Response loadDistributionImage(Uri uri) throws IOException {
- // Eliminate the leading '//'
- final String ssp = uri.getSchemeSpecificPart().substring(2);
-
- final String filename;
- final String basePath;
-
- final int slashIndex = ssp.lastIndexOf('/');
- if (slashIndex == -1) {
- filename = ssp;
- basePath = "";
- } else {
- filename = ssp.substring(slashIndex + 1);
- basePath = ssp.substring(0, slashIndex);
- }
-
- Set<Density> triedDensities = EnumSet.noneOf(Density.class);
-
- for (int i = 0; i < densityFactors.length; i++) {
- final Density density = getDensity(densityFactors[i]);
- if (!triedDensities.add(density)) {
- continue;
- }
-
- final String path = getPathForDensity(basePath, density, filename);
- Log.d(LOGTAG, "Trying to load image from distribution " + path);
-
- final File f = distribution.getDistributionFile(path);
- if (f != null) {
- return new Response(new FileInputStream(f), true);
- }
- }
-
- throw new ResponseException("Couldn't find suggested site image in distribution");
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/MultiTypeCursorAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/MultiTypeCursorAdapter.java
deleted file mode 100644
index 26edf13ff..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/MultiTypeCursorAdapter.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v4.widget.CursorAdapter;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * MultiTypeCursorAdapter wraps a cursor and any meta data associated with it.
- * A set of view types (corresponding to the cursor and its meta data)
- * are mapped to a set of layouts.
- */
-abstract class MultiTypeCursorAdapter extends CursorAdapter {
- private final int[] mViewTypes;
- private final int[] mLayouts;
-
- // Bind the view for the given position.
- abstract public void bindView(View view, Context context, int position);
-
- public MultiTypeCursorAdapter(Context context, Cursor cursor, int[] viewTypes, int[] layouts) {
- super(context, cursor, 0);
-
- if (viewTypes.length != layouts.length) {
- throw new IllegalStateException("The view types and the layouts should be of same size");
- }
-
- mViewTypes = viewTypes;
- mLayouts = layouts;
- }
-
- @Override
- public final int getViewTypeCount() {
- return mViewTypes.length;
- }
-
- /**
- * @return Cursor for the given position.
- */
- public final Cursor getCursor(int position) {
- final Cursor cursor = getCursor();
- if (cursor == null || !cursor.moveToPosition(position)) {
- throw new IllegalStateException("Couldn't move cursor to position " + position);
- }
-
- return cursor;
- }
-
- @Override
- public final View getView(int position, View convertView, ViewGroup parent) {
- final Context context = parent.getContext();
- if (convertView == null) {
- convertView = newView(context, position, parent);
- }
-
- bindView(convertView, context, position);
- return convertView;
- }
-
- @Override
- public final void bindView(View view, Context context, Cursor cursor) {
- // Do nothing.
- }
-
- @Override
- public boolean hasStableIds() {
- return false;
- }
-
- @Override
- public final View newView(Context context, Cursor cursor, ViewGroup parent) {
- return null;
- }
-
- /**
- * Inflate a new view from a set of view types and layouts based on the position.
- *
- * @param context Context for inflating the view.
- * @param position Position of the view.
- * @param parent Parent view group that will hold this view.
- */
- private View newView(Context context, int position, ViewGroup parent) {
- final int type = getItemViewType(position);
- final int count = mViewTypes.length;
-
- for (int i = 0; i < count; i++) {
- if (mViewTypes[i] == type) {
- return LayoutInflater.from(context).inflate(mLayouts[i], parent, false);
- }
- }
-
- return null;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelAuthCache.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelAuthCache.java
deleted file mode 100644
index d66919344..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelAuthCache.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.util.Log;
-
-import org.mozilla.gecko.GeckoSharedPrefs;
-
-/**
- * Cache used to store authentication state of dynamic panels. The values
- * in this cache are set in JS through the Home.panels API.
- *
- * {@code DynamicPanel} uses this cache to determine whether or not to
- * show authentication UI for dynamic panels, including listening for
- * changes in authentication state.
- */
-class PanelAuthCache {
- private static final String LOGTAG = "GeckoPanelAuthCache";
-
- // Keep this in sync with the constant defined in Home.jsm
- private static final String PREFS_PANEL_AUTH_PREFIX = "home_panels_auth_";
-
- private final Context mContext;
- private SharedPrefsListener mSharedPrefsListener;
- private OnChangeListener mChangeListener;
-
- public interface OnChangeListener {
- public void onChange(String panelId, boolean isAuthenticated);
- }
-
- public PanelAuthCache(Context context) {
- mContext = context;
- }
-
- private SharedPreferences getSharedPreferences() {
- return GeckoSharedPrefs.forProfile(mContext);
- }
-
- private String getPanelAuthKey(String panelId) {
- return PREFS_PANEL_AUTH_PREFIX + panelId;
- }
-
- public boolean isAuthenticated(String panelId) {
- final SharedPreferences prefs = getSharedPreferences();
- return prefs.getBoolean(getPanelAuthKey(panelId), false);
- }
-
- public void setOnChangeListener(OnChangeListener listener) {
- final SharedPreferences prefs = getSharedPreferences();
-
- if (mChangeListener != null) {
- prefs.unregisterOnSharedPreferenceChangeListener(mSharedPrefsListener);
- mSharedPrefsListener = null;
- }
-
- mChangeListener = listener;
-
- if (mChangeListener != null) {
- mSharedPrefsListener = new SharedPrefsListener();
- prefs.registerOnSharedPreferenceChangeListener(mSharedPrefsListener);
- }
- }
-
- private class SharedPrefsListener implements OnSharedPreferenceChangeListener {
- @Override
- public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
- if (key.startsWith(PREFS_PANEL_AUTH_PREFIX)) {
- final String panelId = key.substring(PREFS_PANEL_AUTH_PREFIX.length());
- final boolean isAuthenticated = prefs.getBoolean(key, false);
-
- Log.d(LOGTAG, "Auth state changed: panelId=" + panelId + ", isAuthenticated=" + isAuthenticated);
- mChangeListener.onChange(panelId, isAuthenticated);
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelAuthLayout.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelAuthLayout.java
deleted file mode 100644
index 1ad91b7ca..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelAuthLayout.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.home.HomeConfig.AuthConfig;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-
-import android.content.Context;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.squareup.picasso.Picasso;
-
-class PanelAuthLayout extends LinearLayout {
-
- public PanelAuthLayout(Context context, PanelConfig panelConfig) {
- super(context);
-
- final AuthConfig authConfig = panelConfig.getAuthConfig();
- if (authConfig == null) {
- throw new IllegalStateException("Can't create PanelAuthLayout without a valid AuthConfig");
- }
-
- setOrientation(LinearLayout.VERTICAL);
- LayoutInflater.from(context).inflate(R.layout.panel_auth_layout, this);
-
- final TextView messageView = (TextView) findViewById(R.id.message);
- messageView.setText(authConfig.getMessageText());
-
- final Button buttonView = (Button) findViewById(R.id.button);
- buttonView.setText(authConfig.getButtonText());
-
- final String panelId = panelConfig.getId();
- buttonView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- GeckoAppShell.notifyObservers("HomePanels:Authenticate", panelId);
- }
- });
-
- final ImageView imageView = (ImageView) findViewById(R.id.image);
- final String imageUrl = authConfig.getImageUrl();
-
- if (TextUtils.isEmpty(imageUrl)) {
- // Use a default image if an image URL isn't specified.
- imageView.setImageResource(R.drawable.icon_home_empty_firefox);
- } else {
- ImageLoader.with(getContext())
- .load(imageUrl)
- .into(imageView);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelBackItemView.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelBackItemView.java
deleted file mode 100644
index 4772e08ab..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelBackItemView.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.home.PanelLayout.FilterDetail;
-
-import android.content.Context;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.squareup.picasso.Picasso;
-
-class PanelBackItemView extends LinearLayout {
- private final TextView title;
-
- public PanelBackItemView(Context context, String backImageUrl) {
- super(context);
-
- LayoutInflater.from(context).inflate(R.layout.panel_back_item, this);
- setOrientation(HORIZONTAL);
-
- title = (TextView) findViewById(R.id.title);
-
- final ImageView image = (ImageView) findViewById(R.id.image);
-
- if (TextUtils.isEmpty(backImageUrl)) {
- image.setImageResource(R.drawable.arrow_up);
- } else {
- ImageLoader.with(getContext())
- .load(backImageUrl)
- .placeholder(R.drawable.arrow_up)
- .into(image);
- }
- }
-
- public void updateFromFilter(FilterDetail filter) {
- final String backText = getResources()
- .getString(R.string.home_move_back_to_filter, filter.title);
- title.setText(backText);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelHeaderView.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelHeaderView.java
deleted file mode 100644
index 50c4dbc07..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelHeaderView.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.mozilla.gecko.home;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.widget.ImageView;
-
-@SuppressLint("ViewConstructor") // View is only created from code
-public class PanelHeaderView extends ImageView {
- public PanelHeaderView(Context context, HomeConfig.HeaderConfig config) {
- super(context);
-
- setAdjustViewBounds(true);
-
- ImageLoader.with(context)
- .load(config.getImageUrl())
- .into(this);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int width = MeasureSpec.getSize(widthMeasureSpec);
-
- // Always span the whole width and adjust height as needed.
- widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
-
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelInfoManager.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelInfoManager.java
deleted file mode 100644
index 089e17837..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelInfoManager.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.util.Log;
-import android.util.SparseArray;
-
-public class PanelInfoManager implements GeckoEventListener {
- private static final String LOGTAG = "GeckoPanelInfoManager";
-
- public class PanelInfo {
- private final String mId;
- private final String mTitle;
- private final JSONObject mJSONData;
-
- public PanelInfo(String id, String title, JSONObject jsonData) {
- mId = id;
- mTitle = title;
- mJSONData = jsonData;
- }
-
- public String getId() {
- return mId;
- }
-
- public String getTitle() {
- return mTitle;
- }
-
- public PanelConfig toPanelConfig() {
- try {
- return new PanelConfig(mJSONData);
- } catch (Exception e) {
- Log.e(LOGTAG, "Failed to convert PanelInfo to PanelConfig", e);
- return null;
- }
- }
- }
-
- public interface RequestCallback {
- public void onComplete(List<PanelInfo> panelInfos);
- }
-
- private static final AtomicInteger sRequestId = new AtomicInteger(0);
-
- // Stores set of pending request callbacks.
- private static final SparseArray<RequestCallback> sCallbacks = new SparseArray<RequestCallback>();
-
- /**
- * Asynchronously fetches list of available panels from Gecko
- * for the given IDs.
- *
- * @param ids list of panel ids to be fetched. A null value will fetch all
- * available panels.
- * @param callback onComplete will be called on the UI thread.
- */
- public void requestPanelsById(Set<String> ids, RequestCallback callback) {
- final int requestId = sRequestId.getAndIncrement();
-
- synchronized (sCallbacks) {
- // If there are no pending callbacks, register the event listener.
- if (sCallbacks.size() == 0) {
- GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
- "HomePanels:Data");
- }
- sCallbacks.put(requestId, callback);
- }
-
- final JSONObject message = new JSONObject();
- try {
- message.put("requestId", requestId);
-
- if (ids != null && ids.size() > 0) {
- JSONArray idsArray = new JSONArray();
- for (String id : ids) {
- idsArray.put(id);
- }
-
- message.put("ids", idsArray);
- }
- } catch (JSONException e) {
- Log.e(LOGTAG, "Failed to build event to request panels by id", e);
- return;
- }
-
- GeckoAppShell.notifyObservers("HomePanels:Get", message.toString());
- }
-
- /**
- * Asynchronously fetches list of available panels from Gecko.
- *
- * @param callback onComplete will be called on the UI thread.
- */
- public void requestAvailablePanels(RequestCallback callback) {
- requestPanelsById(null, callback);
- }
-
- /**
- * Handles "HomePanels:Data" events.
- */
- @Override
- public void handleMessage(String event, JSONObject message) {
- final ArrayList<PanelInfo> panelInfos = new ArrayList<PanelInfo>();
-
- try {
- final JSONArray panels = message.getJSONArray("panels");
- final int count = panels.length();
- for (int i = 0; i < count; i++) {
- final PanelInfo panelInfo = getPanelInfoFromJSON(panels.getJSONObject(i));
- panelInfos.add(panelInfo);
- }
-
- final RequestCallback callback;
- final int requestId = message.getInt("requestId");
-
- synchronized (sCallbacks) {
- callback = sCallbacks.get(requestId);
- sCallbacks.delete(requestId);
-
- // Unregister the event listener if there are no more pending callbacks.
- if (sCallbacks.size() == 0) {
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
- "HomePanels:Data");
- }
- }
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- callback.onComplete(panelInfos);
- }
- });
- } catch (JSONException e) {
- Log.e(LOGTAG, "Exception handling " + event + " message", e);
- }
- }
-
- private PanelInfo getPanelInfoFromJSON(JSONObject jsonPanelInfo) throws JSONException {
- final String id = jsonPanelInfo.getString("id");
- final String title = jsonPanelInfo.getString("title");
-
- return new PanelInfo(id, title, jsonPanelInfo);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelItemView.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelItemView.java
deleted file mode 100644
index 2a97d42bc..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelItemView.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract.HomeItems;
-import org.mozilla.gecko.home.HomeConfig.ItemType;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Color;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-class PanelItemView extends LinearLayout {
- private final TextView titleView;
- private final TextView descriptionView;
- private final ImageView imageView;
- private final LinearLayout titleDescContainerView;
- private final ImageView backgroundView;
-
- private PanelItemView(Context context, int layoutId) {
- super(context);
-
- LayoutInflater.from(context).inflate(layoutId, this);
- titleView = (TextView) findViewById(R.id.title);
- descriptionView = (TextView) findViewById(R.id.description);
- imageView = (ImageView) findViewById(R.id.image);
- backgroundView = (ImageView) findViewById(R.id.background);
- titleDescContainerView = (LinearLayout) findViewById(R.id.title_desc_container);
- }
-
- public void updateFromCursor(Cursor cursor) {
- int titleIndex = cursor.getColumnIndexOrThrow(HomeItems.TITLE);
- final String titleText = cursor.getString(titleIndex);
-
- // Only show title if the item has one
- final boolean hasTitle = !TextUtils.isEmpty(titleText);
- titleView.setVisibility(hasTitle ? View.VISIBLE : View.GONE);
- if (hasTitle) {
- titleView.setText(titleText);
- }
-
- int descriptionIndex = cursor.getColumnIndexOrThrow(HomeItems.DESCRIPTION);
- final String descriptionText = cursor.getString(descriptionIndex);
-
- // Only show description if the item has one
- // Descriptions are not supported for IconItemView objects (Bug 1157539)
- final boolean hasDescription = !TextUtils.isEmpty(descriptionText);
- if (descriptionView != null) {
- descriptionView.setVisibility(hasDescription ? View.VISIBLE : View.GONE);
- if (hasDescription) {
- descriptionView.setText(descriptionText);
- }
- }
- if (titleDescContainerView != null) {
- titleDescContainerView.setVisibility(hasTitle || hasDescription ? View.VISIBLE : View.GONE);
- }
-
- int imageIndex = cursor.getColumnIndexOrThrow(HomeItems.IMAGE_URL);
- final String imageUrl = cursor.getString(imageIndex);
-
- // Only try to load the image if the item has define image URL
- final boolean hasImageUrl = !TextUtils.isEmpty(imageUrl);
- imageView.setVisibility(hasImageUrl ? View.VISIBLE : View.GONE);
-
- if (hasImageUrl) {
- ImageLoader.with(getContext())
- .load(imageUrl)
- .into(imageView);
- }
-
- final int columnIndexBackgroundColor = cursor.getColumnIndex(HomeItems.BACKGROUND_COLOR);
- if (columnIndexBackgroundColor != -1) {
- final String color = cursor.getString(columnIndexBackgroundColor);
- if (!TextUtils.isEmpty(color)) {
- setBackgroundColor(Color.parseColor(color));
- }
- }
-
- // Backgrounds are only supported for IconItemView objects (Bug 1157539)
- final int columnIndexBackgroundUrl = cursor.getColumnIndex(HomeItems.BACKGROUND_URL);
- if (columnIndexBackgroundUrl != -1) {
- final String backgroundUrl = cursor.getString(columnIndexBackgroundUrl);
- if (backgroundView != null && !TextUtils.isEmpty(backgroundUrl)) {
- ImageLoader.with(getContext())
- .load(backgroundUrl)
- .fit()
- .into(backgroundView);
- }
- }
- }
-
- private static class ArticleItemView extends PanelItemView {
- private ArticleItemView(Context context) {
- super(context, R.layout.panel_article_item);
- setOrientation(LinearLayout.HORIZONTAL);
- }
- }
-
- private static class ImageItemView extends PanelItemView {
- private ImageItemView(Context context) {
- super(context, R.layout.panel_image_item);
- setOrientation(LinearLayout.VERTICAL);
- }
- }
-
- private static class IconItemView extends PanelItemView {
- private IconItemView(Context context) {
- super(context, R.layout.panel_icon_item);
- }
- }
-
- public static PanelItemView create(Context context, ItemType itemType) {
- switch (itemType) {
- case ARTICLE:
- return new ArticleItemView(context);
-
- case IMAGE:
- return new ImageItemView(context);
-
- case ICON:
- return new IconItemView(context);
-
- default:
- throw new IllegalArgumentException("Could not create panel item view from " + itemType);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelLayout.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelLayout.java
deleted file mode 100644
index 2c2d89ae0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelLayout.java
+++ /dev/null
@@ -1,747 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract.HomeItems;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.home.HomeConfig.EmptyViewConfig;
-import org.mozilla.gecko.home.HomeConfig.ItemHandler;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.home.HomeConfig.ViewConfig;
-import org.mozilla.gecko.util.StringUtils;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import java.lang.ref.SoftReference;
-import java.util.EnumSet;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.WeakHashMap;
-
-import com.squareup.picasso.Picasso;
-
-/**
- * {@code PanelLayout} is the base class for custom layouts to be
- * used in {@code DynamicPanel}. It provides the basic framework
- * that enables custom layouts to request and reset datasets and
- * create panel views. Furthermore, it automates most of the process
- * of binding panel views with their respective datasets.
- *
- * {@code PanelLayout} abstracts the implemention details of how
- * datasets are actually loaded through the {@DatasetHandler} interface.
- * {@code DatasetHandler} provides two operations: request and reset.
- * The results of the dataset requests done via the {@code DatasetHandler}
- * are delivered to the {@code PanelLayout} with the {@code deliverDataset()}
- * method.
- *
- * Subclasses of {@code PanelLayout} should simply use the utilities
- * provided by {@code PanelLayout}. Namely:
- *
- * {@code requestDataset()} - To fetch datasets and auto-bind them to
- * the existing panel views backed by them.
- *
- * {@code resetDataset()} - To release any resources associated with a
- * previously loaded dataset.
- *
- * {@code createPanelView()} - To create a panel view for a ViewConfig
- * associated with the panel.
- *
- * {@code disposePanelView()} - To dispose any dataset references associated
- * with the given view.
- *
- * {@code PanelLayout} subclasses should always use {@code createPanelView()}
- * to create the views dynamically created based on {@code ViewConfig}. This
- * allows {@code PanelLayout} to auto-bind datasets with panel views.
- * {@code PanelLayout} subclasses are free to have any type of views to arrange
- * the panel views in different ways.
- */
-abstract class PanelLayout extends FrameLayout {
- private static final String LOGTAG = "GeckoPanelLayout";
-
- protected final SparseArray<ViewState> mViewStates;
- private final PanelConfig mPanelConfig;
- private final DatasetHandler mDatasetHandler;
- private final OnUrlOpenListener mUrlOpenListener;
- private final ContextMenuRegistry mContextMenuRegistry;
-
- /**
- * To be used by panel views to express that they are
- * backed by datasets.
- */
- public interface DatasetBacked {
- public void setDataset(Cursor cursor);
- public void setFilterManager(FilterManager manager);
- }
-
- /**
- * To be used by requests made to {@code DatasetHandler}s to couple dataset ID with current
- * filter for queries on the database.
- */
- public static class DatasetRequest implements Parcelable {
- public enum Type implements Parcelable {
- DATASET_LOAD,
- FILTER_PUSH,
- FILTER_POP;
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(ordinal());
- }
-
- public static final Creator<Type> CREATOR = new Creator<Type>() {
- @Override
- public Type createFromParcel(final Parcel source) {
- return Type.values()[source.readInt()];
- }
-
- @Override
- public Type[] newArray(final int size) {
- return new Type[size];
- }
- };
- }
-
- private final int mViewIndex;
- private final Type mType;
- private final String mDatasetId;
- private final FilterDetail mFilterDetail;
-
- private DatasetRequest(Parcel in) {
- this.mViewIndex = in.readInt();
- this.mType = (Type) in.readParcelable(getClass().getClassLoader());
- this.mDatasetId = in.readString();
- this.mFilterDetail = (FilterDetail) in.readParcelable(getClass().getClassLoader());
- }
-
- public DatasetRequest(int index, String datasetId, FilterDetail filterDetail) {
- this(index, Type.DATASET_LOAD, datasetId, filterDetail);
- }
-
- public DatasetRequest(int index, Type type, String datasetId, FilterDetail filterDetail) {
- this.mViewIndex = index;
- this.mType = type;
- this.mDatasetId = datasetId;
- this.mFilterDetail = filterDetail;
- }
-
- public int getViewIndex() {
- return mViewIndex;
- }
-
- public Type getType() {
- return mType;
- }
-
- public String getDatasetId() {
- return mDatasetId;
- }
-
- public String getFilter() {
- return (mFilterDetail != null ? mFilterDetail.filter : null);
- }
-
- public FilterDetail getFilterDetail() {
- return mFilterDetail;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mViewIndex);
- dest.writeParcelable(mType, 0);
- dest.writeString(mDatasetId);
- dest.writeParcelable(mFilterDetail, 0);
- }
-
- public String toString() {
- return "{ index: " + mViewIndex +
- ", type: " + mType +
- ", dataset: " + mDatasetId +
- ", filter: " + mFilterDetail +
- " }";
- }
-
- public static final Creator<DatasetRequest> CREATOR = new Creator<DatasetRequest>() {
- @Override
- public DatasetRequest createFromParcel(Parcel in) {
- return new DatasetRequest(in);
- }
-
- @Override
- public DatasetRequest[] newArray(int size) {
- return new DatasetRequest[size];
- }
- };
- }
-
- /**
- * Defines the contract with the component that is responsible
- * for handling datasets requests.
- */
- public interface DatasetHandler {
- /**
- * Requests a dataset to be fetched and auto-bound to the
- * panel views backed by it.
- */
- public void requestDataset(DatasetRequest request);
-
- /**
- * Releases any resources associated with a panel view. It will
- * do nothing if the view with the given index been created
- * before.
- */
- public void resetDataset(int viewIndex);
- }
-
- public interface PanelView {
- public void setOnItemOpenListener(OnItemOpenListener listener);
- public void setOnKeyListener(OnKeyListener listener);
- public void setContextMenuInfoFactory(HomeContextMenuInfo.Factory factory);
- }
-
- public interface FilterManager {
- public FilterDetail getPreviousFilter();
- public boolean canGoBack();
- public void goBack();
- }
-
- public interface ContextMenuRegistry {
- public void register(View view);
- }
-
- public PanelLayout(Context context, PanelConfig panelConfig, DatasetHandler datasetHandler,
- OnUrlOpenListener urlOpenListener, ContextMenuRegistry contextMenuRegistry) {
- super(context);
- mViewStates = new SparseArray<ViewState>();
- mPanelConfig = panelConfig;
- mDatasetHandler = datasetHandler;
- mUrlOpenListener = urlOpenListener;
- mContextMenuRegistry = contextMenuRegistry;
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- final int count = mViewStates.size();
- for (int i = 0; i < count; i++) {
- final ViewState viewState = mViewStates.valueAt(i);
-
- final View view = viewState.getView();
- if (view != null) {
- maybeSetDataset(view, null);
- }
- }
- mViewStates.clear();
- }
-
- /**
- * Delivers the dataset as a {@code Cursor} to be bound to the
- * panel view backed by it. This is used by the {@code DatasetHandler}
- * in response to a dataset request.
- */
- public final void deliverDataset(DatasetRequest request, Cursor cursor) {
- Log.d(LOGTAG, "Delivering request: " + request);
- final ViewState viewState = mViewStates.get(request.getViewIndex());
- if (viewState == null) {
- return;
- }
-
- switch (request.getType()) {
- case FILTER_PUSH:
- viewState.pushFilter(request.getFilterDetail());
- break;
- case FILTER_POP:
- viewState.popFilter();
- break;
- }
-
- final View activeView = viewState.getActiveView();
- if (activeView == null) {
- throw new IllegalStateException("No active view for view state: " + viewState.getIndex());
- }
-
- final ViewConfig viewConfig = viewState.getViewConfig();
-
- final View newView;
- if (cursor == null || cursor.getCount() == 0) {
- newView = createEmptyView(viewConfig);
- maybeSetDataset(activeView, null);
- } else {
- newView = createPanelView(viewConfig);
- maybeSetDataset(newView, cursor);
- }
-
- if (activeView != newView) {
- replacePanelView(activeView, newView);
- }
- }
-
- /**
- * Releases any references to the given dataset from all
- * existing panel views.
- */
- public final void releaseDataset(int viewIndex) {
- Log.d(LOGTAG, "Releasing dataset: " + viewIndex);
- final ViewState viewState = mViewStates.get(viewIndex);
- if (viewState == null) {
- return;
- }
-
- final View view = viewState.getView();
- if (view != null) {
- maybeSetDataset(view, null);
- }
- }
-
- /**
- * Requests a dataset to be loaded and bound to any existing
- * panel view backed by it.
- */
- protected final void requestDataset(DatasetRequest request) {
- Log.d(LOGTAG, "Requesting request: " + request);
- if (mViewStates.get(request.getViewIndex()) == null) {
- return;
- }
-
- mDatasetHandler.requestDataset(request);
- }
-
- /**
- * Releases any resources associated with a panel view.
- * e.g. close any associated {@code Cursor}.
- */
- protected final void resetDataset(int viewIndex) {
- Log.d(LOGTAG, "Resetting view with index: " + viewIndex);
- if (mViewStates.get(viewIndex) == null) {
- return;
- }
-
- mDatasetHandler.resetDataset(viewIndex);
- }
-
- /**
- * Factory method to create instance of panels from a given
- * {@code ViewConfig}. All panel views defined in {@code PanelConfig}
- * should be created using this method so that {@PanelLayout} can
- * keep track of panel views and their associated datasets.
- */
- protected final View createPanelView(ViewConfig viewConfig) {
- Log.d(LOGTAG, "Creating panel view: " + viewConfig.getType());
-
- ViewState viewState = mViewStates.get(viewConfig.getIndex());
- if (viewState == null) {
- viewState = new ViewState(viewConfig);
- mViewStates.put(viewConfig.getIndex(), viewState);
- }
-
- View view = viewState.getView();
- if (view == null) {
- switch (viewConfig.getType()) {
- case LIST:
- view = new PanelListView(getContext(), viewConfig);
- break;
-
- case GRID:
- view = new PanelRecyclerView(getContext(), viewConfig);
- break;
-
- default:
- throw new IllegalStateException("Unrecognized view type in " + getClass().getSimpleName());
- }
-
- PanelView panelView = (PanelView) view;
- panelView.setOnItemOpenListener(new PanelOnItemOpenListener(viewState));
- panelView.setOnKeyListener(new PanelKeyListener(viewState));
- panelView.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
- @Override
- public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
- final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
- info.url = cursor.getString(cursor.getColumnIndexOrThrow(HomeItems.URL));
- info.title = cursor.getString(cursor.getColumnIndexOrThrow(HomeItems.TITLE));
- return info;
- }
- });
-
- mContextMenuRegistry.register(view);
-
- if (view instanceof DatasetBacked) {
- DatasetBacked datasetBacked = (DatasetBacked) view;
- datasetBacked.setFilterManager(new PanelFilterManager(viewState));
-
- if (viewConfig.isRefreshEnabled()) {
- view = new PanelRefreshLayout(getContext(), view,
- mPanelConfig.getId(), viewConfig.getIndex());
- }
- }
-
- viewState.setView(view);
- }
-
- return view;
- }
-
- /**
- * Dispose any dataset references associated with the
- * given view.
- */
- protected final void disposePanelView(View view) {
- Log.d(LOGTAG, "Disposing panel view");
- final int count = mViewStates.size();
- for (int i = 0; i < count; i++) {
- final ViewState viewState = mViewStates.valueAt(i);
-
- if (viewState.getView() == view) {
- maybeSetDataset(view, null);
- mViewStates.remove(viewState.getIndex());
- break;
- }
- }
- }
-
- private void maybeSetDataset(View view, Cursor cursor) {
- if (view instanceof DatasetBacked) {
- final DatasetBacked dsb = (DatasetBacked) view;
- dsb.setDataset(cursor);
- }
- }
-
- private View createEmptyView(ViewConfig viewConfig) {
- Log.d(LOGTAG, "Creating empty view: " + viewConfig.getType());
-
- ViewState viewState = mViewStates.get(viewConfig.getIndex());
- if (viewState == null) {
- throw new IllegalStateException("No view state found for view index: " + viewConfig.getIndex());
- }
-
- View view = viewState.getEmptyView();
- if (view == null) {
- view = LayoutInflater.from(getContext()).inflate(R.layout.home_empty_panel, null);
-
- final EmptyViewConfig emptyViewConfig = viewConfig.getEmptyViewConfig();
-
- // XXX: Refactor this into a custom view (bug 985134)
- final String text = (emptyViewConfig == null) ? null : emptyViewConfig.getText();
- final TextView textView = (TextView) view.findViewById(R.id.home_empty_text);
- if (TextUtils.isEmpty(text)) {
- textView.setText(R.string.home_default_empty);
- } else {
- textView.setText(text);
- }
-
- final String imageUrl = (emptyViewConfig == null) ? null : emptyViewConfig.getImageUrl();
- final ImageView imageView = (ImageView) view.findViewById(R.id.home_empty_image);
-
- if (TextUtils.isEmpty(imageUrl)) {
- imageView.setImageResource(R.drawable.icon_home_empty_firefox);
- } else {
- ImageLoader.with(getContext())
- .load(imageUrl)
- .error(R.drawable.icon_home_empty_firefox)
- .into(imageView);
- }
-
- viewState.setEmptyView(view);
- }
-
- return view;
- }
-
- private void replacePanelView(View currentView, View newView) {
- final ViewGroup parent = (ViewGroup) currentView.getParent();
- parent.addView(newView, parent.indexOfChild(currentView), currentView.getLayoutParams());
- parent.removeView(currentView);
- }
-
- /**
- * Must be implemented by {@code PanelLayout} subclasses to define
- * what happens then the layout is first loaded. Should set initial
- * UI state and request any necessary datasets.
- */
- public abstract void load();
-
- /**
- * Represents a 'live' instance of a panel view associated with
- * the {@code PanelLayout}. Is responsible for tracking the history stack of filters.
- */
- protected class ViewState {
- private final ViewConfig mViewConfig;
- private SoftReference<View> mView;
- private SoftReference<View> mEmptyView;
- private LinkedList<FilterDetail> mFilterStack;
-
- public ViewState(ViewConfig viewConfig) {
- mViewConfig = viewConfig;
- mView = new SoftReference<View>(null);
- mEmptyView = new SoftReference<View>(null);
- }
-
- public ViewConfig getViewConfig() {
- return mViewConfig;
- }
-
- public int getIndex() {
- return mViewConfig.getIndex();
- }
-
- public View getView() {
- return mView.get();
- }
-
- public void setView(View view) {
- mView = new SoftReference<View>(view);
- }
-
- public View getEmptyView() {
- return mEmptyView.get();
- }
-
- public void setEmptyView(View view) {
- mEmptyView = new SoftReference<View>(view);
- }
-
- public View getActiveView() {
- final View view = getView();
- if (view != null && view.getParent() != null) {
- return view;
- }
-
- final View emptyView = getEmptyView();
- if (emptyView != null && emptyView.getParent() != null) {
- return emptyView;
- }
-
- return null;
- }
-
- public String getDatasetId() {
- return mViewConfig.getDatasetId();
- }
-
- public ItemHandler getItemHandler() {
- return mViewConfig.getItemHandler();
- }
-
- /**
- * Get the current filter that this view is displaying, or null if none.
- */
- public FilterDetail getCurrentFilter() {
- if (mFilterStack == null) {
- return null;
- } else {
- return mFilterStack.peek();
- }
- }
-
- /**
- * Get the previous filter that this view was displaying, or null if none.
- */
- public FilterDetail getPreviousFilter() {
- if (!canPopFilter()) {
- return null;
- }
-
- return mFilterStack.get(1);
- }
-
- /**
- * Adds a filter to the history stack for this view.
- */
- public void pushFilter(FilterDetail filter) {
- if (mFilterStack == null) {
- mFilterStack = new LinkedList<FilterDetail>();
-
- // Initialize with the initial filter.
- mFilterStack.push(new FilterDetail(mViewConfig.getFilter(),
- mPanelConfig.getTitle()));
- }
-
- mFilterStack.push(filter);
- }
-
- /**
- * Remove the most recent filter from the stack.
- *
- * @return whether the filter was popped
- */
- public boolean popFilter() {
- if (!canPopFilter()) {
- return false;
- }
-
- mFilterStack.pop();
- return true;
- }
-
- public boolean canPopFilter() {
- return (mFilterStack != null && mFilterStack.size() > 1);
- }
- }
-
- static class FilterDetail implements Parcelable {
- final String filter;
- final String title;
-
- private FilterDetail(Parcel in) {
- this.filter = in.readString();
- this.title = in.readString();
- }
-
- public FilterDetail(String filter, String title) {
- this.filter = filter;
- this.title = title;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(filter);
- dest.writeString(title);
- }
-
- public static final Creator<FilterDetail> CREATOR = new Creator<FilterDetail>() {
- @Override
- public FilterDetail createFromParcel(Parcel in) {
- return new FilterDetail(in);
- }
-
- @Override
- public FilterDetail[] newArray(int size) {
- return new FilterDetail[size];
- }
- };
- }
-
- /**
- * Pushes filter to {@code ViewState}'s stack and makes request for new filter value.
- */
- private void pushFilterOnView(ViewState viewState, FilterDetail filterDetail) {
- final int index = viewState.getIndex();
- final String datasetId = viewState.getDatasetId();
-
- mDatasetHandler.requestDataset(new DatasetRequest(index,
- DatasetRequest.Type.FILTER_PUSH,
- datasetId,
- filterDetail));
- }
-
- /**
- * Pops filter from {@code ViewState}'s stack and makes request for previous filter value.
- *
- * @return whether the filter has changed
- */
- private boolean popFilterOnView(ViewState viewState) {
- if (viewState.canPopFilter()) {
- final int index = viewState.getIndex();
- final String datasetId = viewState.getDatasetId();
- final FilterDetail filterDetail = viewState.getPreviousFilter();
-
- mDatasetHandler.requestDataset(new DatasetRequest(index,
- DatasetRequest.Type.FILTER_POP,
- datasetId,
- filterDetail));
-
- return true;
- } else {
- return false;
- }
- }
-
- public interface OnItemOpenListener {
- public void onItemOpen(String url, String title);
- }
-
- private class PanelOnItemOpenListener implements OnItemOpenListener {
- private final ViewState mViewState;
-
- public PanelOnItemOpenListener(ViewState viewState) {
- mViewState = viewState;
- }
-
- @Override
- public void onItemOpen(String url, String title) {
- if (StringUtils.isFilterUrl(url)) {
- FilterDetail filterDetail = new FilterDetail(StringUtils.getFilterFromUrl(url), title);
- pushFilterOnView(mViewState, filterDetail);
- } else {
- EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.noneOf(OnUrlOpenListener.Flags.class);
- if (mViewState.getItemHandler() == ItemHandler.INTENT) {
- flags.add(OnUrlOpenListener.Flags.OPEN_WITH_INTENT);
- }
-
- mUrlOpenListener.onUrlOpen(url, flags);
- }
- }
- }
-
- private class PanelKeyListener implements View.OnKeyListener {
- private final ViewState mViewState;
-
- public PanelKeyListener(ViewState viewState) {
- mViewState = viewState;
- }
-
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
- return popFilterOnView(mViewState);
- }
-
- return false;
- }
- }
-
- private class PanelFilterManager implements FilterManager {
- private final ViewState mViewState;
-
- public PanelFilterManager(ViewState viewState) {
- mViewState = viewState;
- }
-
- @Override
- public FilterDetail getPreviousFilter() {
- return mViewState.getPreviousFilter();
- }
-
- @Override
- public boolean canGoBack() {
- return mViewState.canPopFilter();
- }
-
- @Override
- public void goBack() {
- popFilterOnView(mViewState);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelListView.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelListView.java
deleted file mode 100644
index 505fb9b0d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelListView.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.EnumSet;
-
-import org.mozilla.gecko.db.BrowserContract.HomeItems;
-import org.mozilla.gecko.home.HomeConfig.ItemHandler;
-import org.mozilla.gecko.home.HomeConfig.ViewConfig;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.home.PanelLayout.DatasetBacked;
-import org.mozilla.gecko.home.PanelLayout.FilterManager;
-import org.mozilla.gecko.home.PanelLayout.OnItemOpenListener;
-import org.mozilla.gecko.home.PanelLayout.PanelView;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.util.Log;
-import android.view.View;
-import android.widget.AdapterView;
-
-public class PanelListView extends HomeListView
- implements DatasetBacked, PanelView {
-
- private static final String LOGTAG = "GeckoPanelListView";
-
- private final ViewConfig viewConfig;
- private final PanelViewAdapter adapter;
- private final PanelViewItemHandler itemHandler;
- private OnItemOpenListener itemOpenListener;
-
- public PanelListView(Context context, ViewConfig viewConfig) {
- super(context);
-
- this.viewConfig = viewConfig;
- itemHandler = new PanelViewItemHandler();
-
- adapter = new PanelViewAdapter(context, viewConfig);
- setAdapter(adapter);
-
- setOnItemClickListener(new PanelListItemClickListener());
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- itemHandler.setOnItemOpenListener(itemOpenListener);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- itemHandler.setOnItemOpenListener(null);
- }
-
- @Override
- public void setDataset(Cursor cursor) {
- Log.d(LOGTAG, "Setting dataset: " + viewConfig.getDatasetId());
- adapter.swapCursor(cursor);
- }
-
- @Override
- public void setOnItemOpenListener(OnItemOpenListener listener) {
- itemHandler.setOnItemOpenListener(listener);
- itemOpenListener = listener;
- }
-
- @Override
- public void setFilterManager(FilterManager filterManager) {
- adapter.setFilterManager(filterManager);
- itemHandler.setFilterManager(filterManager);
- }
-
- private class PanelListItemClickListener implements AdapterView.OnItemClickListener {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- itemHandler.openItemAtPosition(adapter.getCursor(), position);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelRecyclerView.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelRecyclerView.java
deleted file mode 100644
index 9145ab1e1..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelRecyclerView.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.home.PanelLayout.DatasetBacked;
-import org.mozilla.gecko.home.PanelLayout.PanelView;
-import org.mozilla.gecko.widget.RecyclerViewClickSupport;
-import org.mozilla.gecko.widget.RecyclerViewClickSupport.OnItemClickListener;
-import org.mozilla.gecko.widget.RecyclerViewClickSupport.OnItemLongClickListener;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.support.v7.widget.GridLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.view.View;
-
-/**
- * RecyclerView implementation for grid home panels.
- */
-@SuppressLint("ViewConstructor") // View is only created from code
-public class PanelRecyclerView extends RecyclerView
- implements DatasetBacked, PanelView, OnItemClickListener, OnItemLongClickListener {
- private final PanelRecyclerViewAdapter adapter;
- private final GridLayoutManager layoutManager;
- private final PanelViewItemHandler itemHandler;
- private final float columnWidth;
- private final boolean autoFit;
- private final HomeConfig.ViewConfig viewConfig;
-
- private PanelLayout.OnItemOpenListener itemOpenListener;
- private HomeContextMenuInfo contextMenuInfo;
- private HomeContextMenuInfo.Factory contextMenuInfoFactory;
-
- public PanelRecyclerView(Context context, HomeConfig.ViewConfig viewConfig) {
- super(context);
-
- this.viewConfig = viewConfig;
-
- final Resources resources = context.getResources();
-
- int spanCount;
- if (viewConfig.getItemType() == HomeConfig.ItemType.ICON) {
- autoFit = false;
- spanCount = getResources().getInteger(R.integer.panel_icon_grid_view_columns);
- } else {
- autoFit = true;
- spanCount = 1;
- }
-
- columnWidth = resources.getDimension(R.dimen.panel_grid_view_column_width);
- layoutManager = new GridLayoutManager(context, spanCount);
- adapter = new PanelRecyclerViewAdapter(context, viewConfig);
- itemHandler = new PanelViewItemHandler();
-
- layoutManager.setSpanSizeLookup(new PanelSpanSizeLookup());
-
- setLayoutManager(layoutManager);
- setAdapter(adapter);
-
- int horizontalSpacing = (int) resources.getDimension(R.dimen.panel_grid_view_horizontal_spacing);
- int verticalSpacing = (int) resources.getDimension(R.dimen.panel_grid_view_vertical_spacing);
- int outerSpacing = (int) resources.getDimension(R.dimen.panel_grid_view_outer_spacing);
-
- addItemDecoration(new SpacingDecoration(horizontalSpacing, verticalSpacing));
-
- setPadding(outerSpacing, outerSpacing, outerSpacing, outerSpacing);
- setClipToPadding(false);
-
- RecyclerViewClickSupport.addTo(this)
- .setOnItemClickListener(this)
- .setOnItemLongClickListener(this);
- }
-
- @Override
- protected void onMeasure(int widthSpec, int heightSpec) {
- super.onMeasure(widthSpec, heightSpec);
-
- if (autoFit) {
- // Adjust span based on space available (What GridView does when you say numColumns="auto_fit")
- final int spanCount = (int) Math.max(1, getMeasuredWidth() / columnWidth);
- layoutManager.setSpanCount(spanCount);
- }
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- itemHandler.setOnItemOpenListener(itemOpenListener);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- itemHandler.setOnItemOpenListener(null);
- }
-
- @Override
- public void setDataset(Cursor cursor) {
- adapter.swapCursor(cursor);
- }
-
- @Override
- public void setFilterManager(PanelLayout.FilterManager manager) {
- adapter.setFilterManager(manager);
- itemHandler.setFilterManager(manager);
- }
-
- @Override
- public void setOnItemOpenListener(PanelLayout.OnItemOpenListener listener) {
- itemOpenListener = listener;
- itemHandler.setOnItemOpenListener(listener);
- }
-
- @Override
- public HomeContextMenuInfo getContextMenuInfo() {
- return contextMenuInfo;
- }
-
- @Override
- public void setContextMenuInfoFactory(HomeContextMenuInfo.Factory factory) {
- contextMenuInfoFactory = factory;
- }
-
- @Override
- public void onItemClicked(RecyclerView recyclerView, int position, View v) {
- if (viewConfig.hasHeaderConfig()) {
- if (position == 0) {
- itemOpenListener.onItemOpen(viewConfig.getHeaderConfig().getUrl(), null);
- return;
- }
-
- position--;
- }
-
- itemHandler.openItemAtPosition(adapter.getCursor(), position);
- }
-
- @Override
- public boolean onItemLongClicked(RecyclerView recyclerView, int position, View v) {
- if (viewConfig.hasHeaderConfig()) {
- if (position == 0) {
- final HomeConfig.HeaderConfig headerConfig = viewConfig.getHeaderConfig();
-
- final HomeContextMenuInfo info = new HomeContextMenuInfo(v, position, -1);
- info.url = headerConfig.getUrl();
- info.title = headerConfig.getUrl();
-
- contextMenuInfo = info;
- return showContextMenuForChild(this);
- }
-
- position--;
- }
-
- Cursor cursor = adapter.getCursor();
- cursor.moveToPosition(position);
-
- contextMenuInfo = contextMenuInfoFactory.makeInfoForCursor(recyclerView, position, -1, cursor);
- return showContextMenuForChild(this);
- }
-
- private class PanelSpanSizeLookup extends GridLayoutManager.SpanSizeLookup {
- @Override
- public int getSpanSize(int position) {
- if (position == 0 && viewConfig.hasHeaderConfig()) {
- return layoutManager.getSpanCount();
- }
-
- return 1;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelRecyclerViewAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelRecyclerViewAdapter.java
deleted file mode 100644
index fa632bccd..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelRecyclerViewAdapter.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-public class PanelRecyclerViewAdapter extends RecyclerView.Adapter<PanelRecyclerViewAdapter.PanelViewHolder> {
- private static final int VIEW_TYPE_ITEM = 0;
- private static final int VIEW_TYPE_BACK = 1;
- private static final int VIEW_TYPE_HEADER = 2;
-
- public static class PanelViewHolder extends RecyclerView.ViewHolder {
- public static PanelViewHolder create(View itemView) {
-
- // Wrap in a FrameLayout that will handle the highlight on touch
- FrameLayout frameLayout = (FrameLayout) LayoutInflater.from(itemView.getContext())
- .inflate(R.layout.panel_item_container, null);
-
- frameLayout.addView(itemView, 0, new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
-
- return new PanelViewHolder(frameLayout);
- }
-
- private PanelViewHolder(View itemView) {
- super(itemView);
- }
- }
-
- private final Context context;
- private final HomeConfig.ViewConfig viewConfig;
- private PanelLayout.FilterManager filterManager;
- private Cursor cursor;
-
- public PanelRecyclerViewAdapter(Context context, HomeConfig.ViewConfig viewConfig) {
- this.context = context;
- this.viewConfig = viewConfig;
- }
-
- public void setFilterManager(PanelLayout.FilterManager filterManager) {
- this.filterManager = filterManager;
- }
-
- private boolean isShowingBack() {
- return filterManager != null && filterManager.canGoBack();
- }
-
- public void swapCursor(Cursor cursor) {
- this.cursor = cursor;
-
- notifyDataSetChanged();
- }
-
- public Cursor getCursor() {
- return cursor;
- }
-
- @Override
- public int getItemViewType(int position) {
- if (viewConfig.hasHeaderConfig() && position == 0) {
- return VIEW_TYPE_HEADER;
- } else if (isShowingBack() && position == getBackPosition()) {
- return VIEW_TYPE_BACK;
- } else {
- return VIEW_TYPE_ITEM;
- }
- }
-
- @Override
- public PanelViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
- switch (viewType) {
- case VIEW_TYPE_HEADER:
- return PanelViewHolder.create(new PanelHeaderView(context, viewConfig.getHeaderConfig()));
- case VIEW_TYPE_BACK:
- return PanelViewHolder.create(new PanelBackItemView(context, viewConfig.getBackImageUrl()));
- case VIEW_TYPE_ITEM:
- return PanelViewHolder.create(PanelItemView.create(context, viewConfig.getItemType()));
- default:
- throw new IllegalArgumentException("Unknown view type: " + viewType);
- }
- }
-
- @Override
- public void onBindViewHolder(PanelViewHolder panelViewHolder, int position) {
- final View view = ((FrameLayout) panelViewHolder.itemView).getChildAt(0);
-
- if (viewConfig.hasHeaderConfig()) {
- if (position == 0) {
- // Nothing to do here, the header is static
- return;
- }
- }
-
- if (isShowingBack()) {
- if (position == getBackPosition()) {
- final PanelBackItemView item = (PanelBackItemView) view;
- item.updateFromFilter(filterManager.getPreviousFilter());
- return;
- }
- }
-
- int actualPosition = position
- - (isShowingBack() ? 1 : 0)
- - (viewConfig.hasHeaderConfig() ? 1 : 0);
-
- cursor.moveToPosition(actualPosition);
-
- final PanelItemView panelItemView = (PanelItemView) view;
- panelItemView.updateFromCursor(cursor);
- }
-
- private int getBackPosition() {
- return viewConfig.hasHeaderConfig() ? 1 : 0;
- }
-
- @Override
- public int getItemCount() {
- if (cursor == null) {
- return 0;
- }
-
- return cursor.getCount()
- + (isShowingBack() ? 1 : 0)
- + (viewConfig.hasHeaderConfig() ? 1 : 0);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelRefreshLayout.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelRefreshLayout.java
deleted file mode 100644
index d43a97f31..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelRefreshLayout.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.R;
-
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.home.PanelLayout.DatasetBacked;
-import org.mozilla.gecko.home.PanelLayout.FilterManager;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v4.widget.SwipeRefreshLayout;
-import android.util.Log;
-import android.view.View;
-
-/**
- * Used to wrap a {@code DatasetBacked} ListView or GridView to give the child view swipe-to-refresh
- * capabilities.
- *
- * This view acts as a decorator to forward the {@code DatasetBacked} methods to the child view
- * while providing the refresh gesture support on top of it.
- */
-class PanelRefreshLayout extends SwipeRefreshLayout implements DatasetBacked {
- private static final String LOGTAG = "GeckoPanelRefreshLayout";
-
- private static final String JSON_KEY_PANEL_ID = "panelId";
- private static final String JSON_KEY_VIEW_INDEX = "viewIndex";
-
- private final String panelId;
- private final int viewIndex;
- private final DatasetBacked datasetBacked;
-
- /**
- * @param context Android context.
- * @param childView ListView or GridView. Must implement {@code DatasetBacked}.
- * @param panelId The ID from the {@code PanelConfig}.
- * @param viewIndex The index from the {@code ViewConfig}.
- */
- public PanelRefreshLayout(Context context, View childView, String panelId, int viewIndex) {
- super(context);
-
- if (!(childView instanceof DatasetBacked)) {
- throw new IllegalArgumentException("View must implement DatasetBacked to be refreshed");
- }
-
- this.panelId = panelId;
- this.viewIndex = viewIndex;
- this.datasetBacked = (DatasetBacked) childView;
-
- setOnRefreshListener(new RefreshListener());
- addView(childView);
-
- // Must be called after the child view has been added.
- setColorSchemeResources(R.color.fennec_ui_orange, R.color.action_orange);
- }
-
- @Override
- public void setDataset(Cursor cursor) {
- datasetBacked.setDataset(cursor);
- setRefreshing(false);
- }
-
- @Override
- public void setFilterManager(FilterManager manager) {
- datasetBacked.setFilterManager(manager);
- }
-
- private class RefreshListener implements OnRefreshListener {
- @Override
- public void onRefresh() {
- final JSONObject response = new JSONObject();
- try {
- response.put(JSON_KEY_PANEL_ID, panelId);
- response.put(JSON_KEY_VIEW_INDEX, viewIndex);
- } catch (JSONException e) {
- Log.e(LOGTAG, "Could not create refresh message", e);
- return;
- }
-
- GeckoAppShell.notifyObservers("HomePanels:RefreshView", response.toString());
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelViewAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelViewAdapter.java
deleted file mode 100644
index cf03c50c0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelViewAdapter.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.home.HomeConfig.ItemType;
-import org.mozilla.gecko.home.HomeConfig.ViewConfig;
-import org.mozilla.gecko.home.PanelLayout.FilterManager;
-
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v4.widget.CursorAdapter;
-import android.view.View;
-import android.view.ViewGroup;
-
-class PanelViewAdapter extends CursorAdapter {
- private static final int VIEW_TYPE_ITEM = 0;
- private static final int VIEW_TYPE_BACK = 1;
-
- private final ViewConfig viewConfig;
- private FilterManager filterManager;
- private final Context context;
-
- public PanelViewAdapter(Context context, ViewConfig viewConfig) {
- super(context, null, 0);
- this.context = context;
- this.viewConfig = viewConfig;
- }
-
- public void setFilterManager(FilterManager manager) {
- this.filterManager = manager;
- }
-
- @Override
- public final int getViewTypeCount() {
- return 2;
- }
-
- @Override
- public int getCount() {
- return super.getCount() + (isShowingBack() ? 1 : 0);
- }
-
- @Override
- public int getItemViewType(int position) {
- if (isShowingBack() && position == 0) {
- return VIEW_TYPE_BACK;
- } else {
- return VIEW_TYPE_ITEM;
- }
- }
-
- @Override
- public final View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = newView(parent.getContext(), position, parent);
- }
-
- bindView(convertView, position);
- return convertView;
- }
-
- private View newView(Context context, int position, ViewGroup parent) {
- if (getItemViewType(position) == VIEW_TYPE_BACK) {
- return new PanelBackItemView(context, viewConfig.getBackImageUrl());
- } else {
- return PanelItemView.create(context, viewConfig.getItemType());
- }
- }
-
- private void bindView(View view, int position) {
- if (isShowingBack()) {
- if (position == 0) {
- final PanelBackItemView item = (PanelBackItemView) view;
- item.updateFromFilter(filterManager.getPreviousFilter());
- return;
- }
-
- position--;
- }
-
- final Cursor cursor = getCursor(position);
- final PanelItemView item = (PanelItemView) view;
- item.updateFromCursor(cursor);
- }
-
- private boolean isShowingBack() {
- return filterManager != null && filterManager.canGoBack();
- }
-
- private final Cursor getCursor(int position) {
- final Cursor cursor = getCursor();
- if (cursor == null || !cursor.moveToPosition(position)) {
- throw new IllegalStateException("Couldn't move cursor to position " + position);
- }
-
- return cursor;
- }
-
- @Override
- public final void bindView(View view, Context context, Cursor cursor) {
- // Do nothing.
- }
-
- @Override
- public final View newView(Context context, Cursor cursor, ViewGroup parent) {
- return null;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PanelViewItemHandler.java b/mobile/android/base/java/org/mozilla/gecko/home/PanelViewItemHandler.java
deleted file mode 100644
index a69db0b41..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PanelViewItemHandler.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.db.BrowserContract.HomeItems;
-import org.mozilla.gecko.home.HomeConfig.ViewConfig;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.home.PanelLayout.FilterManager;
-import org.mozilla.gecko.home.PanelLayout.OnItemOpenListener;
-
-import android.database.Cursor;
-
-import java.util.EnumSet;
-
-class PanelViewItemHandler {
- private OnItemOpenListener mItemOpenListener;
- private FilterManager mFilterManager;
-
- public void setOnItemOpenListener(OnItemOpenListener listener) {
- mItemOpenListener = listener;
- }
-
- public void setFilterManager(FilterManager manager) {
- mFilterManager = manager;
- }
-
- /**
- * If item at this position is a back item, perform the go back action via the
- * {@code FilterManager}. Otherwise, prepare the url to be opened by the
- * {@code OnUrlOpenListener}.
- */
- public void openItemAtPosition(Cursor cursor, int position) {
- if (mFilterManager != null && mFilterManager.canGoBack()) {
- if (position == 0) {
- mFilterManager.goBack();
- return;
- }
-
- position--;
- }
-
- if (cursor == null || !cursor.moveToPosition(position)) {
- throw new IllegalStateException("Couldn't move cursor to position " + position);
- }
-
- int urlIndex = cursor.getColumnIndexOrThrow(HomeItems.URL);
- final String url = cursor.getString(urlIndex);
-
- int titleIndex = cursor.getColumnIndexOrThrow(HomeItems.TITLE);
- final String title = cursor.getString(titleIndex);
-
- if (mItemOpenListener != null) {
- mItemOpenListener.onItemOpen(url, title);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/PinSiteDialog.java b/mobile/android/base/java/org/mozilla/gecko/home/PinSiteDialog.java
deleted file mode 100644
index 230b1d329..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/PinSiteDialog.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.EnumSet;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract.URLColumns;
-import org.mozilla.gecko.db.BrowserDB.FilterFlags;
-import org.mozilla.gecko.util.StringUtils;
-
-import android.app.Dialog;
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.support.v4.app.DialogFragment;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.app.LoaderManager.LoaderCallbacks;
-import android.support.v4.content.Loader;
-import android.support.v4.widget.CursorAdapter;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.AdapterView;
-import android.widget.EditText;
-import android.widget.ListView;
-
-/**
- * Dialog fragment that displays frecency search results, for pinning a site, in a GridView.
- */
-class PinSiteDialog extends DialogFragment {
- // Listener for url selection
- public static interface OnSiteSelectedListener {
- public void onSiteSelected(String url, String title);
- }
-
- // Cursor loader ID for search query
- private static final int LOADER_ID_SEARCH = 0;
-
- // Holds the current search term to use in the query
- private String mSearchTerm;
-
- // Adapter for the list of search results
- private SearchAdapter mAdapter;
-
- // Search entry
- private EditText mSearch;
-
- // Search results
- private ListView mList;
-
- // Callbacks used for the search loader
- private CursorLoaderCallbacks mLoaderCallbacks;
-
- // Bookmark selected listener
- private OnSiteSelectedListener mOnSiteSelectedListener;
-
- public static PinSiteDialog newInstance() {
- return new PinSiteDialog();
- }
-
- private PinSiteDialog() {
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setStyle(DialogFragment.STYLE_NO_TITLE, android.R.style.Theme_Holo_Light_Dialog);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- // All list views are styled to look the same with a global activity theme.
- // If the style of the list changes, inflate it from an XML.
- return inflater.inflate(R.layout.pin_site_dialog, container, false);
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- mSearch = (EditText) view.findViewById(R.id.search);
- mSearch.addTextChangedListener(new TextWatcher() {
- @Override
- public void afterTextChanged(Editable s) {
- }
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- setSearchTerm(mSearch.getText().toString());
- filter(mSearchTerm);
- }
- });
-
- mSearch.setOnKeyListener(new View.OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (keyCode != KeyEvent.KEYCODE_ENTER || mOnSiteSelectedListener == null) {
- return false;
- }
-
- // If the user manually entered a search term or URL, wrap the value in
- // a special URI until we can get a valid URL for this bookmark.
- final String text = mSearch.getText().toString().trim();
- if (!TextUtils.isEmpty(text)) {
- final String url = StringUtils.encodeUserEnteredUrl(text);
- mOnSiteSelectedListener.onSiteSelected(url, text);
- dismiss();
- }
-
- return true;
- }
- });
-
- mSearch.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus) {
- // On rotation, the view gets destroyed and we could be in a race to get the dialog
- // and window (see bug 1072959).
- Dialog dialog = getDialog();
- if (dialog == null) {
- return;
- }
- Window window = dialog.getWindow();
- if (window == null) {
- return;
- }
- window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
- }
- }
- });
-
- mList = (HomeListView) view.findViewById(R.id.list);
- mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- if (mOnSiteSelectedListener != null) {
- final Cursor c = mAdapter.getCursor();
- if (c == null || !c.moveToPosition(position)) {
- return;
- }
-
- final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
- final String title = c.getString(c.getColumnIndexOrThrow(URLColumns.TITLE));
- mOnSiteSelectedListener.onSiteSelected(url, title);
- }
-
- // Dismiss the fragment and the dialog.
- dismiss();
- }
- });
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- final LoaderManager manager = getLoaderManager();
-
- // Initialize the search adapter
- mAdapter = new SearchAdapter(getActivity());
- mList.setAdapter(mAdapter);
-
- // Create callbacks before the initial loader is started
- mLoaderCallbacks = new CursorLoaderCallbacks();
-
- // Reconnect to the loader only if present
- manager.initLoader(LOADER_ID_SEARCH, null, mLoaderCallbacks);
-
- // If there is a search term, put it in the text field
- if (!TextUtils.isEmpty(mSearchTerm)) {
- mSearch.setText(mSearchTerm);
- mSearch.selectAll();
- }
-
- // Always start with an empty filter
- filter("");
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
-
- // Discard any additional site selection as the dialog
- // is getting destroyed (see bug 935542).
- setOnSiteSelectedListener(null);
- }
-
- public void setSearchTerm(String searchTerm) {
- mSearchTerm = searchTerm;
- }
-
- private void filter(String searchTerm) {
- // Restart loaders with the new search term
- SearchLoader.restart(getLoaderManager(), LOADER_ID_SEARCH,
- mLoaderCallbacks, searchTerm,
- EnumSet.of(FilterFlags.EXCLUDE_PINNED_SITES));
- }
-
- public void setOnSiteSelectedListener(OnSiteSelectedListener listener) {
- mOnSiteSelectedListener = listener;
- }
-
- private static class SearchAdapter extends CursorAdapter {
- private final LayoutInflater mInflater;
-
- public SearchAdapter(Context context) {
- super(context, null, 0);
- mInflater = LayoutInflater.from(context);
- }
-
- @Override
- public void bindView(View view, Context context, Cursor cursor) {
- TwoLinePageRow row = (TwoLinePageRow) view;
- row.setShowIcons(false);
- row.updateFromCursor(cursor);
- }
-
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- return (TwoLinePageRow) mInflater.inflate(R.layout.home_item_row, parent, false);
- }
- }
-
- private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- return SearchLoader.createInstance(getActivity(), args);
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
- mAdapter.swapCursor(c);
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- mAdapter.swapCursor(null);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/RecentTabsAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/RecentTabsAdapter.java
deleted file mode 100755
index 3091f77da..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/RecentTabsAdapter.java
+++ /dev/null
@@ -1,454 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.SessionParser;
-import org.mozilla.gecko.home.CombinedHistoryAdapter.RecentTabsUpdateHandler;
-import org.mozilla.gecko.home.CombinedHistoryPanel.PanelStateUpdateHandler;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.NativeEventListener;
-import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.mozilla.gecko.home.CombinedHistoryItem.ItemType;
-import static org.mozilla.gecko.home.CombinedHistoryPanel.OnPanelLevelChangeListener.PanelLevel.CHILD_RECENT_TABS;
-
-public class RecentTabsAdapter extends RecyclerView.Adapter<CombinedHistoryItem>
- implements CombinedHistoryRecyclerView.AdapterContextMenuBuilder, NativeEventListener {
- private static final String LOGTAG = "GeckoRecentTabsAdapter";
-
- private static final int NAVIGATION_BACK_BUTTON_INDEX = 0;
-
- private static final String TELEMETRY_EXTRA_LAST_TIME = "recent_tabs_last_time";
- private static final String TELEMETRY_EXTRA_RECENTLY_CLOSED = "recent_closed_tabs";
- private static final String TELEMETRY_EXTRA_MIXED = "recent_tabs_mixed";
-
- // Recently closed tabs from Gecko.
- private ClosedTab[] recentlyClosedTabs;
- private boolean recentlyClosedTabsReceived = false;
-
- // "Tabs from last time".
- private ClosedTab[] lastSessionTabs;
-
- public static final class ClosedTab {
- public final String url;
- public final String title;
- public final String data;
-
- public ClosedTab(String url, String title, String data) {
- this.url = url;
- this.title = title;
- this.data = data;
- }
- }
-
- private final Context context;
- private final RecentTabsUpdateHandler recentTabsUpdateHandler;
- private final PanelStateUpdateHandler panelStateUpdateHandler;
-
- public RecentTabsAdapter(Context context,
- RecentTabsUpdateHandler recentTabsUpdateHandler,
- PanelStateUpdateHandler panelStateUpdateHandler) {
- this.context = context;
- this.recentTabsUpdateHandler = recentTabsUpdateHandler;
- this.panelStateUpdateHandler = panelStateUpdateHandler;
- recentlyClosedTabs = new ClosedTab[0];
- lastSessionTabs = new ClosedTab[0];
-
- readPreviousSessionData();
- }
-
- public void startListeningForClosedTabs() {
- EventDispatcher.getInstance().registerGeckoThreadListener(this, "ClosedTabs:Data");
- GeckoAppShell.notifyObservers("ClosedTabs:StartNotifications", null);
- }
-
- public void stopListeningForClosedTabs() {
- GeckoAppShell.notifyObservers("ClosedTabs:StopNotifications", null);
- EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "ClosedTabs:Data");
- recentlyClosedTabsReceived = false;
- }
-
- public void startListeningForHistorySanitize() {
- EventDispatcher.getInstance().registerGeckoThreadListener(this, "Sanitize:Finished");
- }
-
- public void stopListeningForHistorySanitize() {
- EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Sanitize:Finished");
- }
-
- @Override
- public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
- switch (event) {
- case "ClosedTabs:Data":
- updateRecentlyClosedTabs(message);
- break;
- case "Sanitize:Finished":
- clearLastSessionData();
- break;
- }
- }
-
- private void updateRecentlyClosedTabs(NativeJSObject message) {
- final NativeJSObject[] tabs = message.getObjectArray("tabs");
- final int length = tabs.length;
-
- final ClosedTab[] closedTabs = new ClosedTab[length];
- for (int i = 0; i < length; i++) {
- final NativeJSObject tab = tabs[i];
- closedTabs[i] = new ClosedTab(tab.getString("url"), tab.getString("title"), tab.getObject("data").toString());
- }
-
- // Only modify recentlyClosedTabs on the UI thread.
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- // Save some data about the old panel state, so we can be
- // smarter about notifying the recycler view which bits changed.
- int prevClosedTabsCount = recentlyClosedTabs.length;
- boolean prevSectionHeaderVisibility = isSectionHeaderVisible();
- int prevSectionHeaderIndex = getSectionHeaderIndex();
-
- recentlyClosedTabs = closedTabs;
- recentlyClosedTabsReceived = true;
- recentTabsUpdateHandler.onRecentTabsCountUpdated(
- getClosedTabsCount(), recentlyClosedTabsReceived);
- panelStateUpdateHandler.onPanelStateUpdated(CHILD_RECENT_TABS);
-
- // Handle the section header hiding/unhiding.
- updateHeaderVisibility(prevSectionHeaderVisibility, prevSectionHeaderIndex);
-
- // Update the "Recently closed" part of the tab list.
- updateTabsList(prevClosedTabsCount, recentlyClosedTabs.length, getFirstRecentTabIndex(), getLastRecentTabIndex());
- }
- });
- }
-
- private void readPreviousSessionData() {
- // If we happen to initialise before GeckoApp, waiting on either the main or the background
- // thread can lead to a deadlock, so we have to run on a separate thread instead.
- final Thread parseThread = new Thread(new Runnable() {
- @Override
- public void run() {
- // Make sure that the start up code has had a chance to update sessionstore.old as necessary.
- GeckoProfile.get(context).waitForOldSessionDataProcessing();
-
- final String jsonString = GeckoProfile.get(context).readPreviousSessionFile();
- if (jsonString == null) {
- // No previous session data.
- return;
- }
-
- final List<ClosedTab> parsedTabs = new ArrayList<>();
-
- new SessionParser() {
- @Override
- public void onTabRead(SessionTab tab) {
- final String url = tab.getUrl();
-
- // Don't show last tabs for about:home
- if (AboutPages.isAboutHome(url)) {
- return;
- }
-
- parsedTabs.add(new ClosedTab(url, tab.getTitle(), tab.getTabObject().toString()));
- }
- }.parse(jsonString);
-
- final ClosedTab[] closedTabs = parsedTabs.toArray(new ClosedTab[parsedTabs.size()]);
-
- // Only modify lastSessionTabs on the UI thread.
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- // Save some data about the old panel state, so we can be
- // smarter about notifying the recycler view which bits changed.
- int prevClosedTabsCount = lastSessionTabs.length;
- boolean prevSectionHeaderVisibility = isSectionHeaderVisible();
- int prevSectionHeaderIndex = getSectionHeaderIndex();
-
- lastSessionTabs = closedTabs;
- recentTabsUpdateHandler.onRecentTabsCountUpdated(
- getClosedTabsCount(), recentlyClosedTabsReceived);
- panelStateUpdateHandler.onPanelStateUpdated(CHILD_RECENT_TABS);
-
- // Handle the section header hiding/unhiding.
- updateHeaderVisibility(prevSectionHeaderVisibility, prevSectionHeaderIndex);
-
- // Update the "Tabs from last time" part of the tab list.
- updateTabsList(prevClosedTabsCount, lastSessionTabs.length, getFirstLastSessionTabIndex(), getLastLastSessionTabIndex());
- }
- });
- }
- }, "LastSessionTabsThread");
-
- parseThread.start();
- }
-
- private void clearLastSessionData() {
- final ClosedTab[] emptyLastSessionTabs = new ClosedTab[0];
-
- // Only modify mLastSessionTabs on the UI thread.
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- // Save some data about the old panel state, so we can be
- // smarter about notifying the recycler view which bits changed.
- int prevClosedTabsCount = lastSessionTabs.length;
- boolean prevSectionHeaderVisibility = isSectionHeaderVisible();
- int prevSectionHeaderIndex = getSectionHeaderIndex();
-
- lastSessionTabs = emptyLastSessionTabs;
- recentTabsUpdateHandler.onRecentTabsCountUpdated(
- getClosedTabsCount(), recentlyClosedTabsReceived);
- panelStateUpdateHandler.onPanelStateUpdated(CHILD_RECENT_TABS);
-
- // Handle the section header hiding.
- updateHeaderVisibility(prevSectionHeaderVisibility, prevSectionHeaderIndex);
-
- // Handle the "tabs from last time" being cleared.
- if (prevClosedTabsCount > 0) {
- notifyItemRangeRemoved(getFirstLastSessionTabIndex(), prevClosedTabsCount);
- }
- }
- });
- }
-
- private void updateHeaderVisibility(boolean prevSectionHeaderVisibility, int prevSectionHeaderIndex) {
- if (prevSectionHeaderVisibility && !isSectionHeaderVisible()) {
- notifyItemRemoved(prevSectionHeaderIndex);
- } else if (!prevSectionHeaderVisibility && isSectionHeaderVisible()) {
- notifyItemInserted(getSectionHeaderIndex());
- }
- }
-
- /**
- * Updates the tab list as necessary to account for any changes in tab count in a particular data source.
- *
- * Since the session store only sends out full updates, we don't know for sure what has changed compared
- * to the last data set, so we can only animate if the tab count actually changes.
- *
- * @param prevClosedTabsCount The previous number of closed tabs from that data source.
- * @param closedTabsCount The current number of closed tabs contained in that data source.
- * @param firstTabListIndex The current position of that data source's first item in the RecyclerView.
- * @param lastTabListIndex The current position of that data source's last item in the RecyclerView.
- */
- private void updateTabsList(int prevClosedTabsCount, int closedTabsCount, int firstTabListIndex, int lastTabListIndex) {
- final int closedTabsCountChange = closedTabsCount - prevClosedTabsCount;
-
- if (closedTabsCountChange <= 0) {
- notifyItemRangeRemoved(lastTabListIndex + 1, -closedTabsCountChange); // Remove tabs from the bottom of the list.
- notifyItemRangeChanged(firstTabListIndex, closedTabsCount); // Update the contents of the remaining items.
- } else { // closedTabsCountChange > 0
- notifyItemRangeInserted(firstTabListIndex, closedTabsCountChange); // Add additional tabs at the top of the list.
- notifyItemRangeChanged(firstTabListIndex + closedTabsCountChange, prevClosedTabsCount); // Update any previous list items.
- }
- }
-
- public String restoreTabFromPosition(int position) {
- final List<String> dataList = new ArrayList<>(1);
- dataList.add(getClosedTabForPosition(position).data);
-
- final String telemetryExtra =
- position > getLastRecentTabIndex() ? TELEMETRY_EXTRA_LAST_TIME : TELEMETRY_EXTRA_RECENTLY_CLOSED;
-
- restoreSessionWithHistory(dataList);
-
- return telemetryExtra;
- }
-
- public String restoreAllTabs() {
- if (recentlyClosedTabs.length == 0 && lastSessionTabs.length == 0) {
- return null;
- }
-
- final List<String> dataList = new ArrayList<>(getClosedTabsCount());
- addTabDataToList(dataList, recentlyClosedTabs);
- addTabDataToList(dataList, lastSessionTabs);
-
- final String telemetryExtra = recentlyClosedTabs.length > 0 && lastSessionTabs.length > 0 ? TELEMETRY_EXTRA_MIXED :
- recentlyClosedTabs.length > 0 ? TELEMETRY_EXTRA_RECENTLY_CLOSED : TELEMETRY_EXTRA_LAST_TIME;
-
- restoreSessionWithHistory(dataList);
-
- return telemetryExtra;
- }
-
- private void addTabDataToList(List<String> dataList, ClosedTab[] closedTabs) {
- for (ClosedTab closedTab : closedTabs) {
- dataList.add(closedTab.data);
- }
- }
-
- private static void restoreSessionWithHistory(List<String> dataList) {
- final JSONObject json = new JSONObject();
- try {
- json.put("tabs", new JSONArray(dataList));
- } catch (JSONException e) {
- Log.e(LOGTAG, "JSON error", e);
- }
-
- GeckoAppShell.notifyObservers("Session:RestoreRecentTabs", json.toString());
- }
-
- @Override
- public CombinedHistoryItem onCreateViewHolder(ViewGroup parent, int viewType) {
- final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
- final View view;
-
- final CombinedHistoryItem.ItemType itemType = CombinedHistoryItem.ItemType.viewTypeToItemType(viewType);
-
- switch (itemType) {
- case NAVIGATION_BACK:
- view = inflater.inflate(R.layout.home_combined_back_item, parent, false);
- return new CombinedHistoryItem.HistoryItem(view);
-
- case SECTION_HEADER:
- view = inflater.inflate(R.layout.home_header_row, parent, false);
- return new CombinedHistoryItem.BasicItem(view);
-
- case CLOSED_TAB:
- view = inflater.inflate(R.layout.home_item_row, parent, false);
- return new CombinedHistoryItem.HistoryItem(view);
- }
- return null;
- }
-
- @Override
- public void onBindViewHolder(CombinedHistoryItem holder, final int position) {
- final CombinedHistoryItem.ItemType itemType = getItemTypeForPosition(position);
-
- switch (itemType) {
- case SECTION_HEADER:
- ((TextView) holder.itemView).setText(context.getString(R.string.home_closed_tabs_title2));
- break;
-
- case CLOSED_TAB:
- final ClosedTab closedTab = getClosedTabForPosition(position);
- ((CombinedHistoryItem.HistoryItem) holder).bind(closedTab);
- break;
- }
- }
-
- @Override
- public int getItemCount() {
- int itemCount = 1; // NAVIGATION_BACK button is always visible.
-
- if (isSectionHeaderVisible()) {
- itemCount += 1;
- }
-
- itemCount += getClosedTabsCount();
-
- return itemCount;
- }
-
- private CombinedHistoryItem.ItemType getItemTypeForPosition(int position) {
- if (position == NAVIGATION_BACK_BUTTON_INDEX) {
- return ItemType.NAVIGATION_BACK;
- }
-
- if (position == getSectionHeaderIndex() && isSectionHeaderVisible()) {
- return ItemType.SECTION_HEADER;
- }
-
- return ItemType.CLOSED_TAB;
- }
-
- @Override
- public int getItemViewType(int position) {
- return CombinedHistoryItem.ItemType.itemTypeToViewType(getItemTypeForPosition(position));
- }
-
- public int getClosedTabsCount() {
- return recentlyClosedTabs.length + lastSessionTabs.length;
- }
-
- private boolean isSectionHeaderVisible() {
- return recentlyClosedTabs.length > 0 || lastSessionTabs.length > 0;
- }
-
- private int getSectionHeaderIndex() {
- return isSectionHeaderVisible() ?
- NAVIGATION_BACK_BUTTON_INDEX + 1 :
- NAVIGATION_BACK_BUTTON_INDEX;
- }
-
- private int getFirstRecentTabIndex() {
- return getSectionHeaderIndex() + 1;
- }
-
- private int getLastRecentTabIndex() {
- return getSectionHeaderIndex() + recentlyClosedTabs.length;
- }
-
- private int getFirstLastSessionTabIndex() {
- return getLastRecentTabIndex() + 1;
- }
-
- private int getLastLastSessionTabIndex() {
- return getLastRecentTabIndex() + lastSessionTabs.length;
- }
-
- /**
- * Get the closed tab corresponding to a RecyclerView list item.
- *
- * The Recent Tab folder combines two data sources, so if we want to get the ClosedTab object
- * behind a certain list item, we need to route this request to the corresponding data source
- * and also transform the global list position into a local array index.
- */
- private ClosedTab getClosedTabForPosition(int position) {
- final ClosedTab closedTab;
- if (position <= getLastRecentTabIndex()) { // Upper part of the list is "Recently closed tabs".
- closedTab = recentlyClosedTabs[position - getFirstRecentTabIndex()];
- } else { // Lower part is "Tabs from last time".
- closedTab = lastSessionTabs[position - getFirstLastSessionTabIndex()];
- }
-
- return closedTab;
- }
-
- @Override
- public HomeContextMenuInfo makeContextMenuInfoFromPosition(View view, int position) {
- final CombinedHistoryItem.ItemType itemType = getItemTypeForPosition(position);
- final HomeContextMenuInfo info;
-
- switch (itemType) {
- case CLOSED_TAB:
- info = new HomeContextMenuInfo(view, position, -1);
- ClosedTab closedTab = getClosedTabForPosition(position);
- return populateChildInfoFromTab(info, closedTab);
- }
-
- return null;
- }
-
- protected static HomeContextMenuInfo populateChildInfoFromTab(HomeContextMenuInfo info, ClosedTab tab) {
- info.url = tab.url;
- info.title = tab.title;
- return info;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsExpandableListState.java b/mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsExpandableListState.java
deleted file mode 100644
index 43497ae6c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsExpandableListState.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.HashSet;
-import java.util.Set;
-
-import org.mozilla.gecko.util.PrefUtils;
-
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-
-/**
- * Encapsulate visual state maintained by the Remote Tabs home panel.
- * <p>
- * This state should persist across database updates by Sync and the like. This
- * state could be stored in a separate "clients_metadata" table and served by
- * the Tabs provider, but that is heavy-weight for what we want to achieve. Such
- * a scheme would require either an expensive table join, or a tricky
- * co-ordination between multiple cursors. In contrast, this is easy and cheap
- * enough to do on the main thread.
- * <p>
- * This state is "per SharedPreferences" object. In practice, there should exist
- * one state object per Gecko Profile; since we can't change profiles without
- * killing our process, this can be a static singleton.
- */
-public class RemoteTabsExpandableListState {
- private static final String PREF_COLLAPSED_CLIENT_GUIDS = "remote_tabs_collapsed_client_guids";
- private static final String PREF_HIDDEN_CLIENT_GUIDS = "remote_tabs_hidden_client_guids";
- private static final String PREF_SELECTED_CLIENT_GUID = "remote_tabs_selected_client_guid";
-
- protected final SharedPreferences sharedPrefs;
-
- // Synchronized by the state instance. The default is to expand a clients
- // tabs, so "not present" means "expanded".
- // Only accessed from the UI thread.
- protected final Set<String> collapsedClients;
-
- // Synchronized by the state instance. The default is to show a client, so
- // "not present" means "shown".
- // Only accessed from the UI thread.
- protected final Set<String> hiddenClients;
-
- // Synchronized by the state instance. The last user selected client guid.
- // The selectedClient may be invalid or null.
- protected String selectedClient;
-
- public RemoteTabsExpandableListState(SharedPreferences sharedPrefs) {
- if (null == sharedPrefs) {
- throw new IllegalArgumentException("sharedPrefs must not be null");
- }
- this.sharedPrefs = sharedPrefs;
-
- this.collapsedClients = getStringSet(PREF_COLLAPSED_CLIENT_GUIDS);
- this.hiddenClients = getStringSet(PREF_HIDDEN_CLIENT_GUIDS);
- this.selectedClient = sharedPrefs.getString(PREF_SELECTED_CLIENT_GUID, null);
- }
-
- /**
- * Extract a string set from shared preferences.
- * <p>
- * Nota bene: it is not OK to modify the set returned by {@link SharedPreferences#getStringSet(String, Set)}.
- *
- * @param pref to read from.
- * @returns string set; never null.
- */
- protected Set<String> getStringSet(String pref) {
- final Set<String> loaded = PrefUtils.getStringSet(sharedPrefs, pref, null);
- if (loaded != null) {
- return new HashSet<String>(loaded);
- } else {
- return new HashSet<String>();
- }
- }
-
- /**
- * Update client membership in a set.
- *
- * @param pref
- * to write updated set to.
- * @param clients
- * set to update membership in.
- * @param clientGuid
- * to update membership of.
- * @param isMember
- * whether the client is a member of the set.
- * @return true if the set of clients was modified.
- */
- protected boolean updateClientMembership(String pref, Set<String> clients, String clientGuid, boolean isMember) {
- final boolean modified;
- if (isMember) {
- modified = clients.add(clientGuid);
- } else {
- modified = clients.remove(clientGuid);
- }
-
- if (modified) {
- // This starts an asynchronous write. We don't care if we drop the
- // write, and we don't really care if we race between writes, since
- // we will return results from our in-memory cache.
- final Editor editor = sharedPrefs.edit();
- PrefUtils.putStringSet(editor, pref, clients);
- editor.apply();
- }
-
- return modified;
- }
-
- /**
- * Mark a client as collapsed.
- *
- * @param clientGuid
- * to update.
- * @param collapsed
- * whether the client is collapsed.
- * @return true if the set of collapsed clients was modified.
- */
- protected synchronized boolean setClientCollapsed(String clientGuid, boolean collapsed) {
- return updateClientMembership(PREF_COLLAPSED_CLIENT_GUIDS, collapsedClients, clientGuid, collapsed);
- }
-
- /**
- * Mark a client as the selected.
- *
- * @param clientGuid
- * to update.
- */
- protected synchronized void setClientAsSelected(String clientGuid) {
- if (hiddenClients.contains(clientGuid)) {
- selectedClient = null;
- } else {
- selectedClient = clientGuid;
- }
-
- final Editor editor = sharedPrefs.edit();
- editor.putString(PREF_SELECTED_CLIENT_GUID, selectedClient);
- editor.apply();
- }
-
- public synchronized boolean isClientCollapsed(String clientGuid) {
- return collapsedClients.contains(clientGuid);
- }
-
- /**
- * Mark a client as hidden.
- *
- * @param clientGuid
- * to update.
- * @param hidden
- * whether the client is hidden.
- * @return true if the set of hidden clients was modified.
- */
- protected synchronized boolean setClientHidden(String clientGuid, boolean hidden) {
- return updateClientMembership(PREF_HIDDEN_CLIENT_GUIDS, hiddenClients, clientGuid, hidden);
- }
-
- public synchronized boolean isClientHidden(String clientGuid) {
- return hiddenClients.contains(clientGuid);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngine.java b/mobile/android/base/java/org/mozilla/gecko/home/SearchEngine.java
deleted file mode 100644
index 9b2d2746a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngine.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.support.annotation.NonNull;
-import org.mozilla.gecko.gfx.BitmapUtils;
-import org.mozilla.gecko.R;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class SearchEngine {
- public static final String LOG_TAG = "GeckoSearchEngine";
-
- public final String name; // Never null.
- public final String identifier; // Can be null.
-
- private final Bitmap icon;
- private volatile List<String> suggestions = new ArrayList<String>(); // Never null.
-
- public SearchEngine(final Context context, final JSONObject engineJSON) throws JSONException {
- if (engineJSON == null) {
- throw new IllegalArgumentException("Can't instantiate SearchEngine from null JSON.");
- }
-
- this.name = getString(engineJSON, "name");
- if (this.name == null) {
- throw new IllegalArgumentException("Cannot have an unnamed search engine.");
- }
-
- this.identifier = getString(engineJSON, "identifier");
-
- final String iconURI = getString(engineJSON, "iconURI");
- if (iconURI == null) {
- Log.w(LOG_TAG, "iconURI is null for search engine " + this.name);
- }
- final Bitmap tempIcon = BitmapUtils.getBitmapFromDataURI(iconURI);
-
- this.icon = (tempIcon != null) ? tempIcon : getDefaultFavicon(context);
- }
-
- private Bitmap getDefaultFavicon(final Context context) {
- return BitmapFactory.decodeResource(context.getResources(), R.drawable.search_icon_inactive);
- }
-
- private static String getString(JSONObject data, String key) throws JSONException {
- if (data.isNull(key)) {
- return null;
- }
- return data.getString(key);
- }
-
- /**
- * @return a non-null string suitable for use by FHR.
- */
- @NonNull
- public String getEngineIdentifier() {
- if (this.identifier != null) {
- return this.identifier;
- }
- if (this.name != null) {
- return "other-" + this.name;
- }
- return "other";
- }
-
- public boolean hasSuggestions() {
- return !this.suggestions.isEmpty();
- }
-
- public int getSuggestionsCount() {
- return this.suggestions.size();
- }
-
- public Iterable<String> getSuggestions() {
- return this.suggestions;
- }
-
- public void setSuggestions(List<String> suggestions) {
- if (suggestions == null) {
- this.suggestions = new ArrayList<String>();
- return;
- }
- this.suggestions = suggestions;
- }
-
- public Bitmap getIcon() {
- return this.icon;
- }
-}
-
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineAdapter.java
deleted file mode 100644
index be5b3b461..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineAdapter.java
+++ /dev/null
@@ -1,122 +0,0 @@
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.support.v4.content.ContextCompat;
-import android.support.v4.graphics.drawable.DrawableCompat;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-
-import org.mozilla.gecko.R;
-
-import java.util.Collections;
-import java.util.List;
-
-public class SearchEngineAdapter
- extends RecyclerView.Adapter<SearchEngineAdapter.SearchEngineViewHolder> {
-
- private static final String LOGTAG = SearchEngineAdapter.class.getSimpleName();
-
- private static final int VIEW_TYPE_SEARCH_ENGINE = 0;
- private static final int VIEW_TYPE_LABEL = 1;
- private final Context mContext;
-
- private int mContainerWidth;
- private List<SearchEngine> mSearchEngines = Collections.emptyList();
-
- public void setSearchEngines(List<SearchEngine> searchEngines) {
- mSearchEngines = searchEngines;
- notifyDataSetChanged();
- }
-
- /**
- * The container width is used for setting the appropriate calculated amount of width that
- * a search engine icon can have. This varies depending on the space available in the
- * {@link SearchEngineBar}. The setter exists for this attribute, in creating the view in the
- * adapter after said calculation is done when the search bar is created.
- * @param iconContainerWidth Width of each search icon.
- */
- void setIconContainerWidth(int iconContainerWidth) {
- mContainerWidth = iconContainerWidth;
- }
-
- public static class SearchEngineViewHolder extends RecyclerView.ViewHolder {
- final private ImageView faviconView;
-
- public void bindItem(SearchEngine searchEngine) {
- faviconView.setImageBitmap(searchEngine.getIcon());
- final String desc = itemView.getResources().getString(R.string.search_bar_item_desc,
- searchEngine.getEngineIdentifier());
- itemView.setContentDescription(desc);
- }
-
- public SearchEngineViewHolder(View itemView) {
- super(itemView);
- faviconView = (ImageView) itemView.findViewById(R.id.search_engine_icon);
- }
- }
-
- public SearchEngineAdapter(Context context) {
- mContext = context;
- }
-
- @Override
- public int getItemViewType(int position) {
- return position == 0 ? VIEW_TYPE_LABEL : VIEW_TYPE_SEARCH_ENGINE;
- }
-
- public SearchEngine getItem(int position) {
- // We omit the first position which is where the label currently is.
- return position == 0 ? null : mSearchEngines.get(position - 1);
- }
-
- @Override
- public SearchEngineViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- switch (viewType) {
- case VIEW_TYPE_LABEL:
- return new SearchEngineViewHolder(createLabelView(parent));
- case VIEW_TYPE_SEARCH_ENGINE:
- return new SearchEngineViewHolder(createSearchEngineView(parent));
- default:
- throw new IllegalArgumentException("Unknown view type: " + viewType);
- }
- }
-
- @Override
- public void onBindViewHolder(SearchEngineViewHolder holder, int position) {
- if (position != 0) {
- holder.bindItem(getItem(position));
- }
- }
-
- @Override
- public int getItemCount() {
- return mSearchEngines.size() + 1;
- }
-
- private View createLabelView(ViewGroup parent) {
- View view = LayoutInflater.from(mContext)
- .inflate(R.layout.search_engine_bar_label, parent, false);
- final Drawable icon = DrawableCompat.wrap(
- ContextCompat.getDrawable(mContext, R.drawable.search_icon_active).mutate());
- DrawableCompat.setTint(icon, ContextCompat.getColor(mContext, R.color.disabled_grey));
-
- final ImageView iconView = (ImageView) view.findViewById(R.id.search_engine_label);
- iconView.setImageDrawable(icon);
- return view;
- }
-
- private View createSearchEngineView(ViewGroup parent) {
- View view = LayoutInflater.from(mContext)
- .inflate(R.layout.search_engine_bar_item, parent, false);
-
- ViewGroup.LayoutParams params = view.getLayoutParams();
- params.width = mContainerWidth;
- view.setLayoutParams(params);
-
- return view;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineBar.java b/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineBar.java
deleted file mode 100644
index 6a6509bcb..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineBar.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.support.v4.content.ContextCompat;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
-import android.view.View;
-
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.widget.RecyclerViewClickSupport;
-
-import java.util.List;
-
-public class SearchEngineBar extends RecyclerView
- implements RecyclerViewClickSupport.OnItemClickListener {
- private static final String LOGTAG = SearchEngineBar.class.getSimpleName();
-
- private static final float ICON_CONTAINER_MIN_WIDTH_DP = 72;
- private static final float LABEL_CONTAINER_WIDTH_DP = 48;
-
- public interface OnSearchBarClickListener {
- void onSearchBarClickListener(SearchEngine searchEngine);
- }
-
- private final SearchEngineAdapter mAdapter;
- private final LinearLayoutManager mLayoutManager;
- private final Paint mDividerPaint;
- private final float mMinIconContainerWidth;
- private final float mDividerHeight;
- private final int mLabelContainerWidth;
-
- private int mIconContainerWidth;
- private OnSearchBarClickListener mOnSearchBarClickListener;
-
- public SearchEngineBar(final Context context, final AttributeSet attrs) {
- super(context, attrs);
-
- mDividerPaint = new Paint();
- mDividerPaint.setColor(ContextCompat.getColor(context, R.color.toolbar_divider_grey));
- mDividerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
-
- final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
- mMinIconContainerWidth = TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP, ICON_CONTAINER_MIN_WIDTH_DP, displayMetrics);
- mDividerHeight = context.getResources().getDimension(R.dimen.page_row_divider_height);
- mLabelContainerWidth = Math.round(TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP, LABEL_CONTAINER_WIDTH_DP, displayMetrics));
-
- mIconContainerWidth = Math.round(mMinIconContainerWidth);
-
- mAdapter = new SearchEngineAdapter(context);
- mAdapter.setIconContainerWidth(mIconContainerWidth);
- mLayoutManager = new LinearLayoutManager(context);
- mLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
-
- setAdapter(mAdapter);
- setLayoutManager(mLayoutManager);
-
- RecyclerViewClickSupport.addTo(this)
- .setOnItemClickListener(this);
- }
-
- public void setSearchEngines(List<SearchEngine> searchEngines) {
- mAdapter.setSearchEngines(searchEngines);
- }
-
- public void setOnSearchBarClickListener(OnSearchBarClickListener listener) {
- mOnSearchBarClickListener = listener;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- final int searchEngineCount = mAdapter.getItemCount() - 1;
-
- if (searchEngineCount > 0) {
- final int availableWidth = getMeasuredWidth() - mLabelContainerWidth;
-
- if (searchEngineCount * mMinIconContainerWidth <= availableWidth) {
- // All search engines fit int: So let's just display all.
- mIconContainerWidth = (int) mMinIconContainerWidth;
- } else {
- // If only (n) search engines fit into the available space then display only (x)
- // search engines with (x) picked so that the last search engine will be cut-off
- // (we only display half of it) to show the ability to scroll this view.
-
- final double searchEnginesToDisplay = Math.floor((availableWidth / mMinIconContainerWidth) - 0.5) + 0.5;
- // Use all available width and spread search engine icons
- mIconContainerWidth = (int) (availableWidth / searchEnginesToDisplay);
- }
-
- mAdapter.setIconContainerWidth(mIconContainerWidth);
- }
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- canvas.drawRect(0, 0, getWidth(), mDividerHeight, mDividerPaint);
- }
-
- @Override
- public void onItemClicked(RecyclerView recyclerView, int position, View v) {
- if (mOnSearchBarClickListener == null) {
- throw new IllegalStateException(
- OnSearchBarClickListener.class.getSimpleName() + " is not initializer."
- );
- }
-
- if (position == 0) {
- final Intent settingsIntent = new Intent(getContext(), GeckoPreferences.class);
- GeckoPreferences.setResourceToOpen(settingsIntent, "preferences_search");
- getContext().startActivity(settingsIntent);
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "searchenginebar-settings");
- return;
- }
-
- final SearchEngine searchEngine = mAdapter.getItem(position);
- mOnSearchBarClickListener.onSearchBarClickListener(searchEngine);
- }
-
- /**
- * We manually add the override for getAdapter because we see this method getting stripped
- * out during compile time by aggressive proguard rules.
- */
- @RobocopTarget
- @Override
- public SearchEngineAdapter getAdapter() {
- return mAdapter;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java b/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java
deleted file mode 100644
index 5b97a8f5f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java
+++ /dev/null
@@ -1,494 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.home.BrowserSearch.OnEditSuggestionListener;
-import org.mozilla.gecko.home.BrowserSearch.OnSearchListener;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.util.DrawableUtil;
-import org.mozilla.gecko.util.StringUtils;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.widget.AnimatedHeightLayout;
-import org.mozilla.gecko.widget.FaviconView;
-import org.mozilla.gecko.widget.FlowLayout;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.graphics.drawable.Drawable;
-import android.graphics.Typeface;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.text.style.StyleSpan;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.animation.AlphaAnimation;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.regex.Pattern;
-
-class SearchEngineRow extends AnimatedHeightLayout {
- // Duration for fade-in animation
- private static final int ANIMATION_DURATION = 250;
-
- // Inner views
- private final FlowLayout mSuggestionView;
- private final FaviconView mIconView;
- private final LinearLayout mUserEnteredView;
- private final TextView mUserEnteredTextView;
-
- // Inflater used when updating from suggestions
- private final LayoutInflater mInflater;
-
- // Search engine associated with this view
- private SearchEngine mSearchEngine;
-
- // Event listeners for suggestion views
- private final OnClickListener mClickListener;
- private final OnLongClickListener mLongClickListener;
-
- // On URL open listener
- private OnUrlOpenListener mUrlOpenListener;
-
- // On search listener
- private OnSearchListener mSearchListener;
-
- // On edit suggestion listener
- private OnEditSuggestionListener mEditSuggestionListener;
-
- // Selected suggestion view
- private int mSelectedView;
-
- // android:backgroundTint only works in Android 21 and higher so we can't do this statically in the xml
- private Drawable mSearchHistorySuggestionIcon;
-
- // Maximums for suggestions
- private int mMaxSavedSuggestions;
- private int mMaxSearchSuggestions;
-
- private final List<Integer> mOccurrences = new ArrayList<Integer>();
-
- public SearchEngineRow(Context context) {
- this(context, null);
- }
-
- public SearchEngineRow(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SearchEngineRow(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- mClickListener = new OnClickListener() {
- @Override
- public void onClick(View v) {
- final String suggestion = getSuggestionTextFromView(v);
-
- // If we're not clicking the user-entered view (the first suggestion item)
- // and the search matches a URL pattern, go to that URL. Otherwise, do a
- // search for the term.
- if (v != mUserEnteredView && !StringUtils.isSearchQuery(suggestion, true)) {
- if (mUrlOpenListener != null) {
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "url");
-
- mUrlOpenListener.onUrlOpen(suggestion, EnumSet.noneOf(OnUrlOpenListener.Flags.class));
- }
- } else if (mSearchListener != null) {
- if (v == mUserEnteredView) {
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "user");
- } else {
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, (String) v.getTag());
- }
- mSearchListener.onSearch(mSearchEngine, suggestion, TelemetryContract.Method.SUGGESTION);
- }
- }
- };
-
- mLongClickListener = new OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- if (mEditSuggestionListener != null) {
- final String suggestion = getSuggestionTextFromView(v);
- mEditSuggestionListener.onEditSuggestion(suggestion);
- return true;
- }
-
- return false;
- }
- };
-
- mInflater = LayoutInflater.from(context);
- mInflater.inflate(R.layout.search_engine_row, this);
-
- mSuggestionView = (FlowLayout) findViewById(R.id.suggestion_layout);
- mIconView = (FaviconView) findViewById(R.id.suggestion_icon);
-
- // User-entered search term is first suggestion
- mUserEnteredView = (LinearLayout) findViewById(R.id.suggestion_user_entered);
- mUserEnteredView.setOnClickListener(mClickListener);
-
- mUserEnteredTextView = (TextView) findViewById(R.id.suggestion_text);
- mSearchHistorySuggestionIcon = DrawableUtil.tintDrawableWithColorRes(getContext(), R.drawable.icon_most_recent_empty, R.color.tabs_tray_icon_grey);
-
- // Suggestion limits
- mMaxSavedSuggestions = getResources().getInteger(R.integer.max_saved_suggestions);
- mMaxSearchSuggestions = getResources().getInteger(R.integer.max_search_suggestions);
- }
-
- private void setDescriptionOnSuggestion(View v, String suggestion) {
- v.setContentDescription(getResources().getString(R.string.suggestion_for_engine,
- mSearchEngine.name, suggestion));
- }
-
- private String getSuggestionTextFromView(View v) {
- final TextView suggestionText = (TextView) v.findViewById(R.id.suggestion_text);
- return suggestionText.getText().toString();
- }
-
- /**
- * Finds all occurrences of pattern in string and returns a list of the starting indices
- * of each occurrence.
- *
- * @param pattern The pattern that is searched for
- * @param string The string where we search for the pattern
- */
- private void refreshOccurrencesWith(String pattern, String string) {
- mOccurrences.clear();
-
- // Don't try to search for an empty string - String.indexOf will return 0, which would result
- // in us iterating with lastIndexOfMatch = 0, which eventually results in an OOM.
- if (TextUtils.isEmpty(pattern)) {
- return;
- }
-
- final int patternLength = pattern.length();
-
- int indexOfMatch = 0;
- int lastIndexOfMatch = 0;
- while (indexOfMatch != -1) {
- indexOfMatch = string.indexOf(pattern, lastIndexOfMatch);
- lastIndexOfMatch = indexOfMatch + patternLength;
- if (indexOfMatch != -1) {
- mOccurrences.add(indexOfMatch);
- }
- }
- }
-
- /**
- * Sets the content for the suggestion view.
- *
- * If the suggestion doesn't contain mUserSearchTerm, nothing is made bold.
- * All instances of mUserSearchTerm in the suggestion are not bold.
- *
- * @param v The View that needs to be populated
- * @param suggestion The suggestion text that will be placed in the view
- * @param isUserSavedSearch whether the suggestion is from history or not
- */
- private void setSuggestionOnView(View v, String suggestion, boolean isUserSavedSearch) {
- final ImageView historyIcon = (ImageView) v.findViewById(R.id.suggestion_item_icon);
- if (isUserSavedSearch) {
- historyIcon.setImageDrawable(mSearchHistorySuggestionIcon);
- historyIcon.setVisibility(View.VISIBLE);
- } else {
- historyIcon.setVisibility(View.GONE);
- }
-
- final TextView suggestionText = (TextView) v.findViewById(R.id.suggestion_text);
- final String searchTerm = getSuggestionTextFromView(mUserEnteredView);
- final int searchTermLength = searchTerm.length();
- refreshOccurrencesWith(searchTerm, suggestion);
- if (mOccurrences.size() > 0) {
- final SpannableStringBuilder sb = new SpannableStringBuilder(suggestion);
- int nextStartSpanIndex = 0;
- // Done to make sure that the stretch of text after the last occurrence, till the end of the suggestion, is made bold
- mOccurrences.add(suggestion.length());
- for (int occurrence : mOccurrences) {
- // Even though they're the same style, SpannableStringBuilder will interpret there as being only one Span present if we re-use a StyleSpan
- StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
- sb.setSpan(boldSpan, nextStartSpanIndex, occurrence, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
- nextStartSpanIndex = occurrence + searchTermLength;
- }
- mOccurrences.clear();
- suggestionText.setText(sb);
- } else {
- suggestionText.setText(suggestion);
- }
-
- setDescriptionOnSuggestion(suggestionText, suggestion);
- }
-
- /**
- * Perform a search for the user-entered term.
- */
- public void performUserEnteredSearch() {
- String searchTerm = getSuggestionTextFromView(mUserEnteredView);
- if (mSearchListener != null) {
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "user");
- mSearchListener.onSearch(mSearchEngine, searchTerm, TelemetryContract.Method.SUGGESTION);
- }
- }
-
- public void setSearchTerm(String searchTerm) {
- mUserEnteredTextView.setText(searchTerm);
-
- // mSearchEngine is not set in the first call to this method; the content description
- // is instead initially set in updateSuggestions().
- if (mSearchEngine != null) {
- setDescriptionOnSuggestion(mUserEnteredTextView, searchTerm);
- }
- }
-
- public void setOnUrlOpenListener(OnUrlOpenListener listener) {
- mUrlOpenListener = listener;
- }
-
- public void setOnSearchListener(OnSearchListener listener) {
- mSearchListener = listener;
- }
-
- public void setOnEditSuggestionListener(OnEditSuggestionListener listener) {
- mEditSuggestionListener = listener;
- }
-
- private void bindSuggestionView(String suggestion, boolean animate, int recycledSuggestionCount, Integer previousSuggestionChildIndex, boolean isUserSavedSearch, String telemetryTag) {
- final View suggestionItem;
-
- // Reuse suggestion views from recycled view, if possible.
- if (previousSuggestionChildIndex + 1 < recycledSuggestionCount) {
- suggestionItem = mSuggestionView.getChildAt(previousSuggestionChildIndex + 1);
- suggestionItem.setVisibility(View.VISIBLE);
- } else {
- suggestionItem = mInflater.inflate(R.layout.suggestion_item, null);
-
- suggestionItem.setOnClickListener(mClickListener);
- suggestionItem.setOnLongClickListener(mLongClickListener);
-
- suggestionItem.setTag(telemetryTag);
-
- mSuggestionView.addView(suggestionItem);
- }
-
- setSuggestionOnView(suggestionItem, suggestion, isUserSavedSearch);
-
- if (animate) {
- AlphaAnimation anim = new AlphaAnimation(0, 1);
- anim.setDuration(ANIMATION_DURATION);
- anim.setStartOffset(previousSuggestionChildIndex * ANIMATION_DURATION);
- suggestionItem.startAnimation(anim);
- }
- }
-
- private void hideRecycledSuggestions(int lastVisibleChildIndex, int recycledSuggestionCount) {
- // Hide extra suggestions that have been recycled.
- for (int i = lastVisibleChildIndex + 1; i < recycledSuggestionCount; ++i) {
- mSuggestionView.getChildAt(i).setVisibility(View.GONE);
- }
- }
-
- /**
- * Displays search suggestions from previous searches.
- *
- * @param savedSuggestions The List to iterate over for saved search suggestions to display. This function does not
- * enforce a ui maximum or filter. It will show all the suggestions in this list.
- * @param suggestionStartIndex global index of where to start adding suggestion "buttons" in the search engine row. Also
- * acts as a counter for total number of suggestions visible.
- * @param animate whether or not to animate suggestions for visual polish
- * @param recycledSuggestionCount How many suggestion "button" views we could recycle from previous calls
- */
- private void updateFromSavedSearches(List<String> savedSuggestions, boolean animate, int suggestionStartIndex, int recycledSuggestionCount) {
- if (savedSuggestions == null || savedSuggestions.isEmpty()) {
- hideRecycledSuggestions(suggestionStartIndex, recycledSuggestionCount);
- return;
- }
-
- final int numSavedSearches = savedSuggestions.size();
- int indexOfPreviousSuggestion = 0;
- for (int i = 0; i < numSavedSearches; i++) {
- String telemetryTag = "history." + i;
- final String suggestion = savedSuggestions.get(i);
- indexOfPreviousSuggestion = suggestionStartIndex + i;
- bindSuggestionView(suggestion, animate, recycledSuggestionCount, indexOfPreviousSuggestion, true, telemetryTag);
- }
-
- hideRecycledSuggestions(indexOfPreviousSuggestion + 1, recycledSuggestionCount);
- }
-
- /**
- * Displays suggestions supplied by the search engine, relative to number of suggestions from search history.
- *
- * @param animate whether or not to animate suggestions for visual polish
- * @param recycledSuggestionCount How many suggestion "button" views we could recycle from previous calls
- * @param savedSuggestionCount how many saved searches this searchTerm has
- * @return the global count of how many suggestions have been bound/shown in the search engine row
- */
- private int updateFromSearchEngine(boolean animate, List<String> searchEngineSuggestions, int recycledSuggestionCount, int savedSuggestionCount) {
- int maxSuggestions = mMaxSearchSuggestions;
- // If there are less than max saved searches on phones, fill the space with more search engine suggestions
- if (!HardwareUtils.isTablet() && savedSuggestionCount < mMaxSavedSuggestions) {
- maxSuggestions += mMaxSavedSuggestions - savedSuggestionCount;
- }
-
- final int numSearchEngineSuggestions = searchEngineSuggestions.size();
- int relativeIndex;
- for (relativeIndex = 0; relativeIndex < numSearchEngineSuggestions; relativeIndex++) {
- if (relativeIndex == maxSuggestions) {
- break;
- }
-
- // Since the search engine suggestions are listed first, their relative index is their global index
- String telemetryTag = "engine." + relativeIndex;
- final String suggestion = searchEngineSuggestions.get(relativeIndex);
- bindSuggestionView(suggestion, animate, recycledSuggestionCount, relativeIndex, false, telemetryTag);
- }
-
- hideRecycledSuggestions(relativeIndex + 1, recycledSuggestionCount);
-
- // Make sure mSelectedView is still valid.
- if (mSelectedView >= mSuggestionView.getChildCount()) {
- mSelectedView = mSuggestionView.getChildCount() - 1;
- }
-
- return relativeIndex;
- }
-
- /**
- * Updates the whole suggestions UI, the search engine UI, suggestions from the default search engine,
- * and suggestions from search history.
- *
- * This can be called before the opt-in permission prompt is shown or set.
- * Even if both suggestion types are disabled, we need to update the search engine, its image, and the content description.
- *
- * @param searchSuggestionsEnabled whether or not suggestions from the default search engine are enabled
- * @param searchEngine the search engine to use throughout the SearchEngineRow class
- * @param rawSearchHistorySuggestions search history suggestions
- * @param animate whether or not to use animations
- **/
- public void updateSuggestions(boolean searchSuggestionsEnabled, SearchEngine searchEngine, @Nullable List<String> rawSearchHistorySuggestions, boolean animate) {
- mSearchEngine = searchEngine;
- // Set the search engine icon (e.g., Google) for the row.
-
- mIconView.updateAndScaleImage(IconResponse.create(mSearchEngine.getIcon()));
- // Set the initial content description.
- setDescriptionOnSuggestion(mUserEnteredTextView, mUserEnteredTextView.getText().toString());
-
- final int recycledSuggestionCount = mSuggestionView.getChildCount();
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(getContext());
- final boolean savedSearchesEnabled = prefs.getBoolean(GeckoPreferences.PREFS_HISTORY_SAVED_SEARCH, true);
-
- // Remove duplicates of search engine suggestions from saved searches.
- List<String> searchHistorySuggestions = (rawSearchHistorySuggestions != null) ? rawSearchHistorySuggestions : new ArrayList<String>();
-
- // Filter out URLs and long search suggestions
- Iterator<String> searchHistoryIterator = searchHistorySuggestions.iterator();
- while (searchHistoryIterator.hasNext()) {
- final String currentSearchHistory = searchHistoryIterator.next();
-
- if (currentSearchHistory.length() > 50 || Pattern.matches("^(https?|ftp|file)://.*", currentSearchHistory)) {
- searchHistoryIterator.remove();
- }
- }
-
-
- List<String> searchEngineSuggestions = new ArrayList<String>();
- for (String suggestion : searchEngine.getSuggestions()) {
- searchHistorySuggestions.remove(suggestion);
- searchEngineSuggestions.add(suggestion);
- }
- // Make sure the search term itself isn't duplicated. This is more important on phones than tablets where screen
- // space is more precious.
- searchHistorySuggestions.remove(getSuggestionTextFromView(mUserEnteredView));
-
- // Trim the history suggestions down to the maximum allowed.
- if (searchHistorySuggestions.size() >= mMaxSavedSuggestions) {
- // The second index to subList() is exclusive, so this looks like an off by one error but it is not.
- searchHistorySuggestions = searchHistorySuggestions.subList(0, mMaxSavedSuggestions);
- }
- final int searchHistoryCount = searchHistorySuggestions.size();
-
- if (searchSuggestionsEnabled && savedSearchesEnabled) {
- final int suggestionViewCount = updateFromSearchEngine(animate, searchEngineSuggestions, recycledSuggestionCount, searchHistoryCount);
- updateFromSavedSearches(searchHistorySuggestions, animate, suggestionViewCount, recycledSuggestionCount);
- } else if (savedSearchesEnabled) {
- updateFromSavedSearches(searchHistorySuggestions, animate, 0, recycledSuggestionCount);
- } else if (searchSuggestionsEnabled) {
- updateFromSearchEngine(animate, searchEngineSuggestions, recycledSuggestionCount, 0);
- } else {
- // The current search term is treated separately from the suggestions list, hence we can
- // recycle ALL suggestion items here. (We always show the current search term, i.e. 1 item,
- // in front of the search engine suggestions and/or the search history.)
- hideRecycledSuggestions(0, recycledSuggestionCount);
- }
- }
-
- @Override
- public boolean onKeyDown(int keyCode, android.view.KeyEvent event) {
- final View suggestion = mSuggestionView.getChildAt(mSelectedView);
-
- if (event.getAction() != android.view.KeyEvent.ACTION_DOWN) {
- return false;
- }
-
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- final View nextSuggestion = mSuggestionView.getChildAt(mSelectedView + 1);
- if (nextSuggestion != null) {
- changeSelectedSuggestion(suggestion, nextSuggestion);
- mSelectedView++;
- return true;
- }
- break;
-
- case KeyEvent.KEYCODE_DPAD_LEFT:
- final View prevSuggestion = mSuggestionView.getChildAt(mSelectedView - 1);
- if (prevSuggestion != null) {
- changeSelectedSuggestion(suggestion, prevSuggestion);
- mSelectedView--;
- return true;
- }
- break;
-
- case KeyEvent.KEYCODE_BUTTON_A:
- // TODO: handle long pressing for editing suggestions
- return suggestion.performClick();
- }
-
- return false;
- }
-
- private void changeSelectedSuggestion(View oldSuggestion, View newSuggestion) {
- oldSuggestion.setDuplicateParentStateEnabled(false);
- newSuggestion.setDuplicateParentStateEnabled(true);
- oldSuggestion.refreshDrawableState();
- newSuggestion.refreshDrawableState();
- }
-
- public void onSelected() {
- mSelectedView = 0;
- mUserEnteredView.setDuplicateParentStateEnabled(true);
- mUserEnteredView.refreshDrawableState();
- }
-
- public void onDeselected() {
- final View suggestion = mSuggestionView.getChildAt(mSelectedView);
- suggestion.setDuplicateParentStateEnabled(false);
- suggestion.refreshDrawableState();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/SearchLoader.java b/mobile/android/base/java/org/mozilla/gecko/home/SearchLoader.java
deleted file mode 100644
index f7b5b6586..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/SearchLoader.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.EnumSet;
-
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.BrowserDB.FilterFlags;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.app.LoaderManager.LoaderCallbacks;
-import android.support.v4.content.Loader;
-
-/**
- * Encapsulates the implementation of the search cursor loader.
- */
-class SearchLoader {
- public static final String LOGTAG = "GeckoSearchLoader";
-
- private static final String KEY_SEARCH_TERM = "search_term";
- private static final String KEY_FILTER_FLAGS = "flags";
-
- private SearchLoader() {
- }
-
- @SuppressWarnings("unchecked")
- public static Loader<Cursor> createInstance(Context context, Bundle args) {
- if (args != null) {
- final String searchTerm = args.getString(KEY_SEARCH_TERM);
- final EnumSet<FilterFlags> flags =
- (EnumSet<FilterFlags>) args.getSerializable(KEY_FILTER_FLAGS);
- return new SearchCursorLoader(context, searchTerm, flags);
- } else {
- return new SearchCursorLoader(context, "", EnumSet.noneOf(FilterFlags.class));
- }
- }
-
- private static Bundle createArgs(String searchTerm, EnumSet<FilterFlags> flags) {
- Bundle args = new Bundle();
- args.putString(SearchLoader.KEY_SEARCH_TERM, searchTerm);
- args.putSerializable(SearchLoader.KEY_FILTER_FLAGS, flags);
-
- return args;
- }
-
- public static void init(LoaderManager manager, int loaderId,
- LoaderCallbacks<Cursor> callbacks, String searchTerm) {
- init(manager, loaderId, callbacks, searchTerm, EnumSet.noneOf(FilterFlags.class));
- }
-
- public static void init(LoaderManager manager, int loaderId,
- LoaderCallbacks<Cursor> callbacks, String searchTerm,
- EnumSet<FilterFlags> flags) {
- final Bundle args = createArgs(searchTerm, flags);
- manager.initLoader(loaderId, args, callbacks);
- }
-
- public static void restart(LoaderManager manager, int loaderId,
- LoaderCallbacks<Cursor> callbacks, String searchTerm) {
- restart(manager, loaderId, callbacks, searchTerm, EnumSet.noneOf(FilterFlags.class));
- }
-
- public static void restart(LoaderManager manager, int loaderId,
- LoaderCallbacks<Cursor> callbacks, String searchTerm,
- EnumSet<FilterFlags> flags) {
- final Bundle args = createArgs(searchTerm, flags);
- manager.restartLoader(loaderId, args, callbacks);
- }
-
- public static class SearchCursorLoader extends SimpleCursorLoader {
- private static final String TELEMETRY_HISTOGRAM_LOAD_CURSOR = "FENNEC_SEARCH_LOADER_TIME_MS";
-
- // Max number of search results.
- private static final int SEARCH_LIMIT = 100;
-
- // The target search term associated with the loader.
- private final String mSearchTerm;
-
- // The filter flags associated with the loader.
- private final EnumSet<FilterFlags> mFlags;
- private final GeckoProfile mProfile;
-
- public SearchCursorLoader(Context context, String searchTerm, EnumSet<FilterFlags> flags) {
- super(context);
- mSearchTerm = searchTerm;
- mFlags = flags;
- mProfile = GeckoProfile.get(context);
- }
-
- @Override
- public Cursor loadCursor() {
- final long start = SystemClock.uptimeMillis();
- final Cursor cursor = BrowserDB.from(mProfile).filter(getContext().getContentResolver(), mSearchTerm, SEARCH_LIMIT, mFlags);
- final long end = SystemClock.uptimeMillis();
- final long took = end - start;
- Telemetry.addToHistogram(TELEMETRY_HISTOGRAM_LOAD_CURSOR, (int) Math.min(took, Integer.MAX_VALUE));
- return cursor;
- }
-
- public String getSearchTerm() {
- return mSearchTerm;
- }
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/SimpleCursorLoader.java b/mobile/android/base/java/org/mozilla/gecko/home/SimpleCursorLoader.java
deleted file mode 100644
index b8889c033..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/SimpleCursorLoader.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * This is an adapted version of Android's original CursorLoader
- * without all the ContentProvider-specific bits.
- *
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v4.content.AsyncTaskLoader;
-
-import org.mozilla.gecko.GeckoApplication;
-
-/**
- * A copy of the framework's {@link android.content.CursorLoader} that
- * instead allows the caller to load the Cursor themselves via the abstract
- * {@link #loadCursor()} method, rather than calling out to a ContentProvider via
- * class methods.
- *
- * For new code, prefer {@link android.content.CursorLoader} (see @deprecated).
- *
- * This was originally created to re-use existing code which loaded Cursors manually.
- *
- * @deprecated since the framework provides an implementation, we'd like to eventually remove
- * this class to reduce maintenance burden. Originally planned for bug 1239491, but
- * it'd be more efficient to do this over time, rather than all at once.
- */
-@Deprecated
-public abstract class SimpleCursorLoader extends AsyncTaskLoader<Cursor> {
- final ForceLoadContentObserver mObserver;
- Cursor mCursor;
-
- public SimpleCursorLoader(Context context) {
- super(context);
- mObserver = new ForceLoadContentObserver();
- }
-
- /**
- * Loads the target cursor for this loader. This method is called
- * on a worker thread.
- */
- protected abstract Cursor loadCursor();
-
- /* Runs on a worker thread */
- @Override
- public Cursor loadInBackground() {
- Cursor cursor = loadCursor();
-
- if (cursor != null) {
- // Ensure the cursor window is filled
- cursor.getCount();
- cursor.registerContentObserver(mObserver);
- }
-
- return cursor;
- }
-
- /* Runs on the UI thread */
- @Override
- public void deliverResult(Cursor cursor) {
- if (isReset()) {
- // An async query came in while the loader is stopped
- if (cursor != null) {
- cursor.close();
- }
-
- return;
- }
-
- Cursor oldCursor = mCursor;
- mCursor = cursor;
-
- if (isStarted()) {
- super.deliverResult(cursor);
- }
-
- if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
- oldCursor.close();
-
- // Trying to read from the closed cursor will cause crashes, hence we should make
- // sure that no adapters/LoaderCallbacks are holding onto this cursor.
- GeckoApplication.getRefWatcher(getContext()).watch(oldCursor);
- }
- }
-
- /**
- * Starts an asynchronous load of the list data. When the result is ready the callbacks
- * will be called on the UI thread. If a previous load has been completed and is still valid
- * the result may be passed to the callbacks immediately.
- *
- * Must be called from the UI thread
- */
- @Override
- protected void onStartLoading() {
- if (mCursor != null) {
- deliverResult(mCursor);
- }
-
- if (takeContentChanged() || mCursor == null) {
- forceLoad();
- }
- }
-
- /**
- * Must be called from the UI thread
- */
- @Override
- protected void onStopLoading() {
- // Attempt to cancel the current load task if possible.
- cancelLoad();
- }
-
- @Override
- public void onCanceled(Cursor cursor) {
- if (cursor != null && !cursor.isClosed()) {
- cursor.close();
- }
- }
-
- @Override
- protected void onReset() {
- super.onReset();
-
- // Ensure the loader is stopped
- onStopLoading();
-
- if (mCursor != null && !mCursor.isClosed()) {
- mCursor.close();
- }
-
- mCursor = null;
- }
-} \ No newline at end of file
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/SpacingDecoration.java b/mobile/android/base/java/org/mozilla/gecko/home/SpacingDecoration.java
deleted file mode 100644
index 039b65e82..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/SpacingDecoration.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.mozilla.gecko.home;
-
-import android.graphics.Rect;
-import android.support.v7.widget.RecyclerView;
-import android.view.View;
-
-public class SpacingDecoration extends RecyclerView.ItemDecoration {
- private final int horizontalSpacing;
- private final int verticalSpacing;
-
- public SpacingDecoration(int horizontalSpacing, int verticalSpacing) {
- this.horizontalSpacing = horizontalSpacing;
- this.verticalSpacing = verticalSpacing;
- }
-
- @Override
- public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
- outRect.set(horizontalSpacing, verticalSpacing, horizontalSpacing, verticalSpacing);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/TabMenuStrip.java b/mobile/android/base/java/org/mozilla/gecko/home/TabMenuStrip.java
deleted file mode 100644
index b302d3522..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/TabMenuStrip.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.HorizontalScrollView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-/**
- * {@code TabMenuStrip} is the view used to display {@code HomePager} tabs
- * on tablets. See {@code TabMenuStripLayout} for details about how the
- * tabs are created and updated.
- */
-public class TabMenuStrip extends HorizontalScrollView
- implements HomePager.Decor {
-
- // Offset between the selected tab title and the edge of the screen,
- // except for the first and last tab in the tab strip.
- private static final int TITLE_OFFSET_DIPS = 24;
-
- private final int titleOffset;
- private final TabMenuStripLayout layout;
-
- private final Paint shadowPaint;
- private final int shadowSize;
-
- public interface OnTitleClickListener {
- void onTitleClicked(int index);
- }
-
- public TabMenuStrip(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- // Disable the scroll bar.
- setHorizontalScrollBarEnabled(false);
- setFillViewport(true);
-
- final Resources res = getResources();
-
- titleOffset = (int) (TITLE_OFFSET_DIPS * res.getDisplayMetrics().density);
-
- layout = new TabMenuStripLayout(context, attrs);
- addView(layout, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
-
- shadowSize = res.getDimensionPixelSize(R.dimen.tabs_strip_shadow_size);
-
- shadowPaint = new Paint();
- shadowPaint.setColor(ContextCompat.getColor(context, R.color.url_bar_shadow));
- shadowPaint.setStrokeWidth(0.0f);
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
-
- final int height = getHeight();
- canvas.drawRect(0, height - shadowSize, layout.getWidth(), height, shadowPaint);
- }
-
- @Override
- public void onAddPagerView(String title) {
- layout.onAddPagerView(title);
- }
-
- @Override
- public void removeAllPagerViews() {
- layout.removeAllViews();
- }
-
- @Override
- public void onPageSelected(final int position) {
- layout.onPageSelected(position);
- }
-
- @Override
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- layout.onPageScrolled(position, positionOffset, positionOffsetPixels);
-
- final View selectedTitle = layout.getChildAt(position);
- if (selectedTitle == null) {
- return;
- }
-
- final int selectedTitleOffset = (int) (positionOffset * selectedTitle.getWidth());
-
- int titleLeft = selectedTitle.getLeft() + selectedTitleOffset;
- if (position > 0) {
- titleLeft -= titleOffset;
- }
-
- int titleRight = selectedTitle.getRight() + selectedTitleOffset;
- if (position < layout.getChildCount() - 1) {
- titleRight += titleOffset;
- }
-
- final int scrollX = getScrollX();
- if (titleLeft < scrollX) {
- // Tab strip overflows to the left.
- scrollTo(titleLeft, 0);
- } else if (titleRight > scrollX + getWidth()) {
- // Tab strip overflows to the right.
- scrollTo(titleRight - getWidth(), 0);
- }
- }
-
- @Override
- public void setOnTitleClickListener(OnTitleClickListener onTitleClickListener) {
- layout.setOnTitleClickListener(onTitleClickListener);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/TabMenuStripLayout.java b/mobile/android/base/java/org/mozilla/gecko/home/TabMenuStripLayout.java
deleted file mode 100644
index a09add80b..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/TabMenuStripLayout.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-
-import android.content.res.ColorStateList;
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.TextView;
-
-/**
- * {@code TabMenuStripLayout} is the view that draws the {@code HomePager}
- * tabs that are displayed in {@code TabMenuStrip}.
- */
-class TabMenuStripLayout extends LinearLayout
- implements View.OnFocusChangeListener {
-
- private TabMenuStrip.OnTitleClickListener onTitleClickListener;
- private Drawable strip;
- private TextView selectedView;
-
- // Data associated with the scrolling of the strip drawable.
- private View toTab;
- private View fromTab;
- private int fromPosition;
- private int toPosition;
- private float progress;
-
- // This variable is used to predict the direction of scroll.
- private float prevProgress;
- private int tabContentStart;
- private boolean titlebarFill;
- private int activeTextColor;
- private ColorStateList inactiveTextColor;
-
- TabMenuStripLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabMenuStrip);
- final int stripResId = a.getResourceId(R.styleable.TabMenuStrip_strip, -1);
-
- titlebarFill = a.getBoolean(R.styleable.TabMenuStrip_titlebarFill, false);
- tabContentStart = a.getDimensionPixelSize(R.styleable.TabMenuStrip_tabsMarginLeft, 0);
- activeTextColor = a.getColor(R.styleable.TabMenuStrip_activeTextColor, R.color.text_and_tabs_tray_grey);
- inactiveTextColor = a.getColorStateList(R.styleable.TabMenuStrip_inactiveTextColor);
- a.recycle();
-
- if (stripResId != -1) {
- strip = getResources().getDrawable(stripResId);
- }
-
- setWillNotDraw(false);
- }
-
- void onAddPagerView(String title) {
- final TextView button = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.tab_menu_strip, this, false);
- button.setText(title.toUpperCase());
- button.setTextColor(inactiveTextColor);
-
- // Set titles width to weight, or wrap text width.
- if (titlebarFill) {
- button.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1.0f));
- } else {
- button.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT));
- }
-
- if (getChildCount() == 0) {
- button.setPadding(button.getPaddingLeft() + tabContentStart,
- button.getPaddingTop(),
- button.getPaddingRight(),
- button.getPaddingBottom());
- }
-
- addView(button);
- button.setOnClickListener(new ViewClickListener(getChildCount() - 1));
- button.setOnFocusChangeListener(this);
- }
-
- void onPageSelected(final int position) {
- if (selectedView != null) {
- selectedView.setTextColor(inactiveTextColor);
- }
-
- selectedView = (TextView) getChildAt(position);
- selectedView.setTextColor(activeTextColor);
-
- // Callback to measure and draw the strip after the view is visible.
- ViewTreeObserver vto = selectedView.getViewTreeObserver();
- if (vto.isAlive()) {
- vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- selectedView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
-
- if (strip != null) {
- strip.setBounds(selectedView.getLeft() + (position == 0 ? tabContentStart : 0),
- selectedView.getTop(),
- selectedView.getRight(),
- selectedView.getBottom());
- }
-
- prevProgress = position;
- }
- });
- }
- }
-
- // Page scroll animates the drawable and its bounds from the previous to next child view.
- void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- if (strip == null) {
- return;
- }
-
- setScrollingData(position, positionOffset);
-
- if (fromTab == null || toTab == null) {
- return;
- }
-
- final int fromTabLeft = fromTab.getLeft();
- final int fromTabRight = fromTab.getRight();
-
- final int toTabLeft = toTab.getLeft();
- final int toTabRight = toTab.getRight();
-
- // The first tab has a padding applied (tabContentStart). We don't want the 'strip' to jump around so we remove
- // this padding slowly (modifier) when scrolling to or from the first tab.
- final int modifier;
-
- if (fromPosition == 0 && toPosition == 1) {
- // Slowly remove extra padding (tabContentStart) based on scroll progress
- modifier = (int) (tabContentStart * (1 - progress));
- } else if (fromPosition == 1 && toPosition == 0) {
- // Slowly add extra padding (tabContentStart) based on scroll progress
- modifier = (int) (tabContentStart * progress);
- } else {
- // We are not scrolling tab 0 in any way, no modifier needed
- modifier = 0;
- }
-
- strip.setBounds((int) (fromTabLeft + ((toTabLeft - fromTabLeft) * progress)) + modifier,
- 0,
- (int) (fromTabRight + ((toTabRight - fromTabRight) * progress)),
- getHeight());
- invalidate();
- }
-
- /*
- * position + positionOffset goes from 0 to 2 as we scroll from page 1 to 3.
- * Normalized progress is relative to the the direction the page is being scrolled towards.
- * For this, we maintain direction of scroll with a state, and the child view we are moving towards and away from.
- */
- void setScrollingData(int position, float positionOffset) {
- if (position >= getChildCount() - 1) {
- return;
- }
-
- final float currProgress = position + positionOffset;
-
- if (prevProgress > currProgress) {
- toPosition = position;
- fromPosition = position + 1;
- progress = 1 - positionOffset;
- } else {
- toPosition = position + 1;
- fromPosition = position;
- progress = positionOffset;
- }
-
- toTab = getChildAt(toPosition);
- fromTab = getChildAt(fromPosition);
-
- prevProgress = currProgress;
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- if (strip != null) {
- strip.draw(canvas);
- }
- }
-
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (v == this && hasFocus && getChildCount() > 0) {
- selectedView.requestFocus();
- return;
- }
-
- if (!hasFocus) {
- return;
- }
-
- int i = 0;
- final int numTabs = getChildCount();
-
- while (i < numTabs) {
- View view = getChildAt(i);
- if (view == v) {
- view.requestFocus();
- if (isShown()) {
- // A view is focused so send an event to announce the menu strip state.
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- }
- break;
- }
-
- i++;
- }
- }
-
- void setOnTitleClickListener(TabMenuStrip.OnTitleClickListener onTitleClickListener) {
- this.onTitleClickListener = onTitleClickListener;
- }
-
- private class ViewClickListener implements OnClickListener {
- private final int mIndex;
-
- public ViewClickListener(int index) {
- mIndex = index;
- }
-
- @Override
- public void onClick(View view) {
- if (onTitleClickListener != null) {
- onTitleClickListener.onTitleClicked(mIndex);
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/TopSitesGridItemView.java b/mobile/android/base/java/org/mozilla/gecko/home/TopSitesGridItemView.java
deleted file mode 100644
index c17aff209..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/TopSitesGridItemView.java
+++ /dev/null
@@ -1,312 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.widget.ImageView.ScaleType;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract.TopSites;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-
-import java.util.concurrent.Future;
-
-/**
- * A view that displays the thumbnail and the title/url for a top/pinned site.
- * If the title/url is longer than the width of the view, they are faded out.
- * If there is no valid url, a default string is shown at 50% opacity.
- * This is denoted by the empty state.
- */
-public class TopSitesGridItemView extends RelativeLayout implements IconCallback {
- private static final String LOGTAG = "GeckoTopSitesGridItemView";
-
- // Empty state, to denote there is no valid url.
- private static final int[] STATE_EMPTY = { android.R.attr.state_empty };
-
- private static final ScaleType SCALE_TYPE_FAVICON = ScaleType.CENTER;
- private static final ScaleType SCALE_TYPE_RESOURCE = ScaleType.CENTER;
- private static final ScaleType SCALE_TYPE_THUMBNAIL = ScaleType.CENTER_CROP;
- private static final ScaleType SCALE_TYPE_URL = ScaleType.CENTER_INSIDE;
-
- // Child views.
- private final TextView mTitleView;
- private final TopSitesThumbnailView mThumbnailView;
-
- // Data backing this view.
- private String mTitle;
- private String mUrl;
-
- private boolean mThumbnailSet;
-
- // Matches BrowserContract.TopSites row types
- private int mType = -1;
-
- // Dirty state.
- private boolean mIsDirty;
-
- private Future<IconResponse> mOngoingIconRequest;
-
- public TopSitesGridItemView(Context context) {
- this(context, null);
- }
-
- public TopSitesGridItemView(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.topSitesGridItemViewStyle);
- }
-
- public TopSitesGridItemView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- LayoutInflater.from(context).inflate(R.layout.top_sites_grid_item_view, this);
-
- mTitleView = (TextView) findViewById(R.id.title);
- mThumbnailView = (TopSitesThumbnailView) findViewById(R.id.thumbnail);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int[] onCreateDrawableState(int extraSpace) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
-
- if (mType == TopSites.TYPE_BLANK) {
- mergeDrawableStates(drawableState, STATE_EMPTY);
- }
-
- return drawableState;
- }
-
- /**
- * @return The title shown by this view.
- */
- public String getTitle() {
- return (!TextUtils.isEmpty(mTitle) ? mTitle : mUrl);
- }
-
- /**
- * @return The url shown by this view.
- */
- public String getUrl() {
- return mUrl;
- }
-
- /**
- * @return The site type associated with this view.
- */
- public int getType() {
- return mType;
- }
-
- /**
- * @param title The title for this view.
- */
- public void setTitle(String title) {
- if (mTitle != null && mTitle.equals(title)) {
- return;
- }
-
- mTitle = title;
- updateTitleView();
- }
-
- /**
- * @param url The url for this view.
- */
- public void setUrl(String url) {
- if (mUrl != null && mUrl.equals(url)) {
- return;
- }
-
- mUrl = url;
- updateTitleView();
- }
-
- public void blankOut() {
- mUrl = "";
- mTitle = "";
- updateType(TopSites.TYPE_BLANK);
- updateTitleView();
- cancelIconLoading();
- ImageLoader.with(getContext()).cancelRequest(mThumbnailView);
- displayThumbnail(R.drawable.top_site_add);
-
- }
-
- public void markAsDirty() {
- mIsDirty = true;
- }
-
- /**
- * Updates the title, URL, and pinned state of this view.
- *
- * Also resets our loadId to NOT_LOADING.
- *
- * Returns true if any fields changed.
- */
- public boolean updateState(final String title, final String url, final int type, final TopSitesPanel.ThumbnailInfo thumbnail) {
- boolean changed = false;
- if (mUrl == null || !mUrl.equals(url)) {
- mUrl = url;
- changed = true;
- }
-
- if (mTitle == null || !mTitle.equals(title)) {
- mTitle = title;
- changed = true;
- }
-
- if (thumbnail != null) {
- if (thumbnail.imageUrl != null) {
- displayThumbnail(thumbnail.imageUrl, thumbnail.bgColor);
- } else if (thumbnail.bitmap != null) {
- displayThumbnail(thumbnail.bitmap);
- }
- } else if (changed) {
- // Because we'll have a new favicon or thumbnail arriving shortly, and
- // we need to not reject it because we already had a thumbnail.
- mThumbnailSet = false;
- }
-
- if (changed) {
- updateTitleView();
- cancelIconLoading();
- ImageLoader.with(getContext()).cancelRequest(mThumbnailView);
- }
-
- if (updateType(type)) {
- changed = true;
- }
-
- // The dirty state forces the state update to return true
- // so that the adapter loads favicons once the thumbnails
- // are loaded in TopSitesPanel/TopSitesGridAdapter.
- changed = (changed || mIsDirty);
- mIsDirty = false;
-
- return changed;
- }
-
- /**
- * Try to load an icon for the given page URL.
- */
- public void loadFavicon(String pageUrl) {
- mOngoingIconRequest = Icons.with(getContext())
- .pageUrl(pageUrl)
- .skipNetwork()
- .build()
- .execute(this);
- }
-
- private void cancelIconLoading() {
- if (mOngoingIconRequest != null) {
- mOngoingIconRequest.cancel(true);
- }
- }
-
- /**
- * Display the thumbnail from a resource.
- *
- * @param resId Resource ID of the drawable to show.
- */
- public void displayThumbnail(int resId) {
- mThumbnailView.setScaleType(SCALE_TYPE_RESOURCE);
- mThumbnailView.setImageResource(resId);
- mThumbnailView.setBackgroundColor(0x0);
- mThumbnailSet = false;
- }
-
- /**
- * Display the thumbnail from a bitmap.
- *
- * @param thumbnail The bitmap to show as thumbnail.
- */
- public void displayThumbnail(Bitmap thumbnail) {
- if (thumbnail == null) {
- return;
- }
-
- mThumbnailSet = true;
-
- cancelIconLoading();
- ImageLoader.with(getContext()).cancelRequest(mThumbnailView);
-
- mThumbnailView.setScaleType(SCALE_TYPE_THUMBNAIL);
- mThumbnailView.setImageBitmap(thumbnail, true);
- mThumbnailView.setBackgroundDrawable(null);
- }
-
- /**
- * Display the thumbnail from a URL.
- *
- * @param imageUrl URL of the image to show.
- * @param bgColor background color to use in the view.
- */
- public void displayThumbnail(final String imageUrl, final int bgColor) {
- mThumbnailView.setScaleType(SCALE_TYPE_URL);
- mThumbnailView.setBackgroundColor(bgColor);
- mThumbnailSet = true;
-
- ImageLoader.with(getContext())
- .load(imageUrl)
- .noFade()
- .into(mThumbnailView);
- }
-
- /**
- * Update the item type associated with this view. Returns true if
- * the type has changed, false otherwise.
- */
- private boolean updateType(int type) {
- if (mType == type) {
- return false;
- }
-
- mType = type;
- refreshDrawableState();
-
- int pinResourceId = (type == TopSites.TYPE_PINNED ? R.drawable.pin : 0);
- mTitleView.setCompoundDrawablesWithIntrinsicBounds(pinResourceId, 0, 0, 0);
-
- return true;
- }
-
- /**
- * Update the title shown by this view. If both title and url
- * are empty, mark the state as STATE_EMPTY and show a default text.
- */
- private void updateTitleView() {
- String title = getTitle();
- if (!TextUtils.isEmpty(title)) {
- mTitleView.setText(title);
- } else {
- mTitleView.setText(R.string.home_top_sites_add);
- }
- }
-
- /**
- * Display the loaded icon (if no thumbnail is set).
- */
- @Override
- public void onIconResponse(IconResponse response) {
- if (mThumbnailSet) {
- // Already showing a thumbnail; do nothing.
- return;
- }
-
- mThumbnailView.setScaleType(SCALE_TYPE_FAVICON);
- mThumbnailView.setImageBitmap(response.getBitmap(), false);
- mThumbnailView.setBackgroundColorWithOpacityFilter(response.getColor());
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/TopSitesGridView.java b/mobile/android/base/java/org/mozilla/gecko/home/TopSitesGridView.java
deleted file mode 100644
index 58a05b198..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/TopSitesGridView.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.ThumbnailHelper;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.View;
-import android.widget.AbsListView;
-import android.widget.GridView;
-
-/**
- * A grid view of top and pinned sites.
- * Each cell in the grid is a TopSitesGridItemView.
- */
-public class TopSitesGridView extends GridView {
- private static final String LOGTAG = "GeckoTopSitesGridView";
-
- // Listener for editing pinned sites.
- public static interface OnEditPinnedSiteListener {
- public void onEditPinnedSite(int position, String searchTerm);
- }
-
- // Max number of top sites that needs to be shown.
- private final int mMaxSites;
-
- // Number of columns to show.
- private final int mNumColumns;
-
- // Horizontal spacing in between the rows.
- private final int mHorizontalSpacing;
-
- // Vertical spacing in between the rows.
- private final int mVerticalSpacing;
-
- // Measured width of this view.
- private int mMeasuredWidth;
-
- // Measured height of this view.
- private int mMeasuredHeight;
-
- // A dummy View used to measure the required size of the child Views.
- private final TopSitesGridItemView dummyChildView;
-
- // Context menu info.
- private TopSitesGridContextMenuInfo mContextMenuInfo;
-
- // Whether we're handling focus changes or not. This is used
- // to avoid infinite re-layouts when using this GridView as
- // a ListView header view (see bug 918044).
- private boolean mIsHandlingFocusChange;
-
- public TopSitesGridView(Context context) {
- this(context, null);
- }
-
- public TopSitesGridView(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.topSitesGridViewStyle);
- }
-
- public TopSitesGridView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mMaxSites = getResources().getInteger(R.integer.number_of_top_sites);
- mNumColumns = getResources().getInteger(R.integer.number_of_top_sites_cols);
- setNumColumns(mNumColumns);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TopSitesGridView, defStyle, 0);
- mHorizontalSpacing = a.getDimensionPixelOffset(R.styleable.TopSitesGridView_android_horizontalSpacing, 0x00);
- mVerticalSpacing = a.getDimensionPixelOffset(R.styleable.TopSitesGridView_android_verticalSpacing, 0x00);
- a.recycle();
-
- dummyChildView = new TopSitesGridItemView(context);
- // Set a default LayoutParams on the child, if it doesn't have one on its own.
- AbsListView.LayoutParams params = (AbsListView.LayoutParams) dummyChildView.getLayoutParams();
- if (params == null) {
- params = new AbsListView.LayoutParams(AbsListView.LayoutParams.WRAP_CONTENT,
- AbsListView.LayoutParams.WRAP_CONTENT);
- dummyChildView.setLayoutParams(params);
- }
- }
-
- @Override
- protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
- mIsHandlingFocusChange = true;
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
- mIsHandlingFocusChange = false;
- }
-
- @Override
- public void requestLayout() {
- if (!mIsHandlingFocusChange) {
- super.requestLayout();
- }
- }
-
- @Override
- public int getColumnWidth() {
- // This method will be called from onMeasure() too.
- // It's better to use getMeasuredWidth(), as it is safe in this case.
- final int totalHorizontalSpacing = mNumColumns > 0 ? (mNumColumns - 1) * mHorizontalSpacing : 0;
- return (getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - totalHorizontalSpacing) / mNumColumns;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // Sets the padding for this view.
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- final int measuredWidth = getMeasuredWidth();
- if (measuredWidth == mMeasuredWidth) {
- // Return the cached values as the width is the same.
- setMeasuredDimension(mMeasuredWidth, mMeasuredHeight);
- return;
- }
-
- final int columnWidth = getColumnWidth();
-
- // Measure the exact width of the child, and the height based on the width.
- // Note: the child (and TopSitesThumbnailView) takes care of calculating its height.
- int childWidthSpec = MeasureSpec.makeMeasureSpec(columnWidth, MeasureSpec.EXACTLY);
- int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- dummyChildView.measure(childWidthSpec, childHeightSpec);
- final int childHeight = dummyChildView.getMeasuredHeight();
-
- // This is the maximum width of the contents of each child in the grid.
- // Use this as the target width for thumbnails.
- final int thumbnailWidth = dummyChildView.getMeasuredWidth() - dummyChildView.getPaddingLeft() - dummyChildView.getPaddingRight();
- ThumbnailHelper.getInstance().setThumbnailWidth(thumbnailWidth);
-
- // Number of rows required to show these top sites.
- final int rows = (int) Math.ceil((double) mMaxSites / mNumColumns);
- final int childrenHeight = childHeight * rows;
- final int totalVerticalSpacing = rows > 0 ? (rows - 1) * mVerticalSpacing : 0;
-
- // Total height of this view.
- final int measuredHeight = childrenHeight + getPaddingTop() + getPaddingBottom() + totalVerticalSpacing;
- setMeasuredDimension(measuredWidth, measuredHeight);
- mMeasuredWidth = measuredWidth;
- mMeasuredHeight = measuredHeight;
- }
-
- @Override
- public ContextMenuInfo getContextMenuInfo() {
- return mContextMenuInfo;
- }
-
- public void setContextMenuInfo(TopSitesGridContextMenuInfo contextMenuInfo) {
- mContextMenuInfo = contextMenuInfo;
- }
-
- /**
- * Stores information regarding the creation of the context menu for a GridView item.
- */
- public static class TopSitesGridContextMenuInfo extends HomeContextMenuInfo {
- public int type = -1;
-
- public TopSitesGridContextMenuInfo(View targetView, int position, long id) {
- super(targetView, position, id);
- this.itemType = RemoveItemType.HISTORY;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/TopSitesPanel.java b/mobile/android/base/java/org/mozilla/gecko/home/TopSitesPanel.java
deleted file mode 100644
index f39e51ac5..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/TopSitesPanel.java
+++ /dev/null
@@ -1,968 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import static org.mozilla.gecko.db.URLMetadataTable.TILE_COLOR_COLUMN;
-import static org.mozilla.gecko.db.URLMetadataTable.TILE_IMAGE_URL_COLUMN;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Future;
-
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserContract.Thumbnails;
-import org.mozilla.gecko.db.BrowserContract.TopSites;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.gfx.BitmapUtils;
-import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.home.PinSiteDialog.OnSiteSelectedListener;
-import org.mozilla.gecko.home.TopSitesGridView.OnEditPinnedSiteListener;
-import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.restrictions.Restrictable;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.util.StringUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.app.Activity;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.LoaderManager.LoaderCallbacks;
-import android.support.v4.content.AsyncTaskLoader;
-import android.support.v4.content.Loader;
-import android.support.v4.widget.CursorAdapter;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.LayoutInflater;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ListView;
-
-/**
- * Fragment that displays frecency search results in a ListView.
- */
-public class TopSitesPanel extends HomeFragment {
- // Logging tag name
- private static final String LOGTAG = "GeckoTopSitesPanel";
-
- // Cursor loader ID for the top sites
- private static final int LOADER_ID_TOP_SITES = 0;
-
- // Loader ID for thumbnails
- private static final int LOADER_ID_THUMBNAILS = 1;
-
- // Key for thumbnail urls
- private static final String THUMBNAILS_URLS_KEY = "urls";
-
- // Adapter for the list of top sites
- private VisitedAdapter mListAdapter;
-
- // Adapter for the grid of top sites
- private TopSitesGridAdapter mGridAdapter;
-
- // List of top sites
- private HomeListView mList;
-
- // Grid of top sites
- private TopSitesGridView mGrid;
-
- // Callbacks used for the search and favicon cursor loaders
- private CursorLoaderCallbacks mCursorLoaderCallbacks;
-
- // Callback for thumbnail loader
- private ThumbnailsLoaderCallbacks mThumbnailsLoaderCallbacks;
-
- // Listener for editing pinned sites.
- private EditPinnedSiteListener mEditPinnedSiteListener;
-
- // Max number of entries shown in the grid from the cursor.
- private int mMaxGridEntries;
-
- // Time in ms until the Gecko thread is reset to normal priority.
- private static final long PRIORITY_RESET_TIMEOUT = 10000;
-
- public static TopSitesPanel newInstance() {
- return new TopSitesPanel();
- }
-
- private static final boolean logDebug = Log.isLoggable(LOGTAG, Log.DEBUG);
- private static final boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
-
- private static void debug(final String message) {
- if (logDebug) {
- Log.d(LOGTAG, message);
- }
- }
-
- private static void trace(final String message) {
- if (logVerbose) {
- Log.v(LOGTAG, message);
- }
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
-
- mMaxGridEntries = activity.getResources().getInteger(R.integer.number_of_top_sites);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.home_top_sites_panel, container, false);
-
- mList = (HomeListView) view.findViewById(R.id.list);
-
- mGrid = new TopSitesGridView(getActivity());
- mList.addHeaderView(mGrid);
-
- return view;
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- mEditPinnedSiteListener = new EditPinnedSiteListener();
-
- mList.setTag(HomePager.LIST_TAG_TOP_SITES);
- mList.setHeaderDividersEnabled(false);
-
- mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- final ListView list = (ListView) parent;
- final int headerCount = list.getHeaderViewsCount();
- if (position < headerCount) {
- // The click is on a header, don't do anything.
- return;
- }
-
- // Absolute position for the adapter.
- position += (mGridAdapter.getCount() - headerCount);
-
- final Cursor c = mListAdapter.getCursor();
- if (c == null || !c.moveToPosition(position)) {
- return;
- }
-
- final String url = c.getString(c.getColumnIndexOrThrow(TopSites.URL));
-
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, "top_sites");
-
- // This item is a TwoLinePageRow, so we allow switch-to-tab.
- mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
- }
- });
-
- mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
- @Override
- public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
- final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
- info.url = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.URL));
- info.title = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.TITLE));
- info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(TopSites.HISTORY_ID));
- info.itemType = RemoveItemType.HISTORY;
- final int bookmarkIdCol = cursor.getColumnIndexOrThrow(TopSites.BOOKMARK_ID);
- if (cursor.isNull(bookmarkIdCol)) {
- // If this is a combined cursor, we may get a history item without a
- // bookmark, in which case the bookmarks ID column value will be null.
- info.bookmarkId = -1;
- } else {
- info.bookmarkId = cursor.getInt(bookmarkIdCol);
- }
- return info;
- }
- });
-
- mGrid.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- TopSitesGridItemView item = (TopSitesGridItemView) view;
-
- // Decode "user-entered" URLs before loading them.
- String url = StringUtils.decodeUserEnteredUrl(item.getUrl());
- int type = item.getType();
-
- // If the url is empty, the user can pin a site.
- // If not, navigate to the page given by the url.
- if (type != TopSites.TYPE_BLANK) {
- if (mUrlOpenListener != null) {
- final TelemetryContract.Method method;
- if (type == TopSites.TYPE_SUGGESTED) {
- method = TelemetryContract.Method.SUGGESTION;
- } else {
- method = TelemetryContract.Method.GRID_ITEM;
- }
-
- String extra = Integer.toString(position);
- if (type == TopSites.TYPE_PINNED) {
- extra += "-pinned";
- }
-
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, method, extra);
-
- mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.NO_READER_VIEW));
- }
- } else {
- if (mEditPinnedSiteListener != null) {
- mEditPinnedSiteListener.onEditPinnedSite(position, "");
- }
- }
- }
- });
-
- mGrid.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
-
- Cursor cursor = (Cursor) parent.getItemAtPosition(position);
-
- TopSitesGridItemView item = (TopSitesGridItemView) view;
- if (cursor == null || item.getType() == TopSites.TYPE_BLANK) {
- mGrid.setContextMenuInfo(null);
- return false;
- }
-
- TopSitesGridContextMenuInfo contextMenuInfo = new TopSitesGridContextMenuInfo(view, position, id);
- updateContextMenuFromCursor(contextMenuInfo, cursor);
- mGrid.setContextMenuInfo(contextMenuInfo);
- return mGrid.showContextMenuForChild(mGrid);
- }
-
- /*
- * Update the fields of a TopSitesGridContextMenuInfo object
- * from a cursor.
- *
- * @param info context menu info object to be updated
- * @param cursor used to update the context menu info object
- */
- private void updateContextMenuFromCursor(TopSitesGridContextMenuInfo info, Cursor cursor) {
- info.url = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.URL));
- info.title = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.TITLE));
- info.type = cursor.getInt(cursor.getColumnIndexOrThrow(TopSites.TYPE));
- info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(TopSites.HISTORY_ID));
- }
- });
-
- registerForContextMenu(mList);
- registerForContextMenu(mGrid);
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
-
- // Discard any additional item clicks on the list as the
- // panel is getting destroyed (see bugs 930160 & 1096958).
- mList.setOnItemClickListener(null);
- mGrid.setOnItemClickListener(null);
-
- mList = null;
- mGrid = null;
- mListAdapter = null;
- mGridAdapter = null;
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- final Activity activity = getActivity();
-
- // Setup the top sites grid adapter.
- mGridAdapter = new TopSitesGridAdapter(activity, null);
- mGrid.setAdapter(mGridAdapter);
-
- // Setup the top sites list adapter.
- mListAdapter = new VisitedAdapter(activity, null);
- mList.setAdapter(mListAdapter);
-
- // Create callbacks before the initial loader is started
- mCursorLoaderCallbacks = new CursorLoaderCallbacks();
- mThumbnailsLoaderCallbacks = new ThumbnailsLoaderCallbacks();
- loadIfVisible();
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
- if (menuInfo == null) {
- return;
- }
-
- if (!(menuInfo instanceof TopSitesGridContextMenuInfo)) {
- // Long pressed item was not a Top Sites GridView item. Superclass
- // can handle this.
- super.onCreateContextMenu(menu, view, menuInfo);
-
- if (!Restrictions.isAllowed(view.getContext(), Restrictable.CLEAR_HISTORY)) {
- menu.findItem(R.id.home_remove).setVisible(false);
- }
-
- return;
- }
-
- final Context context = view.getContext();
-
- // Long pressed item was a Top Sites GridView item, handle it.
- MenuInflater inflater = new MenuInflater(context);
- inflater.inflate(R.menu.home_contextmenu, menu);
-
- // Hide unused menu items.
- menu.findItem(R.id.home_edit_bookmark).setVisible(false);
-
- menu.findItem(R.id.home_remove).setVisible(Restrictions.isAllowed(context, Restrictable.CLEAR_HISTORY));
-
- TopSitesGridContextMenuInfo info = (TopSitesGridContextMenuInfo) menuInfo;
- menu.setHeaderTitle(info.getDisplayTitle());
-
- if (info.type != TopSites.TYPE_BLANK) {
- if (info.type == TopSites.TYPE_PINNED) {
- menu.findItem(R.id.top_sites_pin).setVisible(false);
- } else {
- menu.findItem(R.id.top_sites_unpin).setVisible(false);
- }
- } else {
- menu.findItem(R.id.home_open_new_tab).setVisible(false);
- menu.findItem(R.id.home_open_private_tab).setVisible(false);
- menu.findItem(R.id.top_sites_pin).setVisible(false);
- menu.findItem(R.id.top_sites_unpin).setVisible(false);
- }
-
- if (!StringUtils.isShareableUrl(info.url) || GeckoProfile.get(getActivity()).inGuestMode()) {
- menu.findItem(R.id.home_share).setVisible(false);
- }
-
- if (!Restrictions.isAllowed(context, Restrictable.PRIVATE_BROWSING)) {
- menu.findItem(R.id.home_open_private_tab).setVisible(false);
- }
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- if (super.onContextItemSelected(item)) {
- // HomeFragment was able to handle to selected item.
- return true;
- }
-
- ContextMenuInfo menuInfo = item.getMenuInfo();
-
- if (!(menuInfo instanceof TopSitesGridContextMenuInfo)) {
- return false;
- }
-
- TopSitesGridContextMenuInfo info = (TopSitesGridContextMenuInfo) menuInfo;
-
- final int itemId = item.getItemId();
- final BrowserDB db = BrowserDB.from(getActivity());
-
- if (itemId == R.id.top_sites_pin) {
- final String url = info.url;
- final String title = info.title;
- final int position = info.position;
- final Context context = getActivity().getApplicationContext();
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- db.pinSite(context.getContentResolver(), url, title, position);
- }
- });
-
- Telemetry.sendUIEvent(TelemetryContract.Event.PIN);
- return true;
- }
-
- if (itemId == R.id.top_sites_unpin) {
- final int position = info.position;
- final Context context = getActivity().getApplicationContext();
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- db.unpinSite(context.getContentResolver(), position);
- }
- });
-
- Telemetry.sendUIEvent(TelemetryContract.Event.UNPIN);
-
- return true;
- }
-
- if (itemId == R.id.top_sites_edit) {
- // Decode "user-entered" URLs before showing them.
- mEditPinnedSiteListener.onEditPinnedSite(info.position,
- StringUtils.decodeUserEnteredUrl(info.url));
-
- Telemetry.sendUIEvent(TelemetryContract.Event.EDIT);
- return true;
- }
-
- return false;
- }
-
- @Override
- protected void load() {
- getLoaderManager().initLoader(LOADER_ID_TOP_SITES, null, mCursorLoaderCallbacks);
-
- // Since this is the primary fragment that loads whenever about:home is
- // visited, we want to load it as quickly as possible. Heavy load on
- // the Gecko thread can slow down the time it takes for thumbnails to
- // appear, especially during startup (bug 897162). By minimizing the
- // Gecko thread priority, we ensure that the UI appears quickly. The
- // priority is reset to normal once thumbnails are loaded.
- ThreadUtils.reduceGeckoPriority(PRIORITY_RESET_TIMEOUT);
- }
-
- /**
- * Listener for editing pinned sites.
- */
- private class EditPinnedSiteListener implements OnEditPinnedSiteListener,
- OnSiteSelectedListener {
- // Tag for the PinSiteDialog fragment.
- private static final String TAG_PIN_SITE = "pin_site";
-
- // Position of the pin.
- private int mPosition;
-
- @Override
- public void onEditPinnedSite(int position, String searchTerm) {
- final FragmentManager manager = getChildFragmentManager();
- PinSiteDialog dialog = (PinSiteDialog) manager.findFragmentByTag(TAG_PIN_SITE);
- if (dialog == null) {
- mPosition = position;
-
- dialog = PinSiteDialog.newInstance();
- dialog.setOnSiteSelectedListener(this);
- dialog.setSearchTerm(searchTerm);
- dialog.show(manager, TAG_PIN_SITE);
- }
- }
-
- @Override
- public void onSiteSelected(final String url, final String title) {
- final int position = mPosition;
- final Context context = getActivity().getApplicationContext();
- final BrowserDB db = BrowserDB.from(getActivity());
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- db.pinSite(context.getContentResolver(), url, title, position);
- }
- });
- }
- }
-
- private void updateUiFromCursor(Cursor c) {
- mList.setHeaderDividersEnabled(c != null && c.getCount() > mMaxGridEntries);
- }
-
- private void updateUiWithThumbnails(Map<String, ThumbnailInfo> thumbnails) {
- if (mGridAdapter != null) {
- mGridAdapter.updateThumbnails(thumbnails);
- }
-
- // Once thumbnails have finished loading, the UI is ready. Reset
- // Gecko to normal priority.
- ThreadUtils.resetGeckoPriority();
- }
-
- private static class TopSitesLoader extends SimpleCursorLoader {
- // Max number of search results.
- private static final int SEARCH_LIMIT = 30;
- private static final String TELEMETRY_HISTOGRAM_LOAD_CURSOR = "FENNEC_TOPSITES_LOADER_TIME_MS";
- private final BrowserDB mDB;
- private final int mMaxGridEntries;
-
- public TopSitesLoader(Context context) {
- super(context);
- mMaxGridEntries = context.getResources().getInteger(R.integer.number_of_top_sites);
- mDB = BrowserDB.from(context);
- }
-
- @Override
- public Cursor loadCursor() {
- final long start = SystemClock.uptimeMillis();
- final Cursor cursor = mDB.getTopSites(getContext().getContentResolver(), mMaxGridEntries, SEARCH_LIMIT);
- final long end = SystemClock.uptimeMillis();
- final long took = end - start;
- Telemetry.addToHistogram(TELEMETRY_HISTOGRAM_LOAD_CURSOR, (int) Math.min(took, Integer.MAX_VALUE));
- return cursor;
- }
- }
-
- private class VisitedAdapter extends CursorAdapter {
- public VisitedAdapter(Context context, Cursor cursor) {
- super(context, cursor, 0);
- }
-
- @Override
- public int getCount() {
- return Math.max(0, super.getCount() - mMaxGridEntries);
- }
-
- @Override
- public Object getItem(int position) {
- return super.getItem(position + mMaxGridEntries);
- }
-
- /**
- * We have to override default getItemId implementation, since for a given position, it returns
- * value of the _id column. In our case _id is always 0 (see Combined view).
- */
- @Override
- public long getItemId(int position) {
- final int adjustedPosition = position + mMaxGridEntries;
- final Cursor cursor = getCursor();
-
- cursor.moveToPosition(adjustedPosition);
- return getItemIdForTopSitesCursor(cursor);
- }
-
- @Override
- public void bindView(View view, Context context, Cursor cursor) {
- final int position = cursor.getPosition();
- cursor.moveToPosition(position + mMaxGridEntries);
-
- final TwoLinePageRow row = (TwoLinePageRow) view;
- row.updateFromCursor(cursor);
- }
-
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- return LayoutInflater.from(context).inflate(R.layout.bookmark_item_row, parent, false);
- }
- }
-
- public class TopSitesGridAdapter extends CursorAdapter {
- private final BrowserDB mDB;
- // Cache to store the thumbnails.
- // Ensure that this is only accessed from the UI thread.
- private Map<String, ThumbnailInfo> mThumbnailInfos;
-
- public TopSitesGridAdapter(Context context, Cursor cursor) {
- super(context, cursor, 0);
- mDB = BrowserDB.from(context);
- }
-
- @Override
- public int getCount() {
- return Math.min(mMaxGridEntries, super.getCount());
- }
-
- @Override
- protected void onContentChanged() {
- // Don't do anything. We don't want to regenerate every time
- // our database is updated.
- return;
- }
-
- /**
- * Update the thumbnails returned by the db.
- *
- * @param thumbnails A map of urls and their thumbnail bitmaps.
- */
- public void updateThumbnails(Map<String, ThumbnailInfo> thumbnails) {
- mThumbnailInfos = thumbnails;
-
- final int count = mGrid.getChildCount();
- for (int i = 0; i < count; i++) {
- TopSitesGridItemView gridItem = (TopSitesGridItemView) mGrid.getChildAt(i);
-
- // All the views have already got their initial state at this point.
- // This will force each view to load favicons for the missing
- // thumbnails if necessary.
- gridItem.markAsDirty();
- }
-
- notifyDataSetChanged();
- }
-
- /**
- * We have to override default getItemId implementation, since for a given position, it returns
- * value of the _id column. In our case _id is always 0 (see Combined view).
- */
- @Override
- public long getItemId(int position) {
- final Cursor cursor = getCursor();
- cursor.moveToPosition(position);
-
- return getItemIdForTopSitesCursor(cursor);
- }
-
- @Override
- public void bindView(View bindView, Context context, Cursor cursor) {
- final String url = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.URL));
- final String title = cursor.getString(cursor.getColumnIndexOrThrow(TopSites.TITLE));
- final int type = cursor.getInt(cursor.getColumnIndexOrThrow(TopSites.TYPE));
-
- final TopSitesGridItemView view = (TopSitesGridItemView) bindView;
-
- // If there is no url, then show "add bookmark".
- if (type == TopSites.TYPE_BLANK) {
- view.blankOut();
- return;
- }
-
- // Show the thumbnail, if any.
- ThumbnailInfo thumbnail = (mThumbnailInfos != null ? mThumbnailInfos.get(url) : null);
-
- // Debounce bindView calls to avoid redundant redraws and favicon
- // fetches.
- final boolean updated = view.updateState(title, url, type, thumbnail);
-
- // Thumbnails are delivered late, so we can't short-circuit any
- // sooner than this. But we can avoid a duplicate favicon
- // fetch...
- if (!updated) {
- debug("bindView called twice for same values; short-circuiting.");
- return;
- }
-
- // Make sure we query suggested images without the user-entered wrapper.
- final String decodedUrl = StringUtils.decodeUserEnteredUrl(url);
-
- // Suggested images have precedence over thumbnails, no need to wait
- // for them to be loaded. See: CursorLoaderCallbacks.onLoadFinished()
- final String imageUrl = mDB.getSuggestedImageUrlForUrl(decodedUrl);
- if (!TextUtils.isEmpty(imageUrl)) {
- final int bgColor = mDB.getSuggestedBackgroundColorForUrl(decodedUrl);
- view.displayThumbnail(imageUrl, bgColor);
- return;
- }
-
- // If thumbnails are still being loaded, don't try to load favicons
- // just yet. If we sent in a thumbnail, we're done now.
- if (mThumbnailInfos == null || thumbnail != null) {
- return;
- }
-
- view.loadFavicon(url);
- }
-
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- return new TopSitesGridItemView(context);
- }
- }
-
- private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- trace("Creating TopSitesLoader: " + id);
- return new TopSitesLoader(getActivity());
- }
-
- /**
- * This method is called *twice* in some circumstances.
- *
- * If you try to avoid that through some kind of boolean flag,
- * sometimes (e.g., returning to the activity) you'll *not* be called
- * twice, and thus you'll never draw thumbnails.
- *
- * The root cause is TopSitesLoader.loadCursor being called twice.
- * Why that is... dunno.
- */
- public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
- debug("onLoadFinished: " + c.getCount() + " rows.");
-
- mListAdapter.swapCursor(c);
- mGridAdapter.swapCursor(c);
- updateUiFromCursor(c);
-
- final int col = c.getColumnIndexOrThrow(TopSites.URL);
-
- // Load the thumbnails.
- // Even though the cursor we're given is supposed to be fresh,
- // we getIcon a bad first value unless we reset its position.
- // Using move(-1) and moveToNext() doesn't work correctly under
- // rotation, so we use moveToFirst.
- if (!c.moveToFirst()) {
- return;
- }
-
- final ArrayList<String> urls = new ArrayList<String>();
- int i = 1;
- do {
- final String url = c.getString(col);
-
- // Only try to fetch thumbnails for non-empty URLs that
- // don't have an associated suggested image URL.
- final GeckoProfile profile = GeckoProfile.get(getActivity());
- if (TextUtils.isEmpty(url) || BrowserDB.from(profile).hasSuggestedImageUrl(url)) {
- continue;
- }
-
- urls.add(url);
- } while (i++ < mMaxGridEntries && c.moveToNext());
-
- if (urls.isEmpty()) {
- // Short-circuit empty results to the UI.
- updateUiWithThumbnails(new HashMap<String, ThumbnailInfo>());
- return;
- }
-
- Bundle bundle = new Bundle();
- bundle.putStringArrayList(THUMBNAILS_URLS_KEY, urls);
- getLoaderManager().restartLoader(LOADER_ID_THUMBNAILS, bundle, mThumbnailsLoaderCallbacks);
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- if (mListAdapter != null) {
- mListAdapter.swapCursor(null);
- }
-
- if (mGridAdapter != null) {
- mGridAdapter.swapCursor(null);
- }
- }
- }
-
- static class ThumbnailInfo {
- public final Bitmap bitmap;
- public final String imageUrl;
- public final int bgColor;
-
- public ThumbnailInfo(final Bitmap bitmap) {
- this.bitmap = bitmap;
- this.imageUrl = null;
- this.bgColor = Color.TRANSPARENT;
- }
-
- public ThumbnailInfo(final String imageUrl, final int bgColor) {
- this.bitmap = null;
- this.imageUrl = imageUrl;
- this.bgColor = bgColor;
- }
-
- public static ThumbnailInfo fromMetadata(final Map<String, Object> data) {
- if (data == null) {
- return null;
- }
-
- final String imageUrl = (String) data.get(TILE_IMAGE_URL_COLUMN);
- if (imageUrl == null) {
- return null;
- }
-
- int bgColor = Color.WHITE;
- final String colorString = (String) data.get(TILE_COLOR_COLUMN);
- try {
- bgColor = Color.parseColor(colorString);
- } catch (Exception ex) {
- }
-
- return new ThumbnailInfo(imageUrl, bgColor);
- }
- }
-
- /**
- * An AsyncTaskLoader to load the thumbnails from a cursor.
- */
- static class ThumbnailsLoader extends AsyncTaskLoader<Map<String, ThumbnailInfo>> {
- private final BrowserDB mDB;
- private Map<String, ThumbnailInfo> mThumbnailInfos;
- private final ArrayList<String> mUrls;
-
- private static final List<String> COLUMNS;
- static {
- final ArrayList<String> tempColumns = new ArrayList<>(2);
- tempColumns.add(TILE_IMAGE_URL_COLUMN);
- tempColumns.add(TILE_COLOR_COLUMN);
- COLUMNS = Collections.unmodifiableList(tempColumns);
- }
-
- public ThumbnailsLoader(Context context, ArrayList<String> urls) {
- super(context);
- mUrls = urls;
- mDB = BrowserDB.from(context);
- }
-
- @Override
- public Map<String, ThumbnailInfo> loadInBackground() {
- final Map<String, ThumbnailInfo> thumbnails = new HashMap<String, ThumbnailInfo>();
- if (mUrls == null || mUrls.size() == 0) {
- return thumbnails;
- }
-
- // We need to query metadata based on the URL without any refs, hence we create a new
- // mapping and list of these URLs (we need to preserve the original URL for display purposes)
- final Map<String, String> queryURLs = new HashMap<>();
- for (final String pageURL : mUrls) {
- queryURLs.put(pageURL, StringUtils.stripRef(pageURL));
- }
-
- // Query the DB for tile images.
- final ContentResolver cr = getContext().getContentResolver();
- // Use the stripped URLs for querying the DB
- final Map<String, Map<String, Object>> metadata = mDB.getURLMetadata().getForURLs(cr, queryURLs.values(), COLUMNS);
-
- // Keep a list of urls that don't have tiles images. We'll use thumbnails for them instead.
- final List<String> thumbnailUrls = new ArrayList<String>();
- for (final String pageURL : mUrls) {
- final String queryURL = queryURLs.get(pageURL);
-
- ThumbnailInfo info = ThumbnailInfo.fromMetadata(metadata.get(queryURL));
- if (info == null) {
- // If we didn't find metadata, we'll look for a thumbnail for this url.
- thumbnailUrls.add(pageURL);
- continue;
- }
-
- thumbnails.put(pageURL, info);
- }
-
- if (thumbnailUrls.size() == 0) {
- return thumbnails;
- }
-
- // Query the DB for tile thumbnails.
- final Cursor cursor = mDB.getThumbnailsForUrls(cr, thumbnailUrls);
- if (cursor == null) {
- return thumbnails;
- }
-
- try {
- final int urlIndex = cursor.getColumnIndexOrThrow(Thumbnails.URL);
- final int dataIndex = cursor.getColumnIndexOrThrow(Thumbnails.DATA);
-
- while (cursor.moveToNext()) {
- String url = cursor.getString(urlIndex);
-
- // This should never be null, but if it is...
- final byte[] b = cursor.getBlob(dataIndex);
- if (b == null) {
- continue;
- }
-
- final Bitmap bitmap = BitmapUtils.decodeByteArray(b);
-
- // Our thumbnails are never null, so if we getIcon a null decoded
- // bitmap, it's because we hit an OOM or some other disaster.
- // Give up immediately rather than hammering on.
- if (bitmap == null) {
- Log.w(LOGTAG, "Aborting thumbnail load; decode failed.");
- break;
- }
-
- thumbnails.put(url, new ThumbnailInfo(bitmap));
- }
- } finally {
- cursor.close();
- }
-
- return thumbnails;
- }
-
- @Override
- public void deliverResult(Map<String, ThumbnailInfo> thumbnails) {
- if (isReset()) {
- mThumbnailInfos = null;
- return;
- }
-
- mThumbnailInfos = thumbnails;
-
- if (isStarted()) {
- super.deliverResult(thumbnails);
- }
- }
-
- @Override
- protected void onStartLoading() {
- if (mThumbnailInfos != null) {
- deliverResult(mThumbnailInfos);
- }
-
- if (takeContentChanged() || mThumbnailInfos == null) {
- forceLoad();
- }
- }
-
- @Override
- protected void onStopLoading() {
- cancelLoad();
- }
-
- @Override
- public void onCanceled(Map<String, ThumbnailInfo> thumbnails) {
- mThumbnailInfos = null;
- }
-
- @Override
- protected void onReset() {
- super.onReset();
-
- // Ensure the loader is stopped.
- onStopLoading();
-
- mThumbnailInfos = null;
- }
- }
-
- /**
- * Loader callbacks for the thumbnails on TopSitesGridView.
- */
- private class ThumbnailsLoaderCallbacks implements LoaderCallbacks<Map<String, ThumbnailInfo>> {
- @Override
- public Loader<Map<String, ThumbnailInfo>> onCreateLoader(int id, Bundle args) {
- return new ThumbnailsLoader(getActivity(), args.getStringArrayList(THUMBNAILS_URLS_KEY));
- }
-
- @Override
- public void onLoadFinished(Loader<Map<String, ThumbnailInfo>> loader, Map<String, ThumbnailInfo> thumbnails) {
- updateUiWithThumbnails(thumbnails);
- }
-
- @Override
- public void onLoaderReset(Loader<Map<String, ThumbnailInfo>> loader) {
- if (mGridAdapter != null) {
- mGridAdapter.updateThumbnails(null);
- }
- }
- }
-
- /**
- * We are trying to return stable IDs so that Android can recycle views appropriately:
- * - If we have a history ID then we return it
- * - If we only have a bookmark ID then we negate it and return it. We negate it in order
- * to avoid clashing/conflicting with history IDs.
- *
- * @param cursorInPosition Cursor already moved to position for which we're getting a stable ID
- * @return Stable ID for a given cursor
- */
- private static long getItemIdForTopSitesCursor(final Cursor cursorInPosition) {
- final int historyIdCol = cursorInPosition.getColumnIndexOrThrow(TopSites.HISTORY_ID);
- final long historyId = cursorInPosition.getLong(historyIdCol);
- if (historyId != 0) {
- return historyId;
- }
-
- final int bookmarkIdCol = cursorInPosition.getColumnIndexOrThrow(TopSites.BOOKMARK_ID);
- final long bookmarkId = cursorInPosition.getLong(bookmarkIdCol);
- return -1 * bookmarkId;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/TopSitesThumbnailView.java b/mobile/android/base/java/org/mozilla/gecko/home/TopSitesThumbnailView.java
deleted file mode 100644
index dd45014b0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/TopSitesThumbnailView.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.ThumbnailHelper;
-import org.mozilla.gecko.widget.CropImageView;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-
-/**
- * A width constrained ImageView to show thumbnails of top and pinned sites.
- */
-public class TopSitesThumbnailView extends CropImageView {
- private static final String LOGTAG = "GeckoTopSitesThumbnailView";
-
- // 27.34% opacity filter for the dominant color.
- private static final int COLOR_FILTER = 0x46FFFFFF;
-
- // Default filter color for "Add a bookmark" views.
- private final int mDefaultColor = ContextCompat.getColor(getContext(), R.color.top_site_default);
-
- // Stroke width for the border.
- private final float mStrokeWidth = getResources().getDisplayMetrics().density * 2;
-
- // Paint for drawing the border.
- private final Paint mBorderPaint;
-
- public TopSitesThumbnailView(Context context) {
- this(context, null);
-
- // A border will be drawn if needed.
- setWillNotDraw(false);
- }
-
- public TopSitesThumbnailView(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.topSitesThumbnailViewStyle);
- }
-
- public TopSitesThumbnailView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- // Initialize the border paint.
- final Resources res = getResources();
- mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mBorderPaint.setColor(ContextCompat.getColor(context, R.color.top_site_border));
- mBorderPaint.setStyle(Paint.Style.STROKE);
- }
-
- @Override
- protected float getAspectRatio() {
- return ThumbnailHelper.TOP_SITES_THUMBNAIL_ASPECT_RATIO;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- if (getBackground() == null) {
- mBorderPaint.setStrokeWidth(mStrokeWidth);
- canvas.drawRect(0, 0, getWidth(), getHeight(), mBorderPaint);
- }
- }
-
- /**
- * Sets the background color with a filter to reduce the color opacity.
- *
- * @param color the color filter to apply over the drawable.
- */
- public void setBackgroundColorWithOpacityFilter(int color) {
- setBackgroundColor(color & COLOR_FILTER);
- }
-
- /**
- * Sets the background to a Drawable by applying the specified color as a filter.
- *
- * @param color the color filter to apply over the drawable.
- */
- @Override
- public void setBackgroundColor(int color) {
- if (color == 0) {
- color = mDefaultColor;
- }
-
- Drawable drawable = getResources().getDrawable(R.drawable.top_sites_thumbnail_bg);
- drawable.setColorFilter(color, Mode.SRC_ATOP);
- setBackgroundDrawable(drawable);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java b/mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java
deleted file mode 100644
index 68eb8daa5..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/TwoLinePageRow.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.lang.ref.WeakReference;
-import java.util.concurrent.Future;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.Combined;
-import org.mozilla.gecko.db.BrowserContract.URLColumns;
-import org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy;
-import org.mozilla.gecko.icons.IconDescriptor;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.reader.ReaderModeUtils;
-import org.mozilla.gecko.reader.SavedReaderViewHelper;
-import org.mozilla.gecko.widget.FaviconView;
-
-public class TwoLinePageRow extends LinearLayout
- implements Tabs.OnTabsChangedListener {
-
- protected static final int NO_ICON = 0;
-
- private final TextView mTitle;
- private final TextView mUrl;
- private final ImageView mStatusIcon;
-
- private int mSwitchToTabIconId;
-
- private final FaviconView mFavicon;
- private Future<IconResponse> mOngoingIconLoad;
-
- private boolean mShowIcons;
-
- // The URL for the page corresponding to this view.
- private String mPageUrl;
-
- private boolean mHasReaderCacheItem;
-
- public TwoLinePageRow(Context context) {
- this(context, null);
- }
-
- public TwoLinePageRow(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- setGravity(Gravity.CENTER_VERTICAL);
-
- LayoutInflater.from(context).inflate(R.layout.two_line_page_row, this);
- // Merge layouts lose their padding, so set it dynamically.
- setPadding(0, 0, (int) getResources().getDimension(R.dimen.page_row_edge_padding), 0);
-
- mTitle = (TextView) findViewById(R.id.title);
- mUrl = (TextView) findViewById(R.id.url);
- mStatusIcon = (ImageView) findViewById(R.id.status_icon_bookmark);
-
- mSwitchToTabIconId = NO_ICON;
- mShowIcons = true;
-
- mFavicon = (FaviconView) findViewById(R.id.icon);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- Tabs.registerOnTabsChangedListener(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- // Tabs' listener array is safe to modify during use: its
- // iteration pattern is based on snapshots.
- Tabs.unregisterOnTabsChangedListener(this);
- }
-
- /**
- * Update the row in response to a tab change event.
- * <p>
- * This method is always invoked on the UI thread.
- */
- @Override
- public void onTabChanged(final Tab tab, final Tabs.TabEvents msg, final String data) {
- // Carefully check if this tab event is relevant to this row.
- final String pageUrl = mPageUrl;
- if (pageUrl == null) {
- return;
- }
- if (tab == null) {
- return;
- }
-
- // Return early if the page URL doesn't match the current tab URL,
- // or the old tab URL.
- // data is an empty String for ADDED/CLOSED, and contains the previous/old URL during
- // LOCATION_CHANGE (the new URL is retrieved using tab.getURL()).
- // tabURL and data may be about:reader URLs if the current or old tab page was a reader view
- // page, however pageUrl will always be a plain URL (i.e. we only add about:reader when opening
- // a reader view bookmark, at all other times it's a normal bookmark with normal URL).
- final String tabUrl = tab.getURL();
- if (!pageUrl.equals(ReaderModeUtils.stripAboutReaderUrl(tabUrl)) &&
- !pageUrl.equals(ReaderModeUtils.stripAboutReaderUrl(data))) {
- return;
- }
-
- // Note: we *might* need to update the display status (i.e. switch-to-tab icon/label) if
- // a matching tab has been opened/closed/switched to a different page. updateDisplayedUrl() will
- // determine the changes (if any) that actually need to be made. A tab change with a matching URL
- // does not imply that any changes are needed - e.g. if a given URL is already open in one tab, and
- // is also opened in a second tab, the switch-to-tab status doesn't change, closing 1 of 2 tabs with a URL
- // similarly doesn't change the switch-to-tab display, etc. (However closing the last tab for
- // a given URL does require a status change, as does opening the first tab with that URL.)
- switch (msg) {
- case ADDED:
- case CLOSED:
- case LOCATION_CHANGE:
- updateDisplayedUrl();
- break;
- default:
- break;
- }
- }
-
- private void setTitle(String text) {
- mTitle.setText(text);
- }
-
- protected void setUrl(String text) {
- mUrl.setText(text);
- }
-
- protected void setUrl(int stringId) {
- mUrl.setText(stringId);
- }
-
- protected String getUrl() {
- return mPageUrl;
- }
-
- protected void setSwitchToTabIcon(int iconId) {
- if (mSwitchToTabIconId == iconId) {
- return;
- }
-
- mSwitchToTabIconId = iconId;
- mUrl.setCompoundDrawablesWithIntrinsicBounds(mSwitchToTabIconId, 0, 0, 0);
- }
-
- private void updateStatusIcon(boolean isBookmark, boolean isReaderItem) {
- if (isReaderItem) {
- mStatusIcon.setImageResource(R.drawable.status_icon_readercache);
- } else if (isBookmark) {
- mStatusIcon.setImageResource(R.drawable.star_blue);
- }
-
- if (mShowIcons && (isBookmark || isReaderItem)) {
- mStatusIcon.setVisibility(View.VISIBLE);
- } else if (mShowIcons) {
- // We use INVISIBLE to have consistent padding for our items. This means text/URLs
- // fade consistently in the same location, regardless of them being bookmarked.
- mStatusIcon.setVisibility(View.INVISIBLE);
- } else {
- mStatusIcon.setVisibility(View.GONE);
- }
-
- }
-
- /**
- * Stores the page URL, so that we can use it to replace "Switch to tab" if the open
- * tab changes or is closed.
- */
- private void updateDisplayedUrl(String url, boolean hasReaderCacheItem) {
- mPageUrl = url;
- mHasReaderCacheItem = hasReaderCacheItem;
- updateDisplayedUrl();
- }
-
- /**
- * Replaces the page URL with "Switch to tab" if there is already a tab open with that URL.
- * Only looks for tabs that are either private or non-private, depending on the current
- * selected tab.
- */
- protected void updateDisplayedUrl() {
- final Tab selectedTab = Tabs.getInstance().getSelectedTab();
- final boolean isPrivate = (selectedTab != null) && (selectedTab.isPrivate());
-
- // We always want to display the underlying page url, however for readermode pages
- // we navigate to the about:reader equivalent, hence we need to use that url when finding
- // existing tabs
- final String navigationUrl = mHasReaderCacheItem ? ReaderModeUtils.getAboutReaderForUrl(mPageUrl) : mPageUrl;
- Tab tab = Tabs.getInstance().getFirstTabForUrl(navigationUrl, isPrivate);
-
-
- if (!mShowIcons || tab == null) {
- setUrl(mPageUrl);
- setSwitchToTabIcon(NO_ICON);
- } else {
- setUrl(R.string.switch_to_tab);
- setSwitchToTabIcon(R.drawable.ic_url_bar_tab);
- }
- }
-
- public void setShowIcons(boolean showIcons) {
- mShowIcons = showIcons;
- }
-
- /**
- * Update the data displayed by this row.
- * <p>
- * This method must be invoked on the UI thread.
- *
- * @param title to display.
- * @param url to display.
- */
- public void update(String title, String url) {
- update(title, url, 0, false);
- }
-
- protected void update(String title, String url, long bookmarkId, boolean hasReaderCacheItem) {
- if (mShowIcons) {
- // The bookmark id will be 0 (null in database) when the url
- // is not a bookmark and negative for 'fake' bookmarks.
- final boolean isBookmark = bookmarkId > 0;
-
- updateStatusIcon(isBookmark, hasReaderCacheItem);
- } else {
- updateStatusIcon(false, false);
- }
-
- // Use the URL instead of an empty title for consistency with the normal URL
- // bar view - this is the equivalent of getDisplayTitle() in Tab.java
- setTitle(TextUtils.isEmpty(title) ? url : title);
-
- // No point updating the below things if URL has not changed. Prevents evil Favicon flicker.
- if (url.equals(mPageUrl)) {
- return;
- }
-
- // Blank the Favicon, so we don't show the wrong Favicon if we scroll and miss DB.
- mFavicon.clearImage();
-
- if (mOngoingIconLoad != null) {
- mOngoingIconLoad.cancel(true);
- }
-
- // Displayed RecentTabsPanel URLs may refer to pages opened in reader mode, so we
- // remove the about:reader prefix to ensure the Favicon loads properly.
- final String pageURL = ReaderModeUtils.stripAboutReaderUrl(url);
-
- if (TextUtils.isEmpty(pageURL)) {
- // If url is empty, display the item as-is but do not load an icon if we do not have a page URL (bug 1310622)
- } else if (bookmarkId < BrowserContract.Bookmarks.FAKE_PARTNER_BOOKMARKS_START) {
- mOngoingIconLoad = Icons.with(getContext())
- .pageUrl(pageURL)
- .skipNetwork()
- .privileged(true)
- .icon(IconDescriptor.createGenericIcon(
- PartnerBookmarksProviderProxy.getUriForIcon(getContext(), bookmarkId).toString()))
- .build()
- .execute(mFavicon.createIconCallback());
- } else {
- mOngoingIconLoad = Icons.with(getContext())
- .pageUrl(pageURL)
- .skipNetwork()
- .build()
- .execute(mFavicon.createIconCallback());
-
- }
-
- updateDisplayedUrl(url, hasReaderCacheItem);
- }
-
- /**
- * Update the data displayed by this row.
- * <p>
- * This method must be invoked on the UI thread.
- *
- * @param cursor to extract data from.
- */
- public void updateFromCursor(Cursor cursor) {
- if (cursor == null) {
- return;
- }
-
- int titleIndex = cursor.getColumnIndexOrThrow(URLColumns.TITLE);
- final String title = cursor.getString(titleIndex);
-
- int urlIndex = cursor.getColumnIndexOrThrow(URLColumns.URL);
- final String url = cursor.getString(urlIndex);
-
- final long bookmarkId;
- final int bookmarkIdIndex = cursor.getColumnIndex(Combined.BOOKMARK_ID);
- if (bookmarkIdIndex != -1) {
- bookmarkId = cursor.getLong(bookmarkIdIndex);
- } else {
- bookmarkId = 0;
- }
-
- SavedReaderViewHelper rch = SavedReaderViewHelper.getSavedReaderViewHelper(getContext());
- final boolean hasReaderCacheItem = rch.isURLCached(url);
-
- update(title, url, bookmarkId, hasReaderCacheItem);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStream.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStream.java
deleted file mode 100644
index ef0c105d3..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStream.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- package org.mozilla.gecko.home.activitystream;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.ContextCompat;
-import android.support.v4.content.Loader;
-import android.support.v4.graphics.ColorUtils;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.TypedValue;
-import android.widget.FrameLayout;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.home.HomePager;
-import org.mozilla.gecko.home.activitystream.topsites.TopSitesPagerAdapter;
-import org.mozilla.gecko.util.ContextUtils;
-import org.mozilla.gecko.widget.RecyclerViewClickSupport;
-
-public class ActivityStream extends FrameLayout {
- private final StreamRecyclerAdapter adapter;
-
- private static final int LOADER_ID_HIGHLIGHTS = 0;
- private static final int LOADER_ID_TOPSITES = 1;
-
- private static final int MINIMUM_TILES = 4;
- private static final int MAXIMUM_TILES = 6;
-
- private int desiredTileWidth;
- private int desiredTilesHeight;
- private int tileMargin;
-
- public ActivityStream(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- setBackgroundColor(ContextCompat.getColor(context, R.color.about_page_header_grey));
-
- inflate(context, R.layout.as_content, this);
-
- adapter = new StreamRecyclerAdapter();
-
- RecyclerView rv = (RecyclerView) findViewById(R.id.activity_stream_main_recyclerview);
-
- rv.setAdapter(adapter);
- rv.setLayoutManager(new LinearLayoutManager(getContext()));
- rv.setHasFixedSize(true);
-
- RecyclerViewClickSupport.addTo(rv)
- .setOnItemClickListener(adapter);
-
- final Resources resources = getResources();
- desiredTileWidth = resources.getDimensionPixelSize(R.dimen.activity_stream_desired_tile_width);
- desiredTilesHeight = resources.getDimensionPixelSize(R.dimen.activity_stream_desired_tile_height);
- tileMargin = resources.getDimensionPixelSize(R.dimen.activity_stream_base_margin);
- }
-
- void setOnUrlOpenListeners(HomePager.OnUrlOpenListener onUrlOpenListener, HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
- adapter.setOnUrlOpenListeners(onUrlOpenListener, onUrlOpenInBackgroundListener);
- }
-
- public void load(LoaderManager lm) {
- CursorLoaderCallbacks callbacks = new CursorLoaderCallbacks();
-
- lm.initLoader(LOADER_ID_HIGHLIGHTS, null, callbacks);
- lm.initLoader(LOADER_ID_TOPSITES, null, callbacks);
- }
-
- public void unload() {
- adapter.swapHighlightsCursor(null);
- adapter.swapTopSitesCursor(null);
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
-
- int tiles = (w - tileMargin) / (desiredTileWidth + tileMargin);
-
- if (tiles < MINIMUM_TILES) {
- tiles = MINIMUM_TILES;
-
- setPadding(0, 0, 0, 0);
- } else if (tiles > MAXIMUM_TILES) {
- tiles = MAXIMUM_TILES;
-
- // Use the remaining space as padding
- int needed = tiles * (desiredTileWidth + tileMargin) + tileMargin;
- int padding = (w - needed) / 2;
- w = needed;
-
- setPadding(padding, 0, padding, 0);
- } else {
- setPadding(0, 0, 0, 0);
- }
-
- final float ratio = (float) desiredTilesHeight / (float) desiredTileWidth;
- final int tilesWidth = (w - (tiles * tileMargin) - tileMargin) / tiles;
- final int tilesHeight = (int) (ratio * tilesWidth);
-
- adapter.setTileSize(tiles, tilesWidth, tilesHeight);
- }
-
- private class CursorLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
- @Override
- public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- final Context context = getContext();
- if (id == LOADER_ID_HIGHLIGHTS) {
- return BrowserDB.from(context).getHighlights(context, 10);
- } else if (id == LOADER_ID_TOPSITES) {
- return BrowserDB.from(context).getActivityStreamTopSites(
- context, TopSitesPagerAdapter.PAGES * MAXIMUM_TILES);
- } else {
- throw new IllegalArgumentException("Can't handle loader id " + id);
- }
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- if (loader.getId() == LOADER_ID_HIGHLIGHTS) {
- adapter.swapHighlightsCursor(data);
- } else if (loader.getId() == LOADER_ID_TOPSITES) {
- adapter.swapTopSitesCursor(data);
- }
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- if (loader.getId() == LOADER_ID_HIGHLIGHTS) {
- adapter.swapHighlightsCursor(null);
- } else if (loader.getId() == LOADER_ID_TOPSITES) {
- adapter.swapTopSitesCursor(null);
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStreamHomeFragment.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStreamHomeFragment.java
deleted file mode 100644
index 09f6705d7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStreamHomeFragment.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream;
-
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.home.HomeFragment;
-
-/**
- * Simple wrapper around the ActivityStream view that allows embedding as a HomePager panel.
- */
-public class ActivityStreamHomeFragment
- extends HomeFragment {
- private ActivityStream activityStream;
-
- @Override
- protected void load() {
- activityStream.load(getLoaderManager());
- }
-
- @Nullable
- @Override
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- if (activityStream == null) {
- activityStream = (ActivityStream) inflater.inflate(R.layout.activity_stream, container, false);
- activityStream.setOnUrlOpenListeners(mUrlOpenListener, mUrlOpenInBackgroundListener);
- }
-
- return activityStream;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStreamHomeScreen.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStreamHomeScreen.java
deleted file mode 100644
index 4decc8218..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/ActivityStreamHomeScreen.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.LoaderManager;
-import android.util.AttributeSet;
-
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.home.HomeBanner;
-import org.mozilla.gecko.home.HomeFragment;
-import org.mozilla.gecko.home.HomeScreen;
-
-/**
- * HomeScreen implementation that displays ActivityStream.
- */
-public class ActivityStreamHomeScreen
- extends ActivityStream
- implements HomeScreen {
-
- private boolean visible = false;
-
- public ActivityStreamHomeScreen(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public boolean isVisible() {
- return visible;
- }
-
- @Override
- public void onToolbarFocusChange(boolean hasFocus) {
-
- }
-
- @Override
- public void showPanel(String panelId, Bundle restoreData) {
-
- }
-
- @Override
- public void setOnPanelChangeListener(OnPanelChangeListener listener) {
-
- }
-
- @Override
- public void setPanelStateChangeListener(HomeFragment.PanelStateChangeListener listener) {
-
- }
-
- @Override
- public void setBanner(HomeBanner banner) {
-
- }
-
- @Override
- public void load(LoaderManager lm, FragmentManager fm, String panelId, Bundle restoreData,
- PropertyAnimator animator) {
- super.load(lm);
- visible = true;
- }
-
- @Override
- public void unload() {
- super.unload();
- visible = false;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java
deleted file mode 100644
index 24348dfe0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamItem.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream;
-
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Color;
-import android.support.v4.view.ViewPager;
-import android.support.v7.widget.RecyclerView;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.activitystream.ActivityStream.LabelCallback;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.home.HomePager;
-import org.mozilla.gecko.home.activitystream.menu.ActivityStreamContextMenu;
-import org.mozilla.gecko.home.activitystream.topsites.CirclePageIndicator;
-import org.mozilla.gecko.home.activitystream.topsites.TopSitesPagerAdapter;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.util.DrawableUtil;
-import org.mozilla.gecko.util.ViewUtil;
-import org.mozilla.gecko.util.TouchTargetUtil;
-import org.mozilla.gecko.widget.FaviconView;
-
-import java.util.concurrent.Future;
-
-import static org.mozilla.gecko.activitystream.ActivityStream.extractLabel;
-
-public abstract class StreamItem extends RecyclerView.ViewHolder {
- public StreamItem(View itemView) {
- super(itemView);
- }
-
- public static class HighlightsTitle extends StreamItem {
- public static final int LAYOUT_ID = R.layout.activity_stream_main_highlightstitle;
-
- public HighlightsTitle(View itemView) {
- super(itemView);
- }
- }
-
- public static class TopPanel extends StreamItem {
- public static final int LAYOUT_ID = R.layout.activity_stream_main_toppanel;
-
- private final ViewPager topSitesPager;
-
- public TopPanel(View itemView, HomePager.OnUrlOpenListener onUrlOpenListener, HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
- super(itemView);
-
- topSitesPager = (ViewPager) itemView.findViewById(R.id.topsites_pager);
- topSitesPager.setAdapter(new TopSitesPagerAdapter(itemView.getContext(), onUrlOpenListener, onUrlOpenInBackgroundListener));
-
- CirclePageIndicator indicator = (CirclePageIndicator) itemView.findViewById(R.id.topsites_indicator);
- indicator.setViewPager(topSitesPager);
- }
-
- public void bind(Cursor cursor, int tiles, int tilesWidth, int tilesHeight) {
- final TopSitesPagerAdapter adapter = (TopSitesPagerAdapter) topSitesPager.getAdapter();
- adapter.setTilesSize(tiles, tilesWidth, tilesHeight);
- adapter.swapCursor(cursor);
-
- final Resources resources = itemView.getResources();
- final int tilesMargin = resources.getDimensionPixelSize(R.dimen.activity_stream_base_margin);
- final int textHeight = resources.getDimensionPixelSize(R.dimen.activity_stream_top_sites_text_height);
-
- ViewGroup.LayoutParams layoutParams = topSitesPager.getLayoutParams();
- layoutParams.height = tilesHeight + tilesMargin + textHeight;
- topSitesPager.setLayoutParams(layoutParams);
- }
- }
-
- public static class HighlightItem extends StreamItem implements IconCallback {
- public static final int LAYOUT_ID = R.layout.activity_stream_card_history_item;
-
- String title;
- String url;
-
- final FaviconView vIconView;
- final TextView vLabel;
- final TextView vTimeSince;
- final TextView vSourceView;
- final TextView vPageView;
- final ImageView vSourceIconView;
-
- private Future<IconResponse> ongoingIconLoad;
- private int tilesMargin;
-
- public HighlightItem(final View itemView,
- final HomePager.OnUrlOpenListener onUrlOpenListener,
- final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
- super(itemView);
-
- tilesMargin = itemView.getResources().getDimensionPixelSize(R.dimen.activity_stream_base_margin);
-
- vLabel = (TextView) itemView.findViewById(R.id.card_history_label);
- vTimeSince = (TextView) itemView.findViewById(R.id.card_history_time_since);
- vIconView = (FaviconView) itemView.findViewById(R.id.icon);
- vSourceView = (TextView) itemView.findViewById(R.id.card_history_source);
- vPageView = (TextView) itemView.findViewById(R.id.page);
- vSourceIconView = (ImageView) itemView.findViewById(R.id.source_icon);
-
- final ImageView menuButton = (ImageView) itemView.findViewById(R.id.menu);
-
- menuButton.setImageDrawable(
- DrawableUtil.tintDrawable(menuButton.getContext(), R.drawable.menu, Color.LTGRAY));
-
- TouchTargetUtil.ensureTargetHitArea(menuButton, itemView);
-
- menuButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- ActivityStreamContextMenu.show(v.getContext(),
- menuButton,
- ActivityStreamContextMenu.MenuMode.HIGHLIGHT,
- title, url, onUrlOpenListener, onUrlOpenInBackgroundListener,
- vIconView.getWidth(), vIconView.getHeight());
- }
- });
-
- ViewUtil.enableTouchRipple(menuButton);
- }
-
- public void bind(Cursor cursor, int tilesWidth, int tilesHeight) {
-
- final long time = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Highlights.DATE));
- final String ago = DateUtils.getRelativeTimeSpanString(time, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, 0).toString();
-
- title = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.History.TITLE));
- url = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
-
- vLabel.setText(title);
- vTimeSince.setText(ago);
-
- ViewGroup.LayoutParams layoutParams = vIconView.getLayoutParams();
- layoutParams.width = tilesWidth - tilesMargin;
- layoutParams.height = tilesHeight;
- vIconView.setLayoutParams(layoutParams);
-
- updateSource(cursor);
- updatePage(url);
-
- if (ongoingIconLoad != null) {
- ongoingIconLoad.cancel(true);
- }
-
- ongoingIconLoad = Icons.with(itemView.getContext())
- .pageUrl(url)
- .skipNetwork()
- .build()
- .execute(this);
- }
-
- private void updateSource(final Cursor cursor) {
- final boolean isBookmark = -1 != cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Combined.BOOKMARK_ID));
- final boolean isHistory = -1 != cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Combined.HISTORY_ID));
-
- if (isBookmark) {
- vSourceView.setText(R.string.activity_stream_highlight_label_bookmarked);
- vSourceView.setVisibility(View.VISIBLE);
- vSourceIconView.setImageResource(R.drawable.ic_as_bookmarked);
- } else if (isHistory) {
- vSourceView.setText(R.string.activity_stream_highlight_label_visited);
- vSourceView.setVisibility(View.VISIBLE);
- vSourceIconView.setImageResource(R.drawable.ic_as_visited);
- } else {
- vSourceView.setVisibility(View.INVISIBLE);
- vSourceIconView.setImageResource(0);
- }
-
- vSourceView.setText(vSourceView.getText());
- }
-
- private void updatePage(final String url) {
- extractLabel(itemView.getContext(), url, false, new LabelCallback() {
- @Override
- public void onLabelExtracted(String label) {
- vPageView.setText(TextUtils.isEmpty(label) ? url : label);
- }
- });
- }
-
- @Override
- public void onIconResponse(IconResponse response) {
- vIconView.updateImage(response);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamRecyclerAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamRecyclerAdapter.java
deleted file mode 100644
index f7cda2e7f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/StreamRecyclerAdapter.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home.activitystream;
-
-import android.database.Cursor;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.home.HomePager;
-import org.mozilla.gecko.home.activitystream.StreamItem.HighlightItem;
-import org.mozilla.gecko.home.activitystream.StreamItem.TopPanel;
-import org.mozilla.gecko.widget.RecyclerViewClickSupport;
-
-import java.util.EnumSet;
-
-public class StreamRecyclerAdapter extends RecyclerView.Adapter<StreamItem> implements RecyclerViewClickSupport.OnItemClickListener {
- private Cursor highlightsCursor;
- private Cursor topSitesCursor;
-
- private HomePager.OnUrlOpenListener onUrlOpenListener;
- private HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener;
-
- private int tiles;
- private int tilesWidth;
- private int tilesHeight;
-
- void setOnUrlOpenListeners(HomePager.OnUrlOpenListener onUrlOpenListener, HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
- this.onUrlOpenListener = onUrlOpenListener;
- this.onUrlOpenInBackgroundListener = onUrlOpenInBackgroundListener;
- }
-
- public void setTileSize(int tiles, int tilesWidth, int tilesHeight) {
- this.tilesWidth = tilesWidth;
- this.tilesHeight = tilesHeight;
- this.tiles = tiles;
-
- notifyDataSetChanged();
- }
-
- @Override
- public int getItemViewType(int position) {
- if (position == 0) {
- return TopPanel.LAYOUT_ID;
- } else if (position == 1) {
- return StreamItem.HighlightsTitle.LAYOUT_ID;
- } else {
- return HighlightItem.LAYOUT_ID;
- }
- }
-
- @Override
- public StreamItem onCreateViewHolder(ViewGroup parent, final int type) {
- final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
-
- if (type == TopPanel.LAYOUT_ID) {
- return new TopPanel(inflater.inflate(type, parent, false), onUrlOpenListener, onUrlOpenInBackgroundListener);
- } else if (type == StreamItem.HighlightsTitle.LAYOUT_ID) {
- return new StreamItem.HighlightsTitle(inflater.inflate(type, parent, false));
- } else if (type == HighlightItem.LAYOUT_ID) {
- return new HighlightItem(inflater.inflate(type, parent, false), onUrlOpenListener, onUrlOpenInBackgroundListener);
- } else {
- throw new IllegalStateException("Missing inflation for ViewType " + type);
- }
- }
-
- private int translatePositionToCursor(int position) {
- if (position == 0) {
- throw new IllegalArgumentException("Requested cursor position for invalid item");
- }
-
- // We have two blank panels at the top, hence remove that to obtain the cursor position
- return position - 2;
- }
-
- @Override
- public void onBindViewHolder(StreamItem holder, int position) {
- int type = getItemViewType(position);
-
- if (type == HighlightItem.LAYOUT_ID) {
- final int cursorPosition = translatePositionToCursor(position);
-
- highlightsCursor.moveToPosition(cursorPosition);
- ((HighlightItem) holder).bind(highlightsCursor, tilesWidth, tilesHeight);
- } else if (type == TopPanel.LAYOUT_ID) {
- ((TopPanel) holder).bind(topSitesCursor, tiles, tilesWidth, tilesHeight);
- }
- }
-
- @Override
- public void onItemClicked(RecyclerView recyclerView, int position, View v) {
- if (position < 1) {
- // The header contains top sites and has its own click handling.
- return;
- }
-
- highlightsCursor.moveToPosition(
- translatePositionToCursor(position));
-
- final String url = highlightsCursor.getString(
- highlightsCursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
-
- onUrlOpenListener.onUrlOpen(url, EnumSet.of(HomePager.OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
- }
-
- @Override
- public int getItemCount() {
- final int highlightsCount;
-
- if (highlightsCursor != null) {
- highlightsCount = highlightsCursor.getCount();
- } else {
- highlightsCount = 0;
- }
-
- return highlightsCount + 2;
- }
-
- public void swapHighlightsCursor(Cursor cursor) {
- highlightsCursor = cursor;
-
- notifyDataSetChanged();
- }
-
- public void swapTopSitesCursor(Cursor cursor) {
- this.topSitesCursor = cursor;
-
- notifyItemChanged(0);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/ActivityStreamContextMenu.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/ActivityStreamContextMenu.java
deleted file mode 100644
index 525d3b426..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/ActivityStreamContextMenu.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream.menu;
-
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.support.annotation.NonNull;
-import android.support.design.widget.NavigationView;
-import android.view.MenuItem;
-import android.view.View;
-
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.IntentHelper;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.home.HomePager;
-import org.mozilla.gecko.util.Clipboard;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.UIAsyncTask;
-
-import java.util.EnumSet;
-
-@RobocopTarget
-public abstract class ActivityStreamContextMenu
- implements NavigationView.OnNavigationItemSelectedListener {
-
- public enum MenuMode {
- HIGHLIGHT,
- TOPSITE
- }
-
- final Context context;
-
- final String title;
- final String url;
-
- final HomePager.OnUrlOpenListener onUrlOpenListener;
- final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener;
-
- boolean isAlreadyBookmarked; // default false;
-
- public abstract MenuItem getItemByID(int id);
-
- public abstract void show();
-
- public abstract void dismiss();
-
- final MenuMode mode;
-
- /* package-private */ ActivityStreamContextMenu(final Context context,
- final MenuMode mode,
- final String title, @NonNull final String url,
- HomePager.OnUrlOpenListener onUrlOpenListener,
- HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
- this.context = context;
-
- this.mode = mode;
-
- this.title = title;
- this.url = url;
- this.onUrlOpenListener = onUrlOpenListener;
- this.onUrlOpenInBackgroundListener = onUrlOpenInBackgroundListener;
- }
-
- /**
- * Must be called before the menu is shown.
- * <p/>
- * Your implementation must be ready to return items from getItemByID() before postInit() is
- * called, i.e. you should probably inflate your menu items before this call.
- */
- protected void postInit() {
- // Disable "dismiss" for topsites until we have decided on its behaviour for topsites
- // (currently "dismiss" adds the URL to a highlights-specific blocklist, which the topsites
- // query has no knowledge of).
- if (mode == MenuMode.TOPSITE) {
- final MenuItem dismissItem = getItemByID(R.id.dismiss);
- dismissItem.setVisible(false);
- }
-
- // Disable the bookmark item until we know its bookmark state
- final MenuItem bookmarkItem = getItemByID(R.id.bookmark);
- bookmarkItem.setEnabled(false);
-
- (new UIAsyncTask.WithoutParams<Void>(ThreadUtils.getBackgroundHandler()) {
- @Override
- protected Void doInBackground() {
- isAlreadyBookmarked = BrowserDB.from(context).isBookmark(context.getContentResolver(), url);
- return null;
- }
-
- @Override
- protected void onPostExecute(Void aVoid) {
- if (isAlreadyBookmarked) {
- bookmarkItem.setTitle(R.string.bookmark_remove);
- }
-
- bookmarkItem.setEnabled(true);
- }
- }).execute();
-
- // Only show the "remove from history" item if a page actually has history
- final MenuItem deleteHistoryItem = getItemByID(R.id.delete);
- deleteHistoryItem.setVisible(false);
-
- (new UIAsyncTask.WithoutParams<Void>(ThreadUtils.getBackgroundHandler()) {
- boolean hasHistory;
-
- @Override
- protected Void doInBackground() {
- final Cursor cursor = BrowserDB.from(context).getHistoryForURL(context.getContentResolver(), url);
- try {
- if (cursor != null &&
- cursor.getCount() == 1) {
- hasHistory = true;
- } else {
- hasHistory = false;
- }
- } finally {
- cursor.close();
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Void aVoid) {
- if (hasHistory) {
- deleteHistoryItem.setVisible(true);
- }
- }
- }).execute();
- }
-
-
- @Override
- public boolean onNavigationItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.share:
- Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "menu");
- IntentHelper.openUriExternal(url, "text/plain", "", "", Intent.ACTION_SEND, title, false);
- break;
-
- case R.id.bookmark:
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- final BrowserDB db = BrowserDB.from(context);
-
- if (isAlreadyBookmarked) {
- db.removeBookmarksWithURL(context.getContentResolver(), url);
- } else {
- db.addBookmark(context.getContentResolver(), title, url);
- }
-
- }
- });
- break;
-
- case R.id.copy_url:
- Clipboard.setText(url);
- break;
-
- case R.id.add_homescreen:
- GeckoAppShell.createShortcut(title, url);
- break;
-
- case R.id.open_new_tab:
- onUrlOpenInBackgroundListener.onUrlOpenInBackground(url, EnumSet.noneOf(HomePager.OnUrlOpenInBackgroundListener.Flags.class));
- break;
-
- case R.id.open_new_private_tab:
- onUrlOpenInBackgroundListener.onUrlOpenInBackground(url, EnumSet.of(HomePager.OnUrlOpenInBackgroundListener.Flags.PRIVATE));
- break;
-
- case R.id.dismiss:
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- BrowserDB.from(context)
- .blockActivityStreamSite(context.getContentResolver(),
- url);
- }
- });
- break;
-
- case R.id.delete:
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- BrowserDB.from(context)
- .removeHistoryEntry(context.getContentResolver(),
- url);
- }
- });
- break;
-
- default:
- throw new IllegalArgumentException("Menu item with ID=" + item.getItemId() + " not handled");
- }
-
- dismiss();
- return true;
- }
-
-
- @RobocopTarget
- public static ActivityStreamContextMenu show(Context context,
- View anchor,
- final MenuMode menuMode,
- final String title, @NonNull final String url,
- HomePager.OnUrlOpenListener onUrlOpenListener,
- HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener,
- final int tilesWidth, final int tilesHeight) {
- final ActivityStreamContextMenu menu;
-
- if (!HardwareUtils.isTablet()) {
- menu = new BottomSheetContextMenu(context,
- menuMode,
- title, url,
- onUrlOpenListener, onUrlOpenInBackgroundListener,
- tilesWidth, tilesHeight);
- } else {
- menu = new PopupContextMenu(context,
- anchor,
- menuMode,
- title, url,
- onUrlOpenListener, onUrlOpenInBackgroundListener);
- }
-
- menu.show();
- return menu;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/BottomSheetContextMenu.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/BottomSheetContextMenu.java
deleted file mode 100644
index e95867c36..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/BottomSheetContextMenu.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream.menu;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.design.widget.BottomSheetBehavior;
-import android.support.design.widget.BottomSheetDialog;
-import android.support.design.widget.NavigationView;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.activitystream.ActivityStream;
-import org.mozilla.gecko.home.HomePager;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.widget.FaviconView;
-
-import static org.mozilla.gecko.activitystream.ActivityStream.extractLabel;
-
-/* package-private */ class BottomSheetContextMenu
- extends ActivityStreamContextMenu {
-
-
- private final BottomSheetDialog bottomSheetDialog;
-
- private final NavigationView navigationView;
-
- public BottomSheetContextMenu(final Context context,
- final MenuMode mode,
- final String title, @NonNull final String url,
- HomePager.OnUrlOpenListener onUrlOpenListener,
- HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener,
- final int tilesWidth, final int tilesHeight) {
-
- super(context,
- mode,
- title,
- url,
- onUrlOpenListener,
- onUrlOpenInBackgroundListener);
-
- final LayoutInflater inflater = LayoutInflater.from(context);
- final View content = inflater.inflate(R.layout.activity_stream_contextmenu_bottomsheet, null);
-
- bottomSheetDialog = new BottomSheetDialog(context);
- bottomSheetDialog.setContentView(content);
-
- ((TextView) content.findViewById(R.id.title)).setText(title);
-
- extractLabel(context, url, false, new ActivityStream.LabelCallback() {
- public void onLabelExtracted(String label) {
- ((TextView) content.findViewById(R.id.url)).setText(label);
- }
- });
-
- // Copy layouted parameters from the Highlights / TopSites items to ensure consistency
- final FaviconView faviconView = (FaviconView) content.findViewById(R.id.icon);
- ViewGroup.LayoutParams layoutParams = faviconView.getLayoutParams();
- layoutParams.width = tilesWidth;
- layoutParams.height = tilesHeight;
- faviconView.setLayoutParams(layoutParams);
-
- Icons.with(context)
- .pageUrl(url)
- .skipNetwork()
- .build()
- .execute(new IconCallback() {
- @Override
- public void onIconResponse(IconResponse response) {
- faviconView.updateImage(response);
- }
- });
-
- navigationView = (NavigationView) content.findViewById(R.id.menu);
- navigationView.setNavigationItemSelectedListener(this);
-
- super.postInit();
- }
-
- @Override
- public MenuItem getItemByID(int id) {
- return navigationView.getMenu().findItem(id);
- }
-
- @Override
- public void show() {
- bottomSheetDialog.show();
- }
-
- public void dismiss() {
- bottomSheetDialog.dismiss();
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/PopupContextMenu.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/PopupContextMenu.java
deleted file mode 100644
index 56615937b..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/PopupContextMenu.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream.menu;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.support.annotation.NonNull;
-import android.support.design.widget.NavigationView;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.PopupWindow;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.home.HomePager;
-
-/* package-private */ class PopupContextMenu
- extends ActivityStreamContextMenu {
-
- private final PopupWindow popupWindow;
- private final NavigationView navigationView;
-
- private final View anchor;
-
- public PopupContextMenu(final Context context,
- View anchor,
- final MenuMode mode,
- final String title,
- @NonNull final String url,
- HomePager.OnUrlOpenListener onUrlOpenListener,
- HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
- super(context,
- mode,
- title,
- url,
- onUrlOpenListener,
- onUrlOpenInBackgroundListener);
-
- this.anchor = anchor;
-
- final LayoutInflater inflater = LayoutInflater.from(context);
-
- View card = inflater.inflate(R.layout.activity_stream_contextmenu_popupmenu, null);
- navigationView = (NavigationView) card.findViewById(R.id.menu);
- navigationView.setNavigationItemSelectedListener(this);
-
- popupWindow = new PopupWindow(context);
- popupWindow.setContentView(card);
- popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
- popupWindow.setFocusable(true);
-
- super.postInit();
- }
-
- @Override
- public MenuItem getItemByID(int id) {
- return navigationView.getMenu().findItem(id);
- }
-
- @Override
- public void show() {
- // By default popupWindow follows the pre-material convention of displaying the popup
- // below a View. We need to shift it over the view:
- popupWindow.showAsDropDown(anchor,
- 0,
- -(anchor.getHeight() + anchor.getPaddingBottom()));
- }
-
- public void dismiss() {
- popupWindow.dismiss();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/CirclePageIndicator.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/CirclePageIndicator.java
deleted file mode 100644
index 096f0c597..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/CirclePageIndicator.java
+++ /dev/null
@@ -1,568 +0,0 @@
-/*
- * Copyright (C) 2011 Patrik Akerfeldt
- * Copyright (C) 2011 Jake Wharton
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.mozilla.gecko.home.activitystream.topsites;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
-import android.graphics.drawable.Drawable;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.support.v4.view.MotionEventCompat;
-import android.support.v4.view.ViewConfigurationCompat;
-import android.support.v4.view.ViewPager;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-import org.mozilla.gecko.R;
-
-import static android.graphics.Paint.ANTI_ALIAS_FLAG;
-import static android.widget.LinearLayout.HORIZONTAL;
-import static android.widget.LinearLayout.VERTICAL;
-
-/**
- * Draws circles (one for each view). The current view position is filled and
- * others are only stroked.
- *
- * This file was imported from Jake Wharton's ViewPagerIndicator library:
- * https://github.com/JakeWharton/ViewPagerIndicator
- * It was modified to not extend the PageIndicator interface (as we only use one single Indicator)
- * implementation, and has had some minor appearance related modifications added alter.
- */
-public class CirclePageIndicator
- extends View
- implements ViewPager.OnPageChangeListener {
-
- /**
- * Separation between circles, as a factor of the circle radius. By default CirclePageIndicator
- * shipped with a separation factor of 3, however we want to be able to tweak this for
- * ActivityStream.
- *
- * If/when we reuse this indicator elsewhere, this should probably become a configurable property.
- */
- private static final int SEPARATION_FACTOR = 7;
-
- private static final int INVALID_POINTER = -1;
-
- private float mRadius;
- private final Paint mPaintPageFill = new Paint(ANTI_ALIAS_FLAG);
- private final Paint mPaintStroke = new Paint(ANTI_ALIAS_FLAG);
- private final Paint mPaintFill = new Paint(ANTI_ALIAS_FLAG);
- private ViewPager mViewPager;
- private ViewPager.OnPageChangeListener mListener;
- private int mCurrentPage;
- private int mSnapPage;
- private float mPageOffset;
- private int mScrollState;
- private int mOrientation;
- private boolean mCentered;
- private boolean mSnap;
-
- private int mTouchSlop;
- private float mLastMotionX = -1;
- private int mActivePointerId = INVALID_POINTER;
- private boolean mIsDragging;
-
-
- public CirclePageIndicator(Context context) {
- this(context, null);
- }
-
- public CirclePageIndicator(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.vpiCirclePageIndicatorStyle);
- }
-
- public CirclePageIndicator(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- if (isInEditMode()) return;
-
- //Load defaults from resources
- final Resources res = getResources();
- final int defaultPageColor = res.getColor(R.color.default_circle_indicator_page_color);
- final int defaultFillColor = res.getColor(R.color.default_circle_indicator_fill_color);
- final int defaultOrientation = res.getInteger(R.integer.default_circle_indicator_orientation);
- final int defaultStrokeColor = res.getColor(R.color.default_circle_indicator_stroke_color);
- final float defaultStrokeWidth = res.getDimension(R.dimen.default_circle_indicator_stroke_width);
- final float defaultRadius = res.getDimension(R.dimen.default_circle_indicator_radius);
- final boolean defaultCentered = res.getBoolean(R.bool.default_circle_indicator_centered);
- final boolean defaultSnap = res.getBoolean(R.bool.default_circle_indicator_snap);
-
- //Retrieve styles attributes
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CirclePageIndicator, defStyle, 0);
-
- mCentered = a.getBoolean(R.styleable.CirclePageIndicator_centered, defaultCentered);
- mOrientation = a.getInt(R.styleable.CirclePageIndicator_android_orientation, defaultOrientation);
- mPaintPageFill.setStyle(Style.FILL);
- mPaintPageFill.setColor(a.getColor(R.styleable.CirclePageIndicator_pageColor, defaultPageColor));
- mPaintStroke.setStyle(Style.STROKE);
- mPaintStroke.setColor(a.getColor(R.styleable.CirclePageIndicator_strokeColor, defaultStrokeColor));
- mPaintStroke.setStrokeWidth(a.getDimension(R.styleable.CirclePageIndicator_strokeWidth, defaultStrokeWidth));
- mPaintFill.setStyle(Style.FILL);
- mPaintFill.setColor(a.getColor(R.styleable.CirclePageIndicator_fillColor, defaultFillColor));
- mRadius = a.getDimension(R.styleable.CirclePageIndicator_radius, defaultRadius);
- mSnap = a.getBoolean(R.styleable.CirclePageIndicator_snap, defaultSnap);
-
- Drawable background = a.getDrawable(R.styleable.CirclePageIndicator_android_background);
- if (background != null) {
- setBackgroundDrawable(background);
- }
-
- a.recycle();
-
- final ViewConfiguration configuration = ViewConfiguration.get(context);
- mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
- }
-
-
- public void setCentered(boolean centered) {
- mCentered = centered;
- invalidate();
- }
-
- public boolean isCentered() {
- return mCentered;
- }
-
- public void setPageColor(int pageColor) {
- mPaintPageFill.setColor(pageColor);
- invalidate();
- }
-
- public int getPageColor() {
- return mPaintPageFill.getColor();
- }
-
- public void setFillColor(int fillColor) {
- mPaintFill.setColor(fillColor);
- invalidate();
- }
-
- public int getFillColor() {
- return mPaintFill.getColor();
- }
-
- public void setOrientation(int orientation) {
- switch (orientation) {
- case HORIZONTAL:
- case VERTICAL:
- mOrientation = orientation;
- requestLayout();
- break;
-
- default:
- throw new IllegalArgumentException("Orientation must be either HORIZONTAL or VERTICAL.");
- }
- }
-
- public int getOrientation() {
- return mOrientation;
- }
-
- public void setStrokeColor(int strokeColor) {
- mPaintStroke.setColor(strokeColor);
- invalidate();
- }
-
- public int getStrokeColor() {
- return mPaintStroke.getColor();
- }
-
- public void setStrokeWidth(float strokeWidth) {
- mPaintStroke.setStrokeWidth(strokeWidth);
- invalidate();
- }
-
- public float getStrokeWidth() {
- return mPaintStroke.getStrokeWidth();
- }
-
- public void setRadius(float radius) {
- mRadius = radius;
- invalidate();
- }
-
- public float getRadius() {
- return mRadius;
- }
-
- public void setSnap(boolean snap) {
- mSnap = snap;
- invalidate();
- }
-
- public boolean isSnap() {
- return mSnap;
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- if (mViewPager == null) {
- return;
- }
- final int count = mViewPager.getAdapter().getCount();
- if (count == 0) {
- return;
- }
-
- if (mCurrentPage >= count) {
- setCurrentItem(count - 1);
- return;
- }
-
- int longSize;
- int longPaddingBefore;
- int longPaddingAfter;
- int shortPaddingBefore;
- if (mOrientation == HORIZONTAL) {
- longSize = getWidth();
- longPaddingBefore = getPaddingLeft();
- longPaddingAfter = getPaddingRight();
- shortPaddingBefore = getPaddingTop();
- } else {
- longSize = getHeight();
- longPaddingBefore = getPaddingTop();
- longPaddingAfter = getPaddingBottom();
- shortPaddingBefore = getPaddingLeft();
- }
-
- final float threeRadius = mRadius * SEPARATION_FACTOR;
- final float shortOffset = shortPaddingBefore + mRadius;
- float longOffset = longPaddingBefore + mRadius;
- if (mCentered) {
- longOffset += ((longSize - longPaddingBefore - longPaddingAfter) / 2.0f) - ((count * threeRadius) / 2.0f);
- }
-
- float dX;
- float dY;
-
- float pageFillRadius = mRadius;
- if (mPaintStroke.getStrokeWidth() > 0) {
- pageFillRadius -= mPaintStroke.getStrokeWidth() / 2.0f;
- }
-
- //Draw stroked circles
- for (int iLoop = 0; iLoop < count; iLoop++) {
- float drawLong = longOffset + (iLoop * threeRadius);
- if (mOrientation == HORIZONTAL) {
- dX = drawLong;
- dY = shortOffset;
- } else {
- dX = shortOffset;
- dY = drawLong;
- }
- // Only paint fill if not completely transparent
- if (mPaintPageFill.getAlpha() > 0) {
- canvas.drawCircle(dX, dY, pageFillRadius, mPaintPageFill);
- }
-
- // Only paint stroke if a stroke width was non-zero
- if (pageFillRadius != mRadius) {
- canvas.drawCircle(dX, dY, mRadius, mPaintStroke);
- }
- }
-
- //Draw the filled circle according to the current scroll
- float cx = (mSnap ? mSnapPage : mCurrentPage) * threeRadius;
- if (!mSnap) {
- cx += mPageOffset * threeRadius;
- }
- if (mOrientation == HORIZONTAL) {
- dX = longOffset + cx;
- dY = shortOffset;
- } else {
- dX = shortOffset;
- dY = longOffset + cx;
- }
- canvas.drawCircle(dX, dY, mRadius, mPaintFill);
- }
-
- public boolean onTouchEvent(android.view.MotionEvent ev) {
- if (super.onTouchEvent(ev)) {
- return true;
- }
- if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) {
- return false;
- }
-
- final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
- mLastMotionX = ev.getX();
- break;
-
- case MotionEvent.ACTION_MOVE: {
- final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
- final float x = MotionEventCompat.getX(ev, activePointerIndex);
- final float deltaX = x - mLastMotionX;
-
- if (!mIsDragging) {
- if (Math.abs(deltaX) > mTouchSlop) {
- mIsDragging = true;
- }
- }
-
- if (mIsDragging) {
- mLastMotionX = x;
- if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) {
- mViewPager.fakeDragBy(deltaX);
- }
- }
-
- break;
- }
-
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- if (!mIsDragging) {
- final int count = mViewPager.getAdapter().getCount();
- final int width = getWidth();
- final float halfWidth = width / 2f;
- final float sixthWidth = width / 6f;
-
- if ((mCurrentPage > 0) && (ev.getX() < halfWidth - sixthWidth)) {
- if (action != MotionEvent.ACTION_CANCEL) {
- mViewPager.setCurrentItem(mCurrentPage - 1);
- }
- return true;
- } else if ((mCurrentPage < count - 1) && (ev.getX() > halfWidth + sixthWidth)) {
- if (action != MotionEvent.ACTION_CANCEL) {
- mViewPager.setCurrentItem(mCurrentPage + 1);
- }
- return true;
- }
- }
-
- mIsDragging = false;
- mActivePointerId = INVALID_POINTER;
- if (mViewPager.isFakeDragging()) mViewPager.endFakeDrag();
- break;
-
- case MotionEventCompat.ACTION_POINTER_DOWN: {
- final int index = MotionEventCompat.getActionIndex(ev);
- mLastMotionX = MotionEventCompat.getX(ev, index);
- mActivePointerId = MotionEventCompat.getPointerId(ev, index);
- break;
- }
-
- case MotionEventCompat.ACTION_POINTER_UP:
- final int pointerIndex = MotionEventCompat.getActionIndex(ev);
- final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
- if (pointerId == mActivePointerId) {
- final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
- mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
- }
- mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId));
- break;
- }
-
- return true;
- }
-
- public void setViewPager(ViewPager view) {
- if (mViewPager == view) {
- return;
- }
- if (mViewPager != null) {
- mViewPager.setOnPageChangeListener(null);
- }
- if (view.getAdapter() == null) {
- throw new IllegalStateException("ViewPager does not have adapter instance.");
- }
- mViewPager = view;
- mViewPager.setOnPageChangeListener(this);
- invalidate();
- }
-
- public void setViewPager(ViewPager view, int initialPosition) {
- setViewPager(view);
- setCurrentItem(initialPosition);
- }
-
- public void setCurrentItem(int item) {
- if (mViewPager == null) {
- throw new IllegalStateException("ViewPager has not been bound.");
- }
- mViewPager.setCurrentItem(item);
- mCurrentPage = item;
- invalidate();
- }
-
- public void notifyDataSetChanged() {
- invalidate();
- }
-
- @Override
- public void onPageScrollStateChanged(int state) {
- mScrollState = state;
-
- if (mListener != null) {
- mListener.onPageScrollStateChanged(state);
- }
- }
-
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- mCurrentPage = position;
- mPageOffset = positionOffset;
- invalidate();
-
- if (mListener != null) {
- mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
- }
- }
-
- @Override
- public void onPageSelected(int position) {
- if (mSnap || mScrollState == ViewPager.SCROLL_STATE_IDLE) {
- mCurrentPage = position;
- mSnapPage = position;
- invalidate();
- }
-
- if (mListener != null) {
- mListener.onPageSelected(position);
- }
- }
-
- public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
- mListener = listener;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see android.view.View#onMeasure(int, int)
- */
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (mOrientation == HORIZONTAL) {
- setMeasuredDimension(measureLong(widthMeasureSpec), measureShort(heightMeasureSpec));
- } else {
- setMeasuredDimension(measureShort(widthMeasureSpec), measureLong(heightMeasureSpec));
- }
- }
-
- /**
- * Determines the width of this view
- *
- * @param measureSpec
- * A measureSpec packed into an int
- * @return The width of the view, honoring constraints from measureSpec
- */
- private int measureLong(int measureSpec) {
- int result;
- int specMode = MeasureSpec.getMode(measureSpec);
- int specSize = MeasureSpec.getSize(measureSpec);
-
- if ((specMode == MeasureSpec.EXACTLY) || (mViewPager == null)) {
- //We were told how big to be
- result = specSize;
- } else {
- //Calculate the width according the views count
- final int count = mViewPager.getAdapter().getCount();
- result = (int)(getPaddingLeft() + getPaddingRight()
- + (count * 2 * mRadius) + (count - 1) * mRadius + 1);
- //Respect AT_MOST value if that was what is called for by measureSpec
- if (specMode == MeasureSpec.AT_MOST) {
- result = Math.min(result, specSize);
- }
- }
- return result;
- }
-
- /**
- * Determines the height of this view
- *
- * @param measureSpec
- * A measureSpec packed into an int
- * @return The height of the view, honoring constraints from measureSpec
- */
- private int measureShort(int measureSpec) {
- int result;
- int specMode = MeasureSpec.getMode(measureSpec);
- int specSize = MeasureSpec.getSize(measureSpec);
-
- if (specMode == MeasureSpec.EXACTLY) {
- //We were told how big to be
- result = specSize;
- } else {
- //Measure the height
- result = (int)(2 * mRadius + getPaddingTop() + getPaddingBottom() + 1);
- //Respect AT_MOST value if that was what is called for by measureSpec
- if (specMode == MeasureSpec.AT_MOST) {
- result = Math.min(result, specSize);
- }
- }
- return result;
- }
-
- @Override
- public void onRestoreInstanceState(Parcelable state) {
- SavedState savedState = (SavedState)state;
- super.onRestoreInstanceState(savedState.getSuperState());
- mCurrentPage = savedState.currentPage;
- mSnapPage = savedState.currentPage;
- requestLayout();
- }
-
- @Override
- public Parcelable onSaveInstanceState() {
- Parcelable superState = super.onSaveInstanceState();
- SavedState savedState = new SavedState(superState);
- savedState.currentPage = mCurrentPage;
- return savedState;
- }
-
- static class SavedState extends BaseSavedState {
- int currentPage;
-
- public SavedState(Parcelable superState) {
- super(superState);
- }
-
- private SavedState(Parcel in) {
- super(in);
- currentPage = in.readInt();
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- dest.writeInt(currentPage);
- }
-
- @SuppressWarnings("UnusedDeclaration")
- public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
- @Override
- public SavedState createFromParcel(Parcel in) {
- return new SavedState(in);
- }
-
- @Override
- public SavedState[] newArray(int size) {
- return new SavedState[size];
- }
- };
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesCard.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesCard.java
deleted file mode 100644
index b436a466f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesCard.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream.topsites;
-
-import android.graphics.Color;
-import android.support.v7.widget.RecyclerView;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.activitystream.ActivityStream;
-import org.mozilla.gecko.home.HomePager;
-import org.mozilla.gecko.home.activitystream.menu.ActivityStreamContextMenu;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.util.DrawableUtil;
-import org.mozilla.gecko.util.ViewUtil;
-import org.mozilla.gecko.util.TouchTargetUtil;
-import org.mozilla.gecko.widget.FaviconView;
-
-import java.util.EnumSet;
-import java.util.concurrent.Future;
-
-class TopSitesCard extends RecyclerView.ViewHolder
- implements IconCallback, View.OnClickListener {
- private final FaviconView faviconView;
-
- private final TextView title;
- private final ImageView menuButton;
- private Future<IconResponse> ongoingIconLoad;
-
- private String url;
-
- private final HomePager.OnUrlOpenListener onUrlOpenListener;
- private final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener;
-
- public TopSitesCard(FrameLayout card, final HomePager.OnUrlOpenListener onUrlOpenListener, final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
- super(card);
-
- faviconView = (FaviconView) card.findViewById(R.id.favicon);
-
- title = (TextView) card.findViewById(R.id.title);
- menuButton = (ImageView) card.findViewById(R.id.menu);
-
- this.onUrlOpenListener = onUrlOpenListener;
- this.onUrlOpenInBackgroundListener = onUrlOpenInBackgroundListener;
-
- card.setOnClickListener(this);
-
- TouchTargetUtil.ensureTargetHitArea(menuButton, card);
- menuButton.setOnClickListener(this);
-
- ViewUtil.enableTouchRipple(menuButton);
- }
-
- void bind(final TopSitesPageAdapter.TopSite topSite) {
- ActivityStream.extractLabel(itemView.getContext(), topSite.url, true, new ActivityStream.LabelCallback() {
- @Override
- public void onLabelExtracted(String label) {
- title.setText(label);
- }
- });
-
- this.url = topSite.url;
-
- if (ongoingIconLoad != null) {
- ongoingIconLoad.cancel(true);
- }
-
- ongoingIconLoad = Icons.with(itemView.getContext())
- .pageUrl(topSite.url)
- .skipNetwork()
- .build()
- .execute(this);
- }
-
- @Override
- public void onIconResponse(IconResponse response) {
- faviconView.updateImage(response);
-
- final int tintColor = !response.hasColor() || response.getColor() == Color.WHITE ? Color.LTGRAY : Color.WHITE;
-
- menuButton.setImageDrawable(
- DrawableUtil.tintDrawable(menuButton.getContext(), R.drawable.menu, tintColor));
- }
-
- @Override
- public void onClick(View clickedView) {
- if (clickedView == itemView) {
- onUrlOpenListener.onUrlOpen(url, EnumSet.noneOf(HomePager.OnUrlOpenListener.Flags.class));
- } else if (clickedView == menuButton) {
- ActivityStreamContextMenu.show(clickedView.getContext(),
- menuButton,
- ActivityStreamContextMenu.MenuMode.TOPSITE,
- title.getText().toString(), url,
- onUrlOpenListener, onUrlOpenInBackgroundListener,
- faviconView.getWidth(), faviconView.getHeight());
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPage.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPage.java
deleted file mode 100644
index 45fdc0d1a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPage.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream.topsites;
-
-import android.content.Context;
-import android.support.annotation.Nullable;
-import android.support.v7.widget.GridLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
-import android.view.View;
-
-import org.mozilla.gecko.home.HomePager;
-import org.mozilla.gecko.widget.RecyclerViewClickSupport;
-
-import java.util.EnumSet;
-
-public class TopSitesPage
- extends RecyclerView {
- public TopSitesPage(Context context,
- @Nullable AttributeSet attrs) {
- super(context, attrs);
-
- setLayoutManager(new GridLayoutManager(context, 1));
- }
-
- public void setTiles(int tiles) {
- setLayoutManager(new GridLayoutManager(getContext(), tiles));
- }
-
- private HomePager.OnUrlOpenListener onUrlOpenListener;
- private HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener;
-
- public TopSitesPageAdapter getAdapter() {
- return (TopSitesPageAdapter) super.getAdapter();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPageAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPageAdapter.java
deleted file mode 100644
index 29e6aca3d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPageAdapter.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream.topsites;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.annotation.UiThread;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.home.HomePager;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class TopSitesPageAdapter extends RecyclerView.Adapter<TopSitesCard> {
- static final class TopSite {
- public final long id;
- public final String url;
- public final String title;
-
- TopSite(long id, String url, String title) {
- this.id = id;
- this.url = url;
- this.title = title;
- }
- }
-
- private List<TopSite> topSites;
- private int tiles;
- private int tilesWidth;
- private int tilesHeight;
- private int textHeight;
-
- private final HomePager.OnUrlOpenListener onUrlOpenListener;
- private final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener;
-
- public TopSitesPageAdapter(Context context, int tiles, int tilesWidth, int tilesHeight,
- HomePager.OnUrlOpenListener onUrlOpenListener, HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
- setHasStableIds(true);
-
- this.topSites = new ArrayList<>();
- this.tiles = tiles;
- this.tilesWidth = tilesWidth;
- this.tilesHeight = tilesHeight;
- this.textHeight = context.getResources().getDimensionPixelSize(R.dimen.activity_stream_top_sites_text_height);
-
- this.onUrlOpenListener = onUrlOpenListener;
- this.onUrlOpenInBackgroundListener = onUrlOpenInBackgroundListener;
- }
-
- /**
- *
- * @param cursor
- * @param startIndex The first item that this topsites group should show. This item, and the following
- * 3 items will be displayed by this adapter.
- */
- public void swapCursor(Cursor cursor, int startIndex) {
- topSites.clear();
-
- if (cursor == null) {
- return;
- }
-
- for (int i = 0; i < tiles && startIndex + i < cursor.getCount(); i++) {
- cursor.moveToPosition(startIndex + i);
-
- // The Combined View only contains pages that have been visited at least once, i.e. any
- // page in the TopSites query will contain a HISTORY_ID. _ID however will be 0 for all rows.
- final long id = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Combined.HISTORY_ID));
- final String url = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
- final String title = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.TITLE));
-
- topSites.add(new TopSite(id, url, title));
- }
-
- notifyDataSetChanged();
- }
-
- @Override
- public void onBindViewHolder(TopSitesCard holder, int position) {
- holder.bind(topSites.get(position));
- }
-
- @Override
- public TopSitesCard onCreateViewHolder(ViewGroup parent, int viewType) {
- final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
-
- final FrameLayout card = (FrameLayout) inflater.inflate(R.layout.activity_stream_topsites_card, parent, false);
- final View content = card.findViewById(R.id.content);
-
- ViewGroup.LayoutParams layoutParams = content.getLayoutParams();
- layoutParams.width = tilesWidth;
- layoutParams.height = tilesHeight + textHeight;
- content.setLayoutParams(layoutParams);
-
- return new TopSitesCard(card, onUrlOpenListener, onUrlOpenInBackgroundListener);
- }
-
- @Override
- public int getItemCount() {
- return topSites.size();
- }
-
- @Override
- @UiThread
- public long getItemId(int position) {
- return topSites.get(position).id;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPagerAdapter.java b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPagerAdapter.java
deleted file mode 100644
index dc824d902..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/topsites/TopSitesPagerAdapter.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.home.activitystream.topsites;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v4.view.PagerAdapter;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.home.HomePager;
-
-import java.util.LinkedList;
-
-/**
- * The primary / top-level TopSites adapter: it handles the ViewPager, and also handles
- * all lower-level Adapters that populate the individual topsite items.
- */
-public class TopSitesPagerAdapter extends PagerAdapter {
- public static final int PAGES = 4;
-
- private int tiles;
- private int tilesWidth;
- private int tilesHeight;
-
- private LinkedList<TopSitesPage> pages = new LinkedList<>();
-
- private final Context context;
- private final HomePager.OnUrlOpenListener onUrlOpenListener;
- private final HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener;
-
- private int count = 0;
-
- public TopSitesPagerAdapter(Context context,
- HomePager.OnUrlOpenListener onUrlOpenListener,
- HomePager.OnUrlOpenInBackgroundListener onUrlOpenInBackgroundListener) {
- this.context = context;
- this.onUrlOpenListener = onUrlOpenListener;
- this.onUrlOpenInBackgroundListener = onUrlOpenInBackgroundListener;
- }
-
- public void setTilesSize(int tiles, int tilesWidth, int tilesHeight) {
- this.tilesWidth = tilesWidth;
- this.tilesHeight = tilesHeight;
- this.tiles = tiles;
- }
-
- @Override
- public int getCount() {
- return Math.min(count, 4);
- }
-
- @Override
- public boolean isViewFromObject(View view, Object object) {
- return view == object;
- }
-
- @Override
- public Object instantiateItem(ViewGroup container, int position) {
- TopSitesPage page = pages.get(position);
-
- container.addView(page);
-
- return page;
- }
-
- @Override
- public int getItemPosition(Object object) {
- return PagerAdapter.POSITION_NONE;
- }
-
- @Override
- public void destroyItem(ViewGroup container, int position, Object object) {
- container.removeView((View) object);
- }
-
- public void swapCursor(Cursor cursor) {
- // Divide while rounding up: 0 items = 0 pages, 1-ITEMS_PER_PAGE items = 1 page, etc.
- if (cursor != null) {
- count = (cursor.getCount() - 1) / tiles + 1;
- } else {
- count = 0;
- }
-
- pages.clear();
- final int pageDelta = count;
-
- if (pageDelta > 0) {
- final LayoutInflater inflater = LayoutInflater.from(context);
- for (int i = 0; i < pageDelta; i++) {
- final TopSitesPage page = (TopSitesPage) inflater.inflate(R.layout.activity_stream_topsites_page, null, false);
-
- page.setTiles(tiles);
- final TopSitesPageAdapter adapter = new TopSitesPageAdapter(context, tiles, tilesWidth, tilesHeight,
- onUrlOpenListener, onUrlOpenInBackgroundListener);
- page.setAdapter(adapter);
- pages.add(page);
- }
- } else if (pageDelta < 0) {
- for (int i = 0; i > pageDelta; i--) {
- final TopSitesPage page = pages.getLast();
-
- // Ensure the page doesn't use the old/invalid cursor anymore
- page.getAdapter().swapCursor(null, 0);
-
- pages.removeLast();
- }
- } else {
- // do nothing: we will be updating all the pages below
- }
-
- int startIndex = 0;
- for (TopSitesPage page : pages) {
- page.getAdapter().swapCursor(cursor, startIndex);
- startIndex += tiles;
- }
-
- notifyDataSetChanged();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/IconCallback.java b/mobile/android/base/java/org/mozilla/gecko/icons/IconCallback.java
deleted file mode 100644
index 0232a4ea6..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/IconCallback.java
+++ /dev/null
@@ -1,13 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons;
-
-/**
- * Interface for a callback that will be executed once an icon has been loaded successfully.
- */
-public interface IconCallback {
- void onIconResponse(IconResponse response);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/IconDescriptor.java b/mobile/android/base/java/org/mozilla/gecko/icons/IconDescriptor.java
deleted file mode 100644
index 359c47e53..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/IconDescriptor.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons;
-
-import android.support.annotation.IntDef;
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-
-/**
- * A class describing the location and properties of an icon that can be loaded.
- */
-public class IconDescriptor {
- @IntDef({ TYPE_GENERIC, TYPE_FAVICON, TYPE_TOUCHICON, TYPE_LOOKUP })
- @interface IconType {}
-
- // The type values are used for ranking icons (higher values = try to load first).
- @VisibleForTesting static final int TYPE_GENERIC = 0;
- @VisibleForTesting static final int TYPE_LOOKUP = 1;
- @VisibleForTesting static final int TYPE_FAVICON = 5;
- @VisibleForTesting static final int TYPE_TOUCHICON = 10;
-
- private final String url;
- private final int size;
- private final String mimeType;
- private final int type;
-
- /**
- * Create a generic icon located at the given URL. No MIME type or size is known.
- */
- public static IconDescriptor createGenericIcon(String url) {
- return new IconDescriptor(TYPE_GENERIC, url, 0, null);
- }
-
- /**
- * Create a favicon located at the given URL and with a known size and MIME type.
- */
- public static IconDescriptor createFavicon(String url, int size, String mimeType) {
- return new IconDescriptor(TYPE_FAVICON, url, size, mimeType);
- }
-
- /**
- * Create a touch icon located at the given URL and with a known MIME type and size.
- */
- public static IconDescriptor createTouchicon(String url, int size, String mimeType) {
- return new IconDescriptor(TYPE_TOUCHICON, url, size, mimeType);
- }
-
- /**
- * Create an icon located at an URL that has been returned from a disk or memory storage. This
- * is an icon with an URL we loaded an icon from previously. Therefore we give it a little higher
- * ranking than a generic icon - even though we do not know the MIME type or size of the icon.
- */
- public static IconDescriptor createLookupIcon(String url) {
- return new IconDescriptor(TYPE_LOOKUP, url, 0, null);
- }
-
- private IconDescriptor(@IconType int type, String url, int size, String mimeType) {
- this.type = type;
- this.url = url;
- this.size = size;
- this.mimeType = mimeType;
- }
-
- /**
- * Get the URL of the icon.
- */
- public String getUrl() {
- return url;
- }
-
- /**
- * Get the (assumed) size of the icon. Returns 0 if no size is known.
- */
- public int getSize() {
- return size;
- }
-
- /**
- * Get the type of the icon (favicon, touch icon, generic, lookup).
- */
- @IconType
- public int getType() {
- return type;
- }
-
- /**
- * Get the (assumed) MIME type of the icon. Returns null if no MIME type is known.
- */
- @Nullable
- public String getMimeType() {
- return mimeType;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/IconDescriptorComparator.java b/mobile/android/base/java/org/mozilla/gecko/icons/IconDescriptorComparator.java
deleted file mode 100644
index 3c6064825..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/IconDescriptorComparator.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons;
-
-import java.util.Comparator;
-
-/**
- * This comparator implementation compares IconDescriptor objects in order to determine which icon
- * to load first.
- *
- * In general this comparator will try touch icons before favicons (they usually have a higher resolution)
- * and prefers larger icons over smaller ones.
- */
-/* package-private */ class IconDescriptorComparator implements Comparator<IconDescriptor> {
- @Override
- public int compare(final IconDescriptor lhs, final IconDescriptor rhs) {
- if (lhs.getUrl().equals(rhs.getUrl())) {
- // Two descriptors pointing to the same URL are always referencing the same icon. So treat
- // them as equal.
- return 0;
- }
-
- // First compare the types. We prefer touch icons because they tend to have a higher resolution
- // than ordinary favicons.
- if (lhs.getType() != rhs.getType()) {
- return compareType(lhs, rhs);
- }
-
- // If one of them is larger than pick the larger icon.
- if (lhs.getSize() != rhs.getSize()) {
- return compareSizes(lhs, rhs);
- }
-
- // If there's no other way to choose, we prefer container types. They *might* contain
- // an image larger than the size given in the <link> tag.
- final boolean lhsContainer = IconsHelper.isContainerType(lhs.getMimeType());
- final boolean rhsContainer = IconsHelper.isContainerType(rhs.getMimeType());
-
- if (lhsContainer != rhsContainer) {
- return lhsContainer ? -1 : 1;
- }
-
- // There's no way to know which icon might be better. However we need to pick a consistent
- // one to avoid breaking the TreeSet implementation (See Bug 1331808). Therefore we are
- // picking one by just comparing the URLs.
- return lhs.getUrl().compareTo(rhs.getUrl());
- }
-
- private int compareType(IconDescriptor lhs, IconDescriptor rhs) {
- if (lhs.getType() > rhs.getType()) {
- return -1;
- } else {
- return 1;
- }
- }
-
- private int compareSizes(IconDescriptor lhs, IconDescriptor rhs) {
- if (lhs.getSize() > rhs.getSize()) {
- return -1;
- } else {
- return 1;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/IconRequest.java b/mobile/android/base/java/org/mozilla/gecko/icons/IconRequest.java
deleted file mode 100644
index be000642e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/IconRequest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons;
-
-import android.content.Context;
-import android.support.annotation.VisibleForTesting;
-
-import org.mozilla.gecko.R;
-
-import java.util.Iterator;
-import java.util.TreeSet;
-import java.util.concurrent.Future;
-
-/**
- * A class describing a request to load an icon for a website.
- */
-public class IconRequest {
- private Context context;
-
- // Those values are written by the IconRequestBuilder class.
- /* package-private */ String pageUrl;
- /* package-private */ boolean privileged;
- /* package-private */ TreeSet<IconDescriptor> icons;
- /* package-private */ boolean skipNetwork;
- /* package-private */ boolean backgroundThread;
- /* package-private */ boolean skipDisk;
- /* package-private */ boolean skipMemory;
- /* package-private */ int targetSize;
- /* package-private */ boolean prepareOnly;
- private IconCallback callback;
-
- /* package-private */ IconRequest(Context context) {
- this.context = context.getApplicationContext();
- this.icons = new TreeSet<>(new IconDescriptorComparator());
-
- // Setting some sensible defaults.
- this.privileged = false;
- this.skipMemory = false;
- this.skipDisk = false;
- this.skipNetwork = false;
- this.targetSize = context.getResources().getDimensionPixelSize(R.dimen.favicon_bg);
- this.prepareOnly = false;
- }
-
- /**
- * Execute this request and try to load an icon. Once an icon has been loaded successfully the
- * callback will be executed.
- *
- * The returned Future can be used to cancel the job.
- */
- public Future<IconResponse> execute(IconCallback callback) {
- setCallback(callback);
-
- return IconRequestExecutor.submit(this);
- }
-
- @VisibleForTesting void setCallback(IconCallback callback) {
- this.callback = callback;
- }
-
- /**
- * Get the (application) context associated with this request.
- */
- public Context getContext() {
- return context;
- }
-
- /**
- * Get the descriptor for the potentially best icon. This is the icon that should be loaded if
- * possible.
- */
- public IconDescriptor getBestIcon() {
- return icons.first();
- }
-
- /**
- * Get the URL of the page for which an icon should be loaded.
- */
- public String getPageUrl() {
- return pageUrl;
- }
-
- /**
- * Is this request allowed to load icons from internal data sources like the omni.ja?
- */
- public boolean isPrivileged() {
- return privileged;
- }
-
- /**
- * Get the number of icon descriptors associated with this request.
- */
- public int getIconCount() {
- return icons.size();
- }
-
- /**
- * Get the required target size of the icon.
- */
- public int getTargetSize() {
- return targetSize;
- }
-
- /**
- * Should a loader access the network to load this icon?
- */
- public boolean shouldSkipNetwork() {
- return skipNetwork;
- }
-
- /**
- * Should a loader access the disk to load this icon?
- */
- public boolean shouldSkipDisk() {
- return skipDisk;
- }
-
- /**
- * Should a loader access the memory cache to load this icon?
- */
- public boolean shouldSkipMemory() {
- return skipMemory;
- }
-
- /**
- * Get an iterator to iterate over all icon descriptors associated with this request.
- */
- public Iterator<IconDescriptor> getIconIterator() {
- return icons.iterator();
- }
-
- /**
- * Create a builder to modify this request.
- *
- * Calling methods on the builder will modify this object and not create a copy.
- */
- public IconRequestBuilder modify() {
- return new IconRequestBuilder(this);
- }
-
- /**
- * Should the callback be executed on a background thread? By default a callback is always
- * executed on the UI thread because an icon is usually loaded in order to display it somewhere
- * in the UI.
- */
- /* package-private */ boolean shouldRunOnBackgroundThread() {
- return backgroundThread;
- }
-
- /* package-private */ IconCallback getCallback() {
- return callback;
- }
-
- /* package-private */ boolean hasIconDescriptors() {
- return !icons.isEmpty();
- }
-
- /**
- * Move to the next icon. This method is called after all loaders for the current best icon
- * have failed. After calling this method getBestIcon() will return the next icon to try.
- * hasIconDescriptors() should be called before requesting the next icon.
- */
- /* package-private */ void moveToNextIcon() {
- if (!icons.remove(getBestIcon())) {
- // Calling this method when there's no next icon is an error (use hasIconDescriptors()).
- // Theoretically this method can fail even if there's a next icon (like it did in bug 1331808).
- // In this case crashing to see and fix the issue is desired.
- throw new IllegalStateException("Moving to next icon failed. Could not remove first icon from set.");
- }
- }
-
- /**
- * Should this request be prepared but not actually load an icon?
- */
- /* package-private */ boolean shouldPrepareOnly() {
- return prepareOnly;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/IconRequestBuilder.java b/mobile/android/base/java/org/mozilla/gecko/icons/IconRequestBuilder.java
deleted file mode 100644
index d9fd9ec5a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/IconRequestBuilder.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons;
-
-import android.content.Context;
-import android.support.annotation.CheckResult;
-
-import org.mozilla.gecko.GeckoAppShell;
-
-import ch.boye.httpclientandroidlib.util.TextUtils;
-
-/**
- * Builder for creating a request to load an icon.
- */
-public class IconRequestBuilder {
- private final IconRequest request;
-
- /* package-private */ IconRequestBuilder(Context context) {
- this(new IconRequest(context));
- }
-
- /* package-private */ IconRequestBuilder(IconRequest request) {
- this.request = request;
- }
-
- /**
- * Set the URL of the page for which the icon should be loaded.
- */
- @CheckResult
- public IconRequestBuilder pageUrl(String pageUrl) {
- request.pageUrl = pageUrl;
- return this;
- }
-
- /**
- * Set whether this request is allowed to load icons from non http(s) URLs (e.g. the omni.ja).
- *
- * For example web content referencing internal URLs should not lead to us loading icons from
- * internal data structures like the omni.ja.
- */
- @CheckResult
- public IconRequestBuilder privileged(boolean privileged) {
- request.privileged = privileged;
- return this;
- }
-
- /**
- * Add an icon descriptor describing the location and properties of an icon. All descriptors
- * will be ranked and tried in order of their rank. Executing the request will modify the list
- * of icons (filter or add additional descriptors).
- */
- @CheckResult
- public IconRequestBuilder icon(IconDescriptor descriptor) {
- request.icons.add(descriptor);
- return this;
- }
-
- /**
- * Skip the network and do not load an icon from a network connection.
- */
- @CheckResult
- public IconRequestBuilder skipNetwork() {
- request.skipNetwork = true;
- return this;
- }
-
- /**
- * Skip the disk cache and do not load an icon from disk.
- */
- @CheckResult
- public IconRequestBuilder skipDisk() {
- request.skipDisk = true;
- return this;
- }
-
- /**
- * Skip the memory cache and do not return a previously loaded icon.
- */
- @CheckResult
- public IconRequestBuilder skipMemory() {
- request.skipMemory = true;
- return this;
- }
-
- /**
- * The icon will be used as (Android) launcher icon. The loaded icon will be scaled to the
- * preferred Android launcher icon size.
- */
- public IconRequestBuilder forLauncherIcon() {
- request.targetSize = GeckoAppShell.getPreferredIconSize();
- return this;
- }
-
- /**
- * Execute the callback on the background thread. By default the callback is always executed on
- * the UI thread in order to add the loaded icon to a view easily.
- */
- @CheckResult
- public IconRequestBuilder executeCallbackOnBackgroundThread() {
- request.backgroundThread = true;
- return this;
- }
-
- /**
- * When executing the request then only prepare executing it but do not actually load an icon.
- * This mode is only used for some legacy code that uses the icon URL and therefore needs to
- * perform a lookup of the URL but doesn't want to load the icon yet.
- */
- public IconRequestBuilder prepareOnly() {
- request.prepareOnly = true;
- return this;
- }
-
- /**
- * Return the request built with this builder.
- */
- @CheckResult
- public IconRequest build() {
- if (TextUtils.isEmpty(request.pageUrl)) {
- throw new IllegalStateException("Page URL is required");
- }
-
- return request;
- }
-
- /**
- * This is a no-op method.
- *
- * All builder methods are annotated with @CheckResult to denote that the
- * methods return the builder object and that it is typically an error to not call another method
- * on it until eventually calling build().
- *
- * However in some situations code can keep a reference
- * to the builder object and call methods only when a specific event occurs. To make this explicit
- * and avoid lint errors this method can be called.
- */
- public void deferBuild() {
- // No op
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/IconRequestExecutor.java b/mobile/android/base/java/org/mozilla/gecko/icons/IconRequestExecutor.java
deleted file mode 100644
index aad784980..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/IconRequestExecutor.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons;
-
-import android.support.annotation.NonNull;
-
-import org.mozilla.gecko.icons.loader.ContentProviderLoader;
-import org.mozilla.gecko.icons.loader.DataUriLoader;
-import org.mozilla.gecko.icons.loader.DiskLoader;
-import org.mozilla.gecko.icons.loader.IconDownloader;
-import org.mozilla.gecko.icons.loader.IconGenerator;
-import org.mozilla.gecko.icons.loader.IconLoader;
-import org.mozilla.gecko.icons.loader.JarLoader;
-import org.mozilla.gecko.icons.loader.LegacyLoader;
-import org.mozilla.gecko.icons.loader.MemoryLoader;
-import org.mozilla.gecko.icons.preparation.AboutPagesPreparer;
-import org.mozilla.gecko.icons.preparation.AddDefaultIconUrl;
-import org.mozilla.gecko.icons.preparation.FilterKnownFailureUrls;
-import org.mozilla.gecko.icons.preparation.FilterMimeTypes;
-import org.mozilla.gecko.icons.preparation.FilterPrivilegedUrls;
-import org.mozilla.gecko.icons.preparation.LookupIconUrl;
-import org.mozilla.gecko.icons.preparation.Preparer;
-import org.mozilla.gecko.icons.processing.ColorProcessor;
-import org.mozilla.gecko.icons.processing.DiskProcessor;
-import org.mozilla.gecko.icons.processing.MemoryProcessor;
-import org.mozilla.gecko.icons.processing.Processor;
-import org.mozilla.gecko.icons.processing.ResizingProcessor;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Executor for icon requests.
- */
-/* package-private */ class IconRequestExecutor {
- /**
- * Loader implementation that generates an icon if none could be loaded.
- */
- private static final IconLoader GENERATOR = new IconGenerator();
-
- /**
- * Ordered list of prepares that run before any icon is loaded.
- */
- private static final List<Preparer> PREPARERS = Arrays.asList(
- // First we look into our memory and disk caches if there are some known icon URLs for
- // the page URL of the request.
- new LookupIconUrl(),
-
- // For all icons with MIME type we filter entries with unknown MIME type that we probably
- // cannot decode anyways.
- new FilterMimeTypes(),
-
- // If this is not a request that is allowed to load icons from privileged locations (omni.jar)
- // then filter such icon URLs.
- new FilterPrivilegedUrls(),
-
- // This preparer adds an icon URL for about pages. It's added after the filter for privileged
- // URLs. We always want to be able to load those specific icons.
- new AboutPagesPreparer(),
-
- // Add the default favicon URL (*/favicon.ico) to the list of icon URLs; with a low priority,
- // this icon URL should be tried last.
- new AddDefaultIconUrl(),
-
- // Finally we filter all URLs that failed to load recently (4xx / 5xx errors).
- new FilterKnownFailureUrls()
- );
-
- /**
- * Ordered list of loaders. If a loader returns a response object then subsequent loaders are not run.
- */
- private static final List<IconLoader> LOADERS = Arrays.asList(
- // First we try to load an icon that is already in the memory. That's cheap.
- new MemoryLoader(),
-
- // Try to decode the icon if it is a data: URI.
- new DataUriLoader(),
-
- // Try to load the icon from the omni.ha if it's a jar:jar URI.
- new JarLoader(),
-
- // Try to load the icon from a content provider (if applicable).
- new ContentProviderLoader(),
-
- // Try to load the icon from the disk cache.
- new DiskLoader(),
-
- // If the icon is not in any of our cashes and can't be decoded then look into the
- // database (legacy). Maybe this icon was loaded before the new code was deployed.
- new LegacyLoader(),
-
- // Download the icon from the web.
- new IconDownloader()
- );
-
- /**
- * Ordered list of processors that run after an icon has been loaded.
- */
- private static final List<Processor> PROCESSORS = Arrays.asList(
- // Store the icon (and mapping) in the disk cache if needed
- new DiskProcessor(),
-
- // Resize the icon to match the target size (if possible)
- new ResizingProcessor(),
-
- // Extract the dominant color from the icon
- new ColorProcessor(),
-
- // Store the icon in the memory cache
- new MemoryProcessor()
- );
-
- private static final ExecutorService EXECUTOR;
- static {
- final ThreadFactory factory = new ThreadFactory() {
- @Override
- public Thread newThread(@NonNull Runnable runnable) {
- Thread thread = new Thread(runnable, "GeckoIconTask");
- thread.setDaemon(false);
- thread.setPriority(Thread.NORM_PRIORITY);
- return thread;
- }
- };
-
- // Single thread executor
- EXECUTOR = new ThreadPoolExecutor(
- 1, /* corePoolSize */
- 1, /* maximumPoolSize */
- 0L, /* keepAliveTime */
- TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue<Runnable>(),
- factory);
- }
-
- /**
- * Submit the request for execution.
- */
- /* package-private */ static Future<IconResponse> submit(IconRequest request) {
- return EXECUTOR.submit(
- new IconTask(request, PREPARERS, LOADERS, PROCESSORS, GENERATOR)
- );
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/IconResponse.java b/mobile/android/base/java/org/mozilla/gecko/icons/IconResponse.java
deleted file mode 100644
index 726619eb9..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/IconResponse.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons;
-
-import android.graphics.Bitmap;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-
-/**
- * Response object containing a successful loaded icon and meta data.
- */
-public class IconResponse {
- /**
- * Create a response for a plain bitmap.
- */
- public static IconResponse create(@NonNull Bitmap bitmap) {
- return new IconResponse(bitmap);
- }
-
- /**
- * Create a response for a bitmap that has been loaded from the network by requesting a specific URL.
- */
- public static IconResponse createFromNetwork(@NonNull Bitmap bitmap, @NonNull String url) {
- final IconResponse response = new IconResponse(bitmap);
- response.url = url;
- response.fromNetwork = true;
- return response;
- }
-
- /**
- * Create a response for a generated bitmap with a dominant color.
- */
- public static IconResponse createGenerated(@NonNull Bitmap bitmap, int color) {
- final IconResponse response = new IconResponse(bitmap);
- response.color = color;
- response.generated = true;
- return response;
- }
-
- /**
- * Create a response for a bitmap that has been loaded from the memory cache.
- */
- public static IconResponse createFromMemory(@NonNull Bitmap bitmap, @NonNull String url, int color) {
- final IconResponse response = new IconResponse(bitmap);
- response.url = url;
- response.color = color;
- response.fromMemory = true;
- return response;
- }
-
- /**
- * Create a response for a bitmap that has been loaded from the disk cache.
- */
- public static IconResponse createFromDisk(@NonNull Bitmap bitmap, @NonNull String url) {
- final IconResponse response = new IconResponse(bitmap);
- response.url = url;
- response.fromDisk = true;
- return response;
- }
-
- private Bitmap bitmap;
- private int color;
- private boolean fromNetwork;
- private boolean fromMemory;
- private boolean fromDisk;
- private boolean generated;
- private String url;
-
- private IconResponse(Bitmap bitmap) {
- if (bitmap == null) {
- throw new NullPointerException("Bitmap is null");
- }
-
- this.bitmap = bitmap;
- this.color = 0;
- this.url = null;
- this.fromNetwork = false;
- this.fromMemory = false;
- this.fromDisk = false;
- this.generated = false;
- }
-
- /**
- * Get the icon bitmap. This method will always return a bitmap.
- */
- @NonNull
- public Bitmap getBitmap() {
- return bitmap;
- }
-
- /**
- * Get the dominant color of the icon. Will return 0 if no color could be extracted.
- */
- public int getColor() {
- return color;
- }
-
- /**
- * Does this response contain a dominant color?
- */
- public boolean hasColor() {
- return color != 0;
- }
-
- /**
- * Has this icon been loaded from the network?
- */
- public boolean isFromNetwork() {
- return fromNetwork;
- }
-
- /**
- * Has this icon been generated?
- */
- public boolean isGenerated() {
- return generated;
- }
-
- /**
- * Has this icon been loaded from memory (cache)?
- */
- public boolean isFromMemory() {
- return fromMemory;
- }
-
- /**
- * Has this icon been loaded from disk (cache)?
- */
- public boolean isFromDisk() {
- return fromDisk;
- }
-
- /**
- * Get the URL this icon has been loaded from.
- */
- @Nullable
- public String getUrl() {
- return url;
- }
-
- /**
- * Does this response contain an URL from which the icon has been loaded?
- */
- public boolean hasUrl() {
- return !TextUtils.isEmpty(url);
- }
-
- /**
- * Update the color of this response. This method is called by processors updating meta data
- * after the icon has been loaded.
- */
- public void updateColor(int color) {
- this.color = color;
- }
-
- /**
- * Update the bitmap of this response. This method is called by processors that modify the
- * loaded icon.
- */
- public void updateBitmap(Bitmap bitmap) {
- this.bitmap = bitmap;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/IconTask.java b/mobile/android/base/java/org/mozilla/gecko/icons/IconTask.java
deleted file mode 100644
index 411a31980..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/IconTask.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons;
-
-import android.graphics.Bitmap;
-import android.support.annotation.NonNull;
-import android.util.Log;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.icons.loader.IconLoader;
-import org.mozilla.gecko.icons.preparation.Preparer;
-import org.mozilla.gecko.icons.processing.Processor;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.util.List;
-import java.util.concurrent.Callable;
-
-/**
- * Task that will be run by the IconRequestExecutor for every icon request.
- */
-/* package-private */ class IconTask implements Callable<IconResponse> {
- private static final String LOGTAG = "Gecko/IconTask";
- private static final boolean DEBUG = false;
-
- private final List<Preparer> preparers;
- private final List<IconLoader> loaders;
- private final List<Processor> processors;
- private final IconLoader generator;
- private final IconRequest request;
-
- /* package-private */ IconTask(
- @NonNull IconRequest request,
- @NonNull List<Preparer> preparers,
- @NonNull List<IconLoader> loaders,
- @NonNull List<Processor> processors,
- @NonNull IconLoader generator) {
- this.request = request;
- this.preparers = preparers;
- this.loaders = loaders;
- this.processors = processors;
- this.generator = generator;
- }
-
- @Override
- public IconResponse call() {
- try {
- logRequest(request);
-
- prepareRequest(request);
-
- if (request.shouldPrepareOnly()) {
- // This request should only be prepared but not load an actual icon.
- return null;
- }
-
- final IconResponse response = loadIcon(request);
-
- if (response != null) {
- processIcon(request, response);
- executeCallback(request, response);
-
- logResponse(response);
-
- return response;
- }
- } catch (InterruptedException e) {
- Log.d(LOGTAG, "IconTask was interrupted", e);
-
- // Clear interrupt thread.
- Thread.interrupted();
- } catch (Throwable e) {
- handleException(e);
- }
-
- return null;
- }
-
- /**
- * Check if this thread was interrupted (e.g. this task was cancelled). Throws an InterruptedException
- * to stop executing the task in this case.
- */
- private void ensureNotInterrupted() throws InterruptedException {
- if (Thread.currentThread().isInterrupted()) {
- throw new InterruptedException("Task has been cancelled");
- }
- }
-
- private void executeCallback(IconRequest request, final IconResponse response) {
- final IconCallback callback = request.getCallback();
-
- if (callback != null) {
- if (request.shouldRunOnBackgroundThread()) {
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- callback.onIconResponse(response);
- }
- });
- } else {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- callback.onIconResponse(response);
- }
- });
- }
- }
- }
-
- private void prepareRequest(IconRequest request) throws InterruptedException {
- for (Preparer preparer : preparers) {
- ensureNotInterrupted();
-
- preparer.prepare(request);
-
- logPreparer(request, preparer);
- }
- }
-
- private IconResponse loadIcon(IconRequest request) throws InterruptedException {
- while (request.hasIconDescriptors()) {
- for (IconLoader loader : loaders) {
- ensureNotInterrupted();
-
- IconResponse response = loader.load(request);
-
- logLoader(request, loader, response);
-
- if (response != null) {
- return response;
- }
- }
-
- request.moveToNextIcon();
- }
-
- return generator.load(request);
- }
-
- private void processIcon(IconRequest request, IconResponse response) throws InterruptedException {
- for (Processor processor : processors) {
- ensureNotInterrupted();
-
- processor.process(request, response);
-
- logProcessor(processor);
- }
- }
-
- private void handleException(final Throwable t) {
- if (AppConstants.NIGHTLY_BUILD) {
- // We want to be aware of problems: Let's re-throw the exception on the main thread to
- // force an app crash. However we only do this in Nightly builds. Every other build
- // (especially release builds) should just carry on and log the error.
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- throw new RuntimeException("Icon task thread crashed", t);
- }
- });
- } else {
- Log.e(LOGTAG, "Icon task crashed", t);
- }
- }
-
- private boolean shouldLog() {
- // Do not log anything if debugging is disabled and never log anything in a non-nightly build.
- return DEBUG && AppConstants.NIGHTLY_BUILD;
- }
-
- private void logPreparer(IconRequest request, Preparer preparer) {
- if (!shouldLog()) {
- return;
- }
-
- Log.d(LOGTAG, String.format(" PREPARE %s" + " (%s)",
- preparer.getClass().getSimpleName(),
- request.getIconCount()));
- }
-
- private void logLoader(IconRequest request, IconLoader loader, IconResponse response) {
- if (!shouldLog()) {
- return;
- }
-
- Log.d(LOGTAG, String.format(" LOAD [%s] %s : %s",
- response != null ? "X" : " ",
- loader.getClass().getSimpleName(),
- request.getBestIcon().getUrl()));
- }
-
- private void logProcessor(Processor processor) {
- if (!shouldLog()) {
- return;
- }
-
- Log.d(LOGTAG, " PROCESS " + processor.getClass().getSimpleName());
- }
-
- private void logResponse(IconResponse response) {
- if (!shouldLog()) {
- return;
- }
-
- final Bitmap bitmap = response.getBitmap();
-
- Log.d(LOGTAG, String.format("=> ICON: %sx%s", bitmap.getWidth(), bitmap.getHeight()));
- }
-
- private void logRequest(IconRequest request) {
- if (!shouldLog()) {
- return;
- }
-
- Log.d(LOGTAG, String.format("REQUEST (%s) %s",
- request.getIconCount(),
- request.getPageUrl()));
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/Icons.java b/mobile/android/base/java/org/mozilla/gecko/icons/Icons.java
deleted file mode 100644
index a5505a694..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/Icons.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons;
-
-import android.content.Context;
-import android.support.annotation.CheckResult;
-
-/**
- * Entry point for loading icons for websites (just high quality icons, can be favicons or
- * touch icons).
- *
- * The API is loosely inspired by Picasso's builder.
- *
- * Example:
- *
- * Icons.with(context)
- * .pageUrl(pageURL)
- * .skipNetwork()
- * .privileged(true)
- * .icon(IconDescriptor.createGenericIcon(url))
- * .build()
- * .execute(callback);
- */
-public abstract class Icons {
- /**
- * Create a new request for loading a website icon.
- */
- @CheckResult
- public static IconRequestBuilder with(Context context) {
- return new IconRequestBuilder(context);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/IconsHelper.java b/mobile/android/base/java/org/mozilla/gecko/icons/IconsHelper.java
deleted file mode 100644
index d351eb4b7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/IconsHelper.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons;
-
-import android.net.Uri;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.util.Log;
-
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.util.StringUtils;
-
-import java.util.HashSet;
-
-/**
- * Helper methods for icon related tasks.
- */
-public class IconsHelper {
- private static final String LOGTAG = "Gecko/IconsHelper";
-
- // Mime types of things we are capable of decoding.
- private static final HashSet<String> sDecodableMimeTypes = new HashSet<>();
-
- // Mime types of things we are both capable of decoding and are container formats (May contain
- // multiple different sizes of image)
- private static final HashSet<String> sContainerMimeTypes = new HashSet<>();
-
- static {
- // MIME types extracted from http://filext.com - ostensibly all in-use mime types for the
- // corresponding formats.
- // ICO
- sContainerMimeTypes.add("image/vnd.microsoft.icon");
- sContainerMimeTypes.add("image/ico");
- sContainerMimeTypes.add("image/icon");
- sContainerMimeTypes.add("image/x-icon");
- sContainerMimeTypes.add("text/ico");
- sContainerMimeTypes.add("application/ico");
-
- // Add supported container types to the set of supported types.
- sDecodableMimeTypes.addAll(sContainerMimeTypes);
-
- // PNG
- sDecodableMimeTypes.add("image/png");
- sDecodableMimeTypes.add("application/png");
- sDecodableMimeTypes.add("application/x-png");
-
- // GIF
- sDecodableMimeTypes.add("image/gif");
-
- // JPEG
- sDecodableMimeTypes.add("image/jpeg");
- sDecodableMimeTypes.add("image/jpg");
- sDecodableMimeTypes.add("image/pipeg");
- sDecodableMimeTypes.add("image/vnd.swiftview-jpeg");
- sDecodableMimeTypes.add("application/jpg");
- sDecodableMimeTypes.add("application/x-jpg");
-
- // BMP
- sDecodableMimeTypes.add("application/bmp");
- sDecodableMimeTypes.add("application/x-bmp");
- sDecodableMimeTypes.add("application/x-win-bitmap");
- sDecodableMimeTypes.add("image/bmp");
- sDecodableMimeTypes.add("image/x-bmp");
- sDecodableMimeTypes.add("image/x-bitmap");
- sDecodableMimeTypes.add("image/x-xbitmap");
- sDecodableMimeTypes.add("image/x-win-bitmap");
- sDecodableMimeTypes.add("image/x-windows-bitmap");
- sDecodableMimeTypes.add("image/x-ms-bitmap");
- sDecodableMimeTypes.add("image/x-ms-bmp");
- sDecodableMimeTypes.add("image/ms-bmp");
- }
-
- /**
- * Helper method to getIcon the default Favicon URL for a given pageURL. Generally: somewhere.com/favicon.ico
- *
- * @param pageURL Page URL for which a default Favicon URL is requested
- * @return The default Favicon URL or null if no default URL could be guessed.
- */
- @Nullable
- public static String guessDefaultFaviconURL(String pageURL) {
- if (TextUtils.isEmpty(pageURL)) {
- return null;
- }
-
- // Special-casing for about: pages. The favicon for about:pages which don't provide a link tag
- // is bundled in the database, keyed only by page URL, hence the need to return the page URL
- // here. If the database ever migrates to stop being silly in this way, this can plausibly
- // be removed.
- if (AboutPages.isAboutPage(pageURL) || pageURL.startsWith("jar:")) {
- return pageURL;
- }
-
- if (!StringUtils.isHttpOrHttps(pageURL)) {
- // Guessing a default URL only makes sense for http(s) URLs.
- return null;
- }
-
- try {
- // Fall back to trying "someScheme:someDomain.someExtension/favicon.ico".
- Uri uri = Uri.parse(pageURL);
- if (uri.getAuthority().isEmpty()) {
- return null;
- }
-
- return uri.buildUpon()
- .path("favicon.ico")
- .clearQuery()
- .fragment("")
- .build()
- .toString();
- } catch (Exception e) {
- Log.d(LOGTAG, "Exception getting default favicon URL");
- return null;
- }
- }
-
- /**
- * Helper function to determine if the provided mime type is that of a format that can contain
- * multiple image types. At time of writing, the only such type is ICO.
- * @param mimeType Mime type to check.
- * @return true if the given mime type is a container type, false otherwise.
- */
- public static boolean isContainerType(@NonNull String mimeType) {
- return sContainerMimeTypes.contains(mimeType);
- }
-
- /**
- * Helper function to determine if we can decode a particular mime type.
- *
- * @param imgType Mime type to check for decodability.
- * @return false if the given mime type is certainly not decodable, true if it might be.
- */
- public static boolean canDecodeType(@NonNull String imgType) {
- return sDecodableMimeTypes.contains(imgType);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/decoders/FaviconDecoder.java b/mobile/android/base/java/org/mozilla/gecko/icons/decoders/FaviconDecoder.java
deleted file mode 100644
index 43f5d0ac6..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/decoders/FaviconDecoder.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.decoders;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.util.Base64;
-import android.util.Log;
-
-import org.mozilla.gecko.gfx.BitmapUtils;
-
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-/**
- * Class providing static utility methods for decoding favicons.
- */
-public class FaviconDecoder {
- private static final String LOG_TAG = "GeckoFaviconDecoder";
-
- static enum ImageMagicNumbers {
- // It is irritating that Java bytes are signed...
- PNG(new byte[] {(byte) (0x89 & 0xFF), 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}),
- GIF(new byte[] {0x47, 0x49, 0x46, 0x38}),
- JPEG(new byte[] {-0x1, -0x28, -0x1, -0x20}),
- BMP(new byte[] {0x42, 0x4d}),
- WEB(new byte[] {0x57, 0x45, 0x42, 0x50, 0x0a});
-
- public byte[] value;
-
- private ImageMagicNumbers(byte[] value) {
- this.value = value;
- }
- }
-
- /**
- * Check for image format magic numbers of formats supported by Android.
- * @param buffer Byte buffer to check for magic numbers
- * @param offset Offset at which to look for magic numbers.
- * @return true if the buffer contains a bitmap decodable by Android (Or at least, a sequence
- * starting with the magic numbers thereof). false otherwise.
- */
- private static boolean isDecodableByAndroid(byte[] buffer, int offset) {
- for (ImageMagicNumbers m : ImageMagicNumbers.values()) {
- if (bufferStartsWith(buffer, m.value, offset)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Utility function to check for the existence of a test byte sequence at a given offset in a
- * buffer.
- *
- * @param buffer Byte buffer to search.
- * @param test Byte sequence to search for.
- * @param bufferOffset Index in input buffer to expect test sequence.
- * @return true if buffer contains the byte sequence given in test at offset bufferOffset, false
- * otherwise.
- */
- static boolean bufferStartsWith(byte[] buffer, byte[] test, int bufferOffset) {
- if (buffer.length < test.length) {
- return false;
- }
-
- for (int i = 0; i < test.length; ++i) {
- if (buffer[bufferOffset + i] != test[i]) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Decode the favicon present in the region of the provided byte[] starting at offset and
- * proceeding for length bytes, if any. Returns either the resulting LoadFaviconResult or null if the
- * given range does not contain a bitmap we know how to decode.
- *
- * @param buffer Byte array containing the favicon to decode.
- * @param offset The index of the first byte in the array of the region of interest.
- * @param length The length of the region in the array to decode.
- * @return The decoded version of the bitmap in the described region, or null if none can be
- * decoded.
- */
- public static LoadFaviconResult decodeFavicon(Context context, byte[] buffer, int offset, int length) {
- LoadFaviconResult result;
- if (isDecodableByAndroid(buffer, offset)) {
- result = new LoadFaviconResult();
- result.offset = offset;
- result.length = length;
- result.isICO = false;
-
- Bitmap decodedImage = BitmapUtils.decodeByteArray(buffer, offset, length);
- if (decodedImage == null) {
- // What we got wasn't decodable after all. Probably corrupted image, or we got a muffled OOM.
- return null;
- }
-
- // We assume here that decodeByteArray doesn't hold on to the entire supplied
- // buffer -- worst case, each of our buffers will be twice the necessary size.
- result.bitmapsDecoded = new SingleBitmapIterator(decodedImage);
- result.faviconBytes = buffer;
-
- return result;
- }
-
- // If it's not decodable by Android, it might be an ICO. Let's try.
- ICODecoder decoder = new ICODecoder(context, buffer, offset, length);
-
- result = decoder.decode();
-
- if (result == null) {
- return null;
- }
-
- return result;
- }
-
- public static LoadFaviconResult decodeDataURI(Context context, String uri) {
- if (uri == null) {
- Log.w(LOG_TAG, "Can't decode null data: URI.");
- return null;
- }
-
- if (!uri.startsWith("data:image/")) {
- // Can't decode non-image data: URI.
- return null;
- }
-
- // Otherwise, let's attack this blindly. Strictly we should be parsing.
- int offset = uri.indexOf(',') + 1;
- if (offset == 0) {
- Log.w(LOG_TAG, "No ',' in data: URI; malformed?");
- return null;
- }
-
- try {
- String base64 = uri.substring(offset);
- byte[] raw = Base64.decode(base64, Base64.DEFAULT);
- return decodeFavicon(context, raw);
- } catch (Exception e) {
- Log.w(LOG_TAG, "Couldn't decode data: URI.", e);
- return null;
- }
- }
-
- public static LoadFaviconResult decodeFavicon(Context context, byte[] buffer) {
- return decodeFavicon(context, buffer, 0, buffer.length);
- }
-
- /**
- * Iterator to hold a single bitmap.
- */
- static class SingleBitmapIterator implements Iterator<Bitmap> {
- private Bitmap bitmap;
-
- public SingleBitmapIterator(Bitmap b) {
- bitmap = b;
- }
-
- /**
- * Slightly cheating here - this iterator supports peeking (Handy in a couple of obscure
- * places where the runtime type of the Iterator under consideration is known and
- * destruction of it is discouraged.
- *
- * @return The bitmap carried by this SingleBitmapIterator.
- */
- public Bitmap peek() {
- return bitmap;
- }
-
- @Override
- public boolean hasNext() {
- return bitmap != null;
- }
-
- @Override
- public Bitmap next() {
- if (bitmap == null) {
- throw new NoSuchElementException("Element already returned from SingleBitmapIterator.");
- }
-
- Bitmap ret = bitmap;
- bitmap = null;
- return ret;
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException("remove() not supported on SingleBitmapIterator.");
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/decoders/ICODecoder.java b/mobile/android/base/java/org/mozilla/gecko/icons/decoders/ICODecoder.java
deleted file mode 100644
index 44e3f1252..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/decoders/ICODecoder.java
+++ /dev/null
@@ -1,396 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.decoders;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.support.annotation.VisibleForTesting;
-import android.util.SparseArray;
-
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.gfx.BitmapUtils;
-import org.mozilla.gecko.R;
-
-/**
- * Utility class for determining the region of a provided array which contains the largest bitmap,
- * assuming the provided array is a valid ICO and the bitmap desired is square, and for pruning
- * unwanted entries from ICO files, if desired.
- *
- * An ICO file is a container format that may hold up to 255 images in either BMP or PNG format.
- * A mixture of image types may not exist.
- *
- * The format consists of a header specifying the number, n, of images, followed by the Icon Directory.
- *
- * The Icon Directory consists of n Icon Directory Entries, each 16 bytes in length, specifying, for
- * the corresponding image, the dimensions, colour information, payload size, and location in the file.
- *
- * All numerical fields follow a little-endian byte ordering.
- *
- * Header format:
- *
- * 0 1 2 3
- * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * | Reserved field. Must be zero | Type (1 for ICO, 2 for CUR) |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * | Image count (n) |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- *
- * The type field is expected to always be 1. CUR format images should not be used for Favicons.
- *
- *
- * Icon Directory Entry format:
- *
- * 0 1 2 3
- * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * | Image width | Image height | Palette size | Reserved (0) |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * | Colour plane count | Bits per pixel |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * | Size of image data, in bytes |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * | Start of image data, as an offset from start of file |
- * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- *
- * Image dimensions of zero are to be interpreted as image dimensions of 256.
- *
- * The palette size field records the number of colours in the stored BMP, if a palette is used. Zero
- * if the payload is a PNG or no palette is in use.
- *
- * The number of colour planes is, usually, 0 (Not in use) or 1. Values greater than 1 are to be
- * interpreted not as a colour plane count, but as a multiplying factor on the bits per pixel field.
- * (Apparently 65535 was not deemed a sufficiently large maximum value of bits per pixel.)
- *
- *
- * The Icon Directory consists of n-many Icon Directory Entries in sequence, with no gaps.
- *
- * This class is not thread safe.
- */
-public class ICODecoder implements Iterable<Bitmap> {
- // The number of bytes that compacting will save for us to bother doing it.
- public static final int COMPACT_THRESHOLD = 4000;
-
- // Some geometry of an ICO file.
- public static final int ICO_HEADER_LENGTH_BYTES = 6;
- public static final int ICO_ICONDIRENTRY_LENGTH_BYTES = 16;
-
- // The buffer containing bytes to attempt to decode.
- private byte[] decodand;
-
- // The region of the decodand to decode.
- private int offset;
- private int len;
-
- IconDirectoryEntry[] iconDirectory;
- private boolean isValid;
- private boolean hasDecoded;
- private int largestFaviconSize;
-
- @RobocopTarget
- public ICODecoder(Context context, byte[] decodand, int offset, int len) {
- this.decodand = decodand;
- this.offset = offset;
- this.len = len;
- this.largestFaviconSize = context.getResources()
- .getDimensionPixelSize(R.dimen.favicon_largest_interesting_size);
- }
-
- /**
- * Decode the Icon Directory for this ICO and store the result in iconDirectory.
- *
- * @return true if ICO decoding was considered to probably be a success, false if it certainly
- * was a failure.
- */
- private boolean decodeIconDirectoryAndPossiblyPrune() {
- hasDecoded = true;
-
- // Fail if the end of the described range is out of bounds.
- if (offset + len > decodand.length) {
- return false;
- }
-
- // Fail if we don't have enough space for the header.
- if (len < ICO_HEADER_LENGTH_BYTES) {
- return false;
- }
-
- // Check that the reserved fields in the header are indeed zero, and that the type field
- // specifies ICO. If not, we've probably been given something that isn't really an ICO.
- if (decodand[offset] != 0 ||
- decodand[offset + 1] != 0 ||
- decodand[offset + 2] != 1 ||
- decodand[offset + 3] != 0) {
- return false;
- }
-
- // Here, and in many other places, byte values are ANDed with 0xFF. This is because Java
- // bytes are signed - to obtain a numerical value of a longer type which holds the unsigned
- // interpretation of the byte of interest, we do this.
- int numEncodedImages = (decodand[offset + 4] & 0xFF) |
- (decodand[offset + 5] & 0xFF) << 8;
-
-
- // Fail if there are no images or the field is corrupt.
- if (numEncodedImages <= 0) {
- return false;
- }
-
- final int headerAndDirectorySize = ICO_HEADER_LENGTH_BYTES + (numEncodedImages * ICO_ICONDIRENTRY_LENGTH_BYTES);
-
- // Fail if there is not enough space in the buffer for the stated number of icondir entries,
- // let alone the data.
- if (len < headerAndDirectorySize) {
- return false;
- }
-
- // Put the pointer on the first byte of the first Icon Directory Entry.
- int bufferIndex = offset + ICO_HEADER_LENGTH_BYTES;
-
- // We now iterate over the Icon Directory, decoding each entry as we go. We also need to
- // discard all entries except one >= the maximum interesting size.
-
- // Size of the smallest image larger than the limit encountered.
- int minimumMaximum = Integer.MAX_VALUE;
-
- // Used to track the best entry for each size. The entries we want to keep.
- SparseArray<IconDirectoryEntry> preferenceArray = new SparseArray<IconDirectoryEntry>();
-
- for (int i = 0; i < numEncodedImages; i++, bufferIndex += ICO_ICONDIRENTRY_LENGTH_BYTES) {
- // Decode the Icon Directory Entry at this offset.
- IconDirectoryEntry newEntry = IconDirectoryEntry.createFromBuffer(decodand, offset, len, bufferIndex);
- newEntry.index = i;
-
- if (newEntry.isErroneous) {
- continue;
- }
-
- if (newEntry.width > largestFaviconSize) {
- // If we already have a smaller image larger than the maximum size of interest, we
- // don't care about the new one which is larger than the smallest image larger than
- // the maximum size.
- if (newEntry.width >= minimumMaximum) {
- continue;
- }
-
- // Remove the previous minimum-maximum.
- preferenceArray.delete(minimumMaximum);
-
- minimumMaximum = newEntry.width;
- }
-
- IconDirectoryEntry oldEntry = preferenceArray.get(newEntry.width);
- if (oldEntry == null) {
- preferenceArray.put(newEntry.width, newEntry);
- continue;
- }
-
- if (oldEntry.compareTo(newEntry) < 0) {
- preferenceArray.put(newEntry.width, newEntry);
- }
- }
-
- final int count = preferenceArray.size();
-
- // Abort if no entries are desired (Perhaps all are corrupt?)
- if (count == 0) {
- return false;
- }
-
- // Allocate space for the icon directory entries in the decoded directory.
- iconDirectory = new IconDirectoryEntry[count];
-
- // The size of the data in the buffer that we find useful.
- int retainedSpace = ICO_HEADER_LENGTH_BYTES;
-
- for (int i = 0; i < count; i++) {
- IconDirectoryEntry e = preferenceArray.valueAt(i);
- retainedSpace += ICO_ICONDIRENTRY_LENGTH_BYTES + e.payloadSize;
- iconDirectory[i] = e;
- }
-
- isValid = true;
-
- // Set the number of images field in the buffer to reflect the number of retained entries.
- decodand[offset + 4] = (byte) iconDirectory.length;
- decodand[offset + 5] = (byte) (iconDirectory.length >>> 8);
-
- if ((len - retainedSpace) > COMPACT_THRESHOLD) {
- compactingCopy(retainedSpace);
- }
-
- return true;
- }
-
- /**
- * Copy the buffer into a new array of exactly the required size, omitting any unwanted data.
- */
- private void compactingCopy(int spaceRetained) {
- byte[] buf = new byte[spaceRetained];
-
- // Copy the header.
- System.arraycopy(decodand, offset, buf, 0, ICO_HEADER_LENGTH_BYTES);
-
- int headerPtr = ICO_HEADER_LENGTH_BYTES;
-
- int payloadPtr = ICO_HEADER_LENGTH_BYTES + (iconDirectory.length * ICO_ICONDIRENTRY_LENGTH_BYTES);
-
- int ind = 0;
- for (IconDirectoryEntry entry : iconDirectory) {
- // Copy this entry.
- System.arraycopy(decodand, offset + entry.getOffset(), buf, headerPtr, ICO_ICONDIRENTRY_LENGTH_BYTES);
-
- // Copy its payload.
- System.arraycopy(decodand, offset + entry.payloadOffset, buf, payloadPtr, entry.payloadSize);
-
- // Update the offset field.
- buf[headerPtr + 12] = (byte) payloadPtr;
- buf[headerPtr + 13] = (byte) (payloadPtr >>> 8);
- buf[headerPtr + 14] = (byte) (payloadPtr >>> 16);
- buf[headerPtr + 15] = (byte) (payloadPtr >>> 24);
-
- entry.payloadOffset = payloadPtr;
- entry.index = ind;
-
- payloadPtr += entry.payloadSize;
- headerPtr += ICO_ICONDIRENTRY_LENGTH_BYTES;
- ind++;
- }
-
- decodand = buf;
- offset = 0;
- len = spaceRetained;
- }
-
- /**
- * Decode and return the bitmap represented by the given index in the Icon Directory, if valid.
- *
- * @param index The index into the Icon Directory of the image of interest.
- * @return The decoded Bitmap object for this image, or null if the entry is invalid or decoding
- * fails.
- */
- public Bitmap decodeBitmapAtIndex(int index) {
- final IconDirectoryEntry iconDirEntry = iconDirectory[index];
-
- if (iconDirEntry.payloadIsPNG) {
- // PNG payload. Simply extract it and decode it.
- return BitmapUtils.decodeByteArray(decodand, offset + iconDirEntry.payloadOffset, iconDirEntry.payloadSize);
- }
-
- // The payload is a BMP, so we need to do some magic to get the decoder to do what we want.
- // We construct an ICO containing just the image we want, and let Android do the rest.
- byte[] decodeTarget = new byte[ICO_HEADER_LENGTH_BYTES + ICO_ICONDIRENTRY_LENGTH_BYTES + iconDirEntry.payloadSize];
-
- // Set the type field in the ICO header.
- decodeTarget[2] = 1;
-
- // Set the num-images field in the header to 1.
- decodeTarget[4] = 1;
-
- // Copy the ICONDIRENTRY we need into the new buffer.
- System.arraycopy(decodand, offset + iconDirEntry.getOffset(), decodeTarget, ICO_HEADER_LENGTH_BYTES, ICO_ICONDIRENTRY_LENGTH_BYTES);
-
- // Copy the payload into the new buffer.
- final int singlePayloadOffset = ICO_HEADER_LENGTH_BYTES + ICO_ICONDIRENTRY_LENGTH_BYTES;
- System.arraycopy(decodand, offset + iconDirEntry.payloadOffset, decodeTarget, singlePayloadOffset, iconDirEntry.payloadSize);
-
- // Update the offset field of the ICONDIRENTRY to make the new ICO valid.
- decodeTarget[ICO_HEADER_LENGTH_BYTES + 12] = singlePayloadOffset;
- decodeTarget[ICO_HEADER_LENGTH_BYTES + 13] = (singlePayloadOffset >>> 8);
- decodeTarget[ICO_HEADER_LENGTH_BYTES + 14] = (singlePayloadOffset >>> 16);
- decodeTarget[ICO_HEADER_LENGTH_BYTES + 15] = (singlePayloadOffset >>> 24);
-
- // Decode the newly-constructed singleton-ICO.
- return BitmapUtils.decodeByteArray(decodeTarget);
- }
-
- /**
- * Fetch an iterator over the images in this ICO, or null if this ICO seems to be invalid.
- *
- * @return An iterator over the Bitmaps stored in this ICO, or null if decoding fails.
- */
- @Override
- public ICOIterator iterator() {
- // If a previous call to decode concluded this ICO is invalid, abort.
- if (hasDecoded && !isValid) {
- return null;
- }
-
- // If we've not been decoded before, but now fail to make any sense of the ICO, abort.
- if (!hasDecoded) {
- if (!decodeIconDirectoryAndPossiblyPrune()) {
- return null;
- }
- }
-
- // If decoding was a success, return an iterator over the images in this ICO.
- return new ICOIterator();
- }
-
- /**
- * Decode this ICO and return the result as a LoadFaviconResult.
- * @return A LoadFaviconResult representing the decoded ICO.
- */
- public LoadFaviconResult decode() {
- // The call to iterator returns null if decoding fails.
- Iterator<Bitmap> bitmaps = iterator();
- if (bitmaps == null) {
- return null;
- }
-
- LoadFaviconResult result = new LoadFaviconResult();
-
- result.bitmapsDecoded = bitmaps;
- result.faviconBytes = decodand;
- result.offset = offset;
- result.length = len;
- result.isICO = true;
-
- return result;
- }
-
- @VisibleForTesting
- @RobocopTarget
- public IconDirectoryEntry[] getIconDirectory() {
- return iconDirectory;
- }
-
- @VisibleForTesting
- @RobocopTarget
- public int getLargestFaviconSize() {
- return largestFaviconSize;
- }
-
- /**
- * Inner class to iterate over the elements in the ICO represented by the enclosing instance.
- */
- private class ICOIterator implements Iterator<Bitmap> {
- private int mIndex;
-
- @Override
- public boolean hasNext() {
- return mIndex < iconDirectory.length;
- }
-
- @Override
- public Bitmap next() {
- if (mIndex > iconDirectory.length) {
- throw new NoSuchElementException("No more elements in this ICO.");
- }
- return decodeBitmapAtIndex(mIndex++);
- }
-
- @Override
- public void remove() {
- if (iconDirectory[mIndex] == null) {
- throw new IllegalStateException("Remove already called for element " + mIndex);
- }
- iconDirectory[mIndex] = null;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/decoders/IconDirectoryEntry.java b/mobile/android/base/java/org/mozilla/gecko/icons/decoders/IconDirectoryEntry.java
deleted file mode 100644
index 82ff91a55..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/decoders/IconDirectoryEntry.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.decoders;
-
-import android.support.annotation.VisibleForTesting;
-
-import org.mozilla.gecko.annotation.RobocopTarget;
-
-/**
- * Representation of an ICO file ICONDIRENTRY structure.
- */
-public class IconDirectoryEntry implements Comparable<IconDirectoryEntry> {
-
- public static int maxBPP;
-
- int width;
- int height;
- int paletteSize;
- int bitsPerPixel;
- int payloadSize;
- int payloadOffset;
- boolean payloadIsPNG;
-
- // Tracks the index in the Icon Directory of this entry. Useful only for pruning.
- int index;
- boolean isErroneous;
-
- @RobocopTarget
- public IconDirectoryEntry(int width, int height, int paletteSize, int bitsPerPixel, int payloadSize, int payloadOffset, boolean payloadIsPNG) {
- this.width = width;
- this.height = height;
- this.paletteSize = paletteSize;
- this.bitsPerPixel = bitsPerPixel;
- this.payloadSize = payloadSize;
- this.payloadOffset = payloadOffset;
- this.payloadIsPNG = payloadIsPNG;
- }
-
- /**
- * Method to get a dummy Icon Directory Entry with the Erroneous bit set.
- *
- * @return An erroneous placeholder Icon Directory Entry.
- */
- public static IconDirectoryEntry getErroneousEntry() {
- IconDirectoryEntry ret = new IconDirectoryEntry(-1, -1, -1, -1, -1, -1, false);
- ret.isErroneous = true;
-
- return ret;
- }
-
- /**
- * Create an IconDirectoryEntry object from a byte[]. Interprets the buffer starting at the given
- * offset as an IconDirectoryEntry and returns the result.
- *
- * @param buffer Byte array containing the icon directory entry to decode.
- * @param regionOffset Offset into the byte array of the valid region of the buffer.
- * @param regionLength Length of the valid region in the buffer.
- * @param entryOffset Offset of the icon directory entry to decode within the buffer.
- * @return An IconDirectoryEntry object representing the entry specified, or null if the entry
- * is obviously invalid.
- */
- public static IconDirectoryEntry createFromBuffer(byte[] buffer, int regionOffset, int regionLength, int entryOffset) {
- // Verify that the reserved field is really zero.
- if (buffer[entryOffset + 3] != 0) {
- return getErroneousEntry();
- }
-
- // Verify that the entry points to a region that actually exists in the buffer, else bin it.
- int fieldPtr = entryOffset + 8;
- int entryLength = (buffer[fieldPtr] & 0xFF) |
- (buffer[fieldPtr + 1] & 0xFF) << 8 |
- (buffer[fieldPtr + 2] & 0xFF) << 16 |
- (buffer[fieldPtr + 3] & 0xFF) << 24;
-
- // Advance to the offset field.
- fieldPtr += 4;
-
- int payloadOffset = (buffer[fieldPtr] & 0xFF) |
- (buffer[fieldPtr + 1] & 0xFF) << 8 |
- (buffer[fieldPtr + 2] & 0xFF) << 16 |
- (buffer[fieldPtr + 3] & 0xFF) << 24;
-
- // Fail if the entry describes a region outside the buffer.
- if (payloadOffset < 0 || entryLength < 0 || payloadOffset + entryLength > regionOffset + regionLength) {
- return getErroneousEntry();
- }
-
- // Extract the image dimensions.
- int imageWidth = buffer[entryOffset] & 0xFF;
- int imageHeight = buffer[entryOffset + 1] & 0xFF;
-
- // Because Microsoft, a size value of zero represents an image size of 256.
- if (imageWidth == 0) {
- imageWidth = 256;
- }
-
- if (imageHeight == 0) {
- imageHeight = 256;
- }
-
- // If the image uses a colour palette, this is the number of colours, otherwise this is zero.
- int paletteSize = buffer[entryOffset + 2] & 0xFF;
-
- // The plane count - usually 0 or 1. When > 1, taken as multiplier on bitsPerPixel.
- int colorPlanes = buffer[entryOffset + 4] & 0xFF;
-
- int bitsPerPixel = (buffer[entryOffset + 6] & 0xFF) |
- (buffer[entryOffset + 7] & 0xFF) << 8;
-
- if (colorPlanes > 1) {
- bitsPerPixel *= colorPlanes;
- }
-
- // Look for PNG magic numbers at the start of the payload.
- boolean payloadIsPNG = FaviconDecoder.bufferStartsWith(buffer, FaviconDecoder.ImageMagicNumbers.PNG.value, regionOffset + payloadOffset);
-
- return new IconDirectoryEntry(imageWidth, imageHeight, paletteSize, bitsPerPixel, entryLength, payloadOffset, payloadIsPNG);
- }
-
- /**
- * Get the number of bytes from the start of the ICO file to the beginning of this entry.
- */
- public int getOffset() {
- return ICODecoder.ICO_HEADER_LENGTH_BYTES + (index * ICODecoder.ICO_ICONDIRENTRY_LENGTH_BYTES);
- }
-
- @Override
- public int compareTo(IconDirectoryEntry another) {
- if (width > another.width) {
- return 1;
- }
-
- if (width < another.width) {
- return -1;
- }
-
- // Where both images exceed the max BPP, take the smaller of the two BPP values.
- if (bitsPerPixel >= maxBPP && another.bitsPerPixel >= maxBPP) {
- if (bitsPerPixel < another.bitsPerPixel) {
- return 1;
- }
-
- if (bitsPerPixel > another.bitsPerPixel) {
- return -1;
- }
- }
-
- // Otherwise, take the larger of the BPP values.
- if (bitsPerPixel > another.bitsPerPixel) {
- return 1;
- }
-
- if (bitsPerPixel < another.bitsPerPixel) {
- return -1;
- }
-
- // Prefer large palettes.
- if (paletteSize > another.paletteSize) {
- return 1;
- }
-
- if (paletteSize < another.paletteSize) {
- return -1;
- }
-
- // Prefer smaller payloads.
- if (payloadSize < another.payloadSize) {
- return 1;
- }
-
- if (payloadSize > another.payloadSize) {
- return -1;
- }
-
- // If all else fails, prefer PNGs over BMPs. They tend to be smaller.
- if (payloadIsPNG && !another.payloadIsPNG) {
- return 1;
- }
-
- if (!payloadIsPNG && another.payloadIsPNG) {
- return -1;
- }
-
- return 0;
- }
-
- public static void setMaxBPP(int maxBPP) {
- IconDirectoryEntry.maxBPP = maxBPP;
- }
-
- @VisibleForTesting
- @RobocopTarget
- public int getWidth() {
- return width;
- }
-
- @Override
- public String toString() {
- return "IconDirectoryEntry{" +
- "\nwidth=" + width +
- ", \nheight=" + height +
- ", \npaletteSize=" + paletteSize +
- ", \nbitsPerPixel=" + bitsPerPixel +
- ", \npayloadSize=" + payloadSize +
- ", \npayloadOffset=" + payloadOffset +
- ", \npayloadIsPNG=" + payloadIsPNG +
- ", \nindex=" + index +
- '}';
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/decoders/LoadFaviconResult.java b/mobile/android/base/java/org/mozilla/gecko/icons/decoders/LoadFaviconResult.java
deleted file mode 100644
index cc196b91e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/decoders/LoadFaviconResult.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.decoders;
-
-import android.graphics.Bitmap;
-import android.support.annotation.Nullable;
-import android.util.Log;
-import android.util.SparseArray;
-
-import java.io.ByteArrayOutputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Class representing the result of loading a favicon.
- * This operation will produce either a collection of favicons, a single favicon, or no favicon.
- * It is necessary to model single favicons differently to a collection of one favicon (An entity
- * that may not exist with this scheme) since the in-database representation of these things differ.
- * (In particular, collections of favicons are stored in encoded ICO format, whereas single icons are
- * stored as decoded bitmap blobs.)
- */
-public class LoadFaviconResult {
- private static final String LOGTAG = "LoadFaviconResult";
-
- byte[] faviconBytes;
- int offset;
- int length;
-
- boolean isICO;
- Iterator<Bitmap> bitmapsDecoded;
-
- public Iterator<Bitmap> getBitmaps() {
- return bitmapsDecoded;
- }
-
- /**
- * Return a representation of this result suitable for storing in the database.
- *
- * @return A byte array containing the bytes from which this result was decoded,
- * or null if re-encoding failed.
- */
- public byte[] getBytesForDatabaseStorage() {
- // Begin by normalising the buffer.
- if (offset != 0 || length != faviconBytes.length) {
- final byte[] normalised = new byte[length];
- System.arraycopy(faviconBytes, offset, normalised, 0, length);
- offset = 0;
- faviconBytes = normalised;
- }
-
- // For results containing multiple images, we store the result verbatim. (But cutting the
- // buffer to size first).
- // We may instead want to consider re-encoding the entire ICO as a collection of efficiently
- // encoded PNGs. This may not be worth the CPU time (Indeed, the encoding of single-image
- // favicons may also not be worth the time/space tradeoff.).
- if (isICO) {
- return faviconBytes;
- }
-
- // For results containing a single image, we re-encode the
- // result as a PNG in an effort to save space.
- final Bitmap favicon = ((FaviconDecoder.SingleBitmapIterator) bitmapsDecoded).peek();
- final ByteArrayOutputStream stream = new ByteArrayOutputStream();
-
- try {
- if (favicon.compress(Bitmap.CompressFormat.PNG, 100, stream)) {
- return stream.toByteArray();
- }
- } catch (OutOfMemoryError e) {
- Log.w(LOGTAG, "Out of memory re-compressing favicon.");
- }
-
- Log.w(LOGTAG, "Favicon re-compression failed.");
- return null;
- }
-
- @Nullable
- public Bitmap getBestBitmap(int targetWidthAndHeight) {
- final SparseArray<Bitmap> iconMap = new SparseArray<>();
- final List<Integer> sizes = new ArrayList<>();
-
- while (bitmapsDecoded.hasNext()) {
- final Bitmap b = bitmapsDecoded.next();
-
- // It's possible to receive null, most likely due to OOM or a zero-sized image,
- // from BitmapUtils.decodeByteArray(byte[], int, int, BitmapFactory.Options)
- if (b != null) {
- iconMap.put(b.getWidth(), b);
- sizes.add(b.getWidth());
- }
- }
-
- int bestSize = selectBestSizeFromList(sizes, targetWidthAndHeight);
-
- if (bestSize == -1) {
- // No icons found: this could occur if we weren't able to process any of the
- // supplied icons.
- return null;
- }
-
- return iconMap.get(bestSize);
- }
-
- /**
- * Select the closest icon size from a list of icon sizes.
- * We just find the first icon that is larger than the preferred size if available, or otherwise select the
- * largest icon (if all icons are smaller than the preferred size).
- *
- * @return The closest icon size, or -1 if no sizes are supplied.
- */
- public static int selectBestSizeFromList(final List<Integer> sizes, final int preferredSize) {
- if (sizes.isEmpty()) {
- // This isn't ideal, however current code assumes this as an error value for now.
- return -1;
- }
-
- Collections.sort(sizes);
-
- for (int size : sizes) {
- if (size >= preferredSize) {
- return size;
- }
- }
-
- // If all icons are smaller than the preferred size then we don't have an icon
- // selected yet, therefore just take the largest (last) icon.
- return sizes.get(sizes.size() - 1);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/loader/ContentProviderLoader.java b/mobile/android/base/java/org/mozilla/gecko/icons/loader/ContentProviderLoader.java
deleted file mode 100644
index be8e6d7de..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/loader/ContentProviderLoader.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.loader;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.text.TextUtils;
-
-import org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy;
-import org.mozilla.gecko.icons.decoders.FaviconDecoder;
-import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.IconResponse;
-
-/**
- * Loader for loading icons from a content provider. This loader was primarily written to load icons
- * from the partner bookmarks provider. However it can load icons from arbitrary content providers
- * as long as they return a cursor with a "favicon" or "touchicon" column (blob).
- */
-public class ContentProviderLoader implements IconLoader {
- @Override
- public IconResponse load(IconRequest request) {
- if (request.shouldSkipDisk()) {
- // If we should not load data from disk then we do not load from content providers either.
- return null;
- }
-
- final String iconUrl = request.getBestIcon().getUrl();
- final Context context = request.getContext();
- final int targetSize = request.getTargetSize();
-
- if (TextUtils.isEmpty(iconUrl) || !iconUrl.startsWith("content://")) {
- return null;
- }
-
- Cursor cursor = context.getContentResolver().query(
- Uri.parse(iconUrl),
- new String[] {
- PartnerBookmarksProviderProxy.PartnerContract.TOUCHICON,
- PartnerBookmarksProviderProxy.PartnerContract.FAVICON,
- },
- null,
- null,
- null
- );
-
- if (cursor == null) {
- return null;
- }
-
- try {
- if (!cursor.moveToFirst()) {
- return null;
- }
-
- // Try the touch icon first. It has a higher resolution usually.
- Bitmap icon = decodeFromCursor(request.getContext(), cursor, PartnerBookmarksProviderProxy.PartnerContract.TOUCHICON, targetSize);
- if (icon != null) {
- return IconResponse.create(icon);
- }
-
- icon = decodeFromCursor(request.getContext(), cursor, PartnerBookmarksProviderProxy.PartnerContract.FAVICON, targetSize);
- if (icon != null) {
- return IconResponse.create(icon);
- }
- } finally {
- cursor.close();
- }
-
- return null;
- }
-
- private Bitmap decodeFromCursor(Context context, Cursor cursor, String column, int targetWidthAndHeight) {
- final int index = cursor.getColumnIndex(column);
- if (index == -1) {
- return null;
- }
-
- if (cursor.isNull(index)) {
- return null;
- }
-
- final byte[] data = cursor.getBlob(index);
- LoadFaviconResult result = FaviconDecoder.decodeFavicon(context, data, 0, data.length);
- if (result == null) {
- return null;
- }
-
- return result.getBestBitmap(targetWidthAndHeight);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/loader/DataUriLoader.java b/mobile/android/base/java/org/mozilla/gecko/icons/loader/DataUriLoader.java
deleted file mode 100644
index 9ddc138ec..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/loader/DataUriLoader.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.loader;
-
-import org.mozilla.gecko.icons.decoders.FaviconDecoder;
-import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.IconResponse;
-
-/**
- * Loader for loading icons from a data URI. This loader will try to decode any data with an
- * "image/*" MIME type.
- *
- * https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
- */
-public class DataUriLoader implements IconLoader {
- @Override
- public IconResponse load(IconRequest request) {
- final String iconUrl = request.getBestIcon().getUrl();
-
- if (!iconUrl.startsWith("data:image/")) {
- return null;
- }
-
- LoadFaviconResult loadFaviconResult = FaviconDecoder.decodeDataURI(request.getContext(), iconUrl);
- if (loadFaviconResult == null) {
- return null;
- }
-
- return IconResponse.create(
- loadFaviconResult.getBestBitmap(request.getTargetSize()));
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/loader/DiskLoader.java b/mobile/android/base/java/org/mozilla/gecko/icons/loader/DiskLoader.java
deleted file mode 100644
index 18a38e32b..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/loader/DiskLoader.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.loader;
-
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.storage.DiskStorage;
-
-/**
- * Loader implementation for loading icons from the disk cache (Implemented by DiskStorage).
- */
-public class DiskLoader implements IconLoader {
- @Override
- public IconResponse load(IconRequest request) {
- if (request.shouldSkipDisk()) {
- return null;
- }
-
- final DiskStorage storage = DiskStorage.get(request.getContext());
- final String iconUrl = request.getBestIcon().getUrl();
-
- return storage.getIcon(iconUrl);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconDownloader.java b/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconDownloader.java
deleted file mode 100644
index 3ae9d15d0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconDownloader.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.loader;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.support.annotation.VisibleForTesting;
-import android.util.Log;
-
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.icons.decoders.FaviconDecoder;
-import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.storage.FailureCache;
-import org.mozilla.gecko.util.IOUtils;
-import org.mozilla.gecko.util.ProxySelector;
-import org.mozilla.gecko.util.StringUtils;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.HashSet;
-
-/**
- * This loader implementation downloads icons from http(s) URLs.
- */
-public class IconDownloader implements IconLoader {
- private static final String LOGTAG = "Gecko/Downloader";
-
- /**
- * The maximum number of http redirects (3xx) until we give up.
- */
- private static final int MAX_REDIRECTS_TO_FOLLOW = 5;
-
- /**
- * The default size of the buffer to use for downloading Favicons in the event no size is given
- * by the server. */
- private static final int DEFAULT_FAVICON_BUFFER_SIZE_BYTES = 25000;
-
- @Override
- public IconResponse load(IconRequest request) {
- if (request.shouldSkipNetwork()) {
- return null;
- }
-
- final String iconUrl = request.getBestIcon().getUrl();
-
- if (!StringUtils.isHttpOrHttps(iconUrl)) {
- return null;
- }
-
- try {
- final LoadFaviconResult result = downloadAndDecodeImage(request.getContext(), iconUrl);
- if (result == null) {
- return null;
- }
-
- final Bitmap bitmap = result.getBestBitmap(request.getTargetSize());
- if (bitmap == null) {
- return null;
- }
-
- return IconResponse.createFromNetwork(bitmap, iconUrl);
- } catch (Exception e) {
- Log.e(LOGTAG, "Error reading favicon", e);
- } catch (OutOfMemoryError e) {
- Log.e(LOGTAG, "Insufficient memory to process favicon");
- }
-
- return null;
- }
-
- /**
- * Download the Favicon from the given URL and pass it to the decoder function.
- *
- * @param targetFaviconURL URL of the favicon to download.
- * @return A LoadFaviconResult containing the bitmap(s) extracted from the downloaded file, or
- * null if no or corrupt data was received.
- * @throws IOException If attempts to fully read the stream result in such an exception, such as
- * in the event of a transient connection failure.
- * @throws URISyntaxException If the underlying call to tryDownload retries and raises such an
- * exception trying a fallback URL.
- */
- @VisibleForTesting
- LoadFaviconResult downloadAndDecodeImage(Context context, String targetFaviconURL) throws IOException, URISyntaxException {
- // Try the URL we were given.
- final HttpURLConnection connection = tryDownload(targetFaviconURL);
- if (connection == null) {
- return null;
- }
-
- InputStream stream = null;
-
- // Decode the image from the fetched response.
- try {
- stream = connection.getInputStream();
- return decodeImageFromResponse(context, stream, connection.getHeaderFieldInt("Content-Length", -1));
- } finally {
- // Close the stream and free related resources.
- IOUtils.safeStreamClose(stream);
- connection.disconnect();
- }
- }
-
- /**
- * Helper method for trying the download request to grab a Favicon.
- *
- * @param faviconURI URL of Favicon to try and download
- * @return The HttpResponse containing the downloaded Favicon if successful, null otherwise.
- */
- private HttpURLConnection tryDownload(String faviconURI) throws URISyntaxException, IOException {
- final HashSet<String> visitedLinkSet = new HashSet<>();
- visitedLinkSet.add(faviconURI);
- return tryDownloadRecurse(faviconURI, visitedLinkSet);
- }
-
- /**
- * Try to download from the favicon URL and recursively follow redirects.
- */
- private HttpURLConnection tryDownloadRecurse(String faviconURI, HashSet<String> visited) throws URISyntaxException, IOException {
- if (visited.size() == MAX_REDIRECTS_TO_FOLLOW) {
- return null;
- }
-
- final HttpURLConnection connection = connectTo(faviconURI);
-
- // Was the response a failure?
- final int status = connection.getResponseCode();
-
- // Handle HTTP status codes requesting a redirect.
- if (status >= 300 && status < 400) {
- final String newURI = connection.getHeaderField("Location");
-
- // Handle mad web servers.
- try {
- if (newURI == null || newURI.equals(faviconURI)) {
- return null;
- }
-
- if (visited.contains(newURI)) {
- // Already been redirected here - abort.
- return null;
- }
-
- visited.add(newURI);
- } finally {
- connection.disconnect();
- }
-
- return tryDownloadRecurse(newURI, visited);
- }
-
- if (status >= 400) {
- // Client or Server error. Let's not retry loading from this URL again for some time.
- FailureCache.get().rememberFailure(faviconURI);
-
- connection.disconnect();
- return null;
- }
-
- return connection;
- }
-
- @VisibleForTesting
- HttpURLConnection connectTo(String faviconURI) throws URISyntaxException, IOException {
- final HttpURLConnection connection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(
- new URI(faviconURI));
-
- connection.setRequestProperty("User-Agent", GeckoAppShell.getGeckoInterface().getDefaultUAString());
-
- // We implemented or own way of following redirects back when this code was using HttpClient.
- // Nowadays we should let HttpUrlConnection do the work - assuming that it doesn't follow
- // redirects in loops forever.
- connection.setInstanceFollowRedirects(false);
-
- connection.connect();
-
- return connection;
- }
-
- /**
- * Copies the favicon stream to a buffer and decodes downloaded content into bitmaps using the
- * FaviconDecoder.
- *
- * @param stream to decode
- * @param contentLength as reported by the server (or -1)
- * @return A LoadFaviconResult containing the bitmap(s) extracted from the downloaded file, or
- * null if no or corrupt data were received.
- * @throws IOException If attempts to fully read the stream result in such an exception, such as
- * in the event of a transient connection failure.
- */
- private LoadFaviconResult decodeImageFromResponse(Context context, InputStream stream, int contentLength) throws IOException {
- // This may not be provided, but if it is, it's useful.
- final int bufferSize;
- if (contentLength > 0) {
- // The size was reported and sane, so let's use that.
- // Integer overflow should not be a problem for Favicon sizes...
- bufferSize = contentLength + 1;
- } else {
- // No declared size, so guess and reallocate later if it turns out to be too small.
- bufferSize = DEFAULT_FAVICON_BUFFER_SIZE_BYTES;
- }
-
- // Read the InputStream into a byte[].
- final IOUtils.ConsumedInputStream result = IOUtils.readFully(stream, bufferSize);
- if (result == null) {
- return null;
- }
-
- // Having downloaded the image, decode it.
- return FaviconDecoder.decodeFavicon(context, result.getData(), 0, result.consumedLength);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconGenerator.java b/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconGenerator.java
deleted file mode 100644
index e0139345d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconGenerator.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.loader;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.RectF;
-import android.net.Uri;
-import android.support.annotation.NonNull;
-import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
-import android.util.TypedValue;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.IconResponse;
-
-/**
- * This loader will generate an icon in case no icon could be loaded. In order to do so this needs
- * to be the last loader that will be tried.
- */
-public class IconGenerator implements IconLoader {
- // Mozilla's Visual Design Colour Palette
- // http://firefoxux.github.io/StyleGuide/#/visualDesign/colours
- private static final int[] COLORS = {
- 0xFFc33c32,
- 0xFFf25820,
- 0xFFff9216,
- 0xFFffcb00,
- 0xFF57bd35,
- 0xFF01bdad,
- 0xFF0996f8,
- 0xFF02538b,
- 0xFF1f386e,
- 0xFF7a2f7a,
- 0xFFea385e,
- };
-
- // List of common prefixes of host names. Those prefixes will be striped before a prepresentative
- // character for an URL is determined.
- private static final String[] COMMON_PREFIXES = {
- "www.",
- "m.",
- "mobile.",
- };
-
- private static final int TEXT_SIZE_DP = 12;
- @Override
- public IconResponse load(IconRequest request) {
- if (request.getIconCount() > 1) {
- // There are still other icons to try. We will only generate an icon if there's only one
- // icon left and all previous loaders have failed (assuming this is the last one).
- return null;
- }
-
- return generate(request.getContext(), request.getPageUrl());
- }
-
- /**
- * Generate default favicon for the given page URL.
- */
- @VisibleForTesting static IconResponse generate(Context context, String pageURL) {
- final Resources resources = context.getResources();
- final int widthAndHeight = resources.getDimensionPixelSize(R.dimen.favicon_bg);
- final int roundedCorners = resources.getDimensionPixelOffset(R.dimen.favicon_corner_radius);
-
- final Bitmap favicon = Bitmap.createBitmap(widthAndHeight, widthAndHeight, Bitmap.Config.ARGB_8888);
- final Canvas canvas = new Canvas(favicon);
-
- final int color = pickColor(pageURL);
-
- final Paint paint = new Paint();
- paint.setColor(color);
-
- canvas.drawRoundRect(new RectF(0, 0, widthAndHeight, widthAndHeight), roundedCorners, roundedCorners, paint);
-
- paint.setColor(Color.WHITE);
-
- final String character = getRepresentativeCharacter(pageURL);
-
- final float textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, TEXT_SIZE_DP, context.getResources().getDisplayMetrics());
-
- paint.setTextAlign(Paint.Align.CENTER);
- paint.setTextSize(textSize);
- paint.setAntiAlias(true);
-
- canvas.drawText(character,
- canvas.getWidth() / 2,
- (int) ((canvas.getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2)),
- paint);
-
- return IconResponse.createGenerated(favicon, color);
- }
-
- /**
- * Get a representative character for the given URL.
- *
- * For example this method will return "f" for "http://m.facebook.com/foobar".
- */
- @VisibleForTesting static String getRepresentativeCharacter(String url) {
- if (TextUtils.isEmpty(url)) {
- return "?";
- }
-
- final String snippet = getRepresentativeSnippet(url);
- for (int i = 0; i < snippet.length(); i++) {
- char c = snippet.charAt(i);
-
- if (Character.isLetterOrDigit(c)) {
- return String.valueOf(Character.toUpperCase(c));
- }
- }
-
- // Nothing found..
- return "?";
- }
-
- /**
- * Return a color for this URL. Colors will be based on the host. URLs with the same host will
- * return the same color.
- */
- @VisibleForTesting static int pickColor(String url) {
- if (TextUtils.isEmpty(url)) {
- return COLORS[0];
- }
-
- final String snippet = getRepresentativeSnippet(url);
- final int color = Math.abs(snippet.hashCode() % COLORS.length);
-
- return COLORS[color];
- }
-
- /**
- * Get the representative part of the URL. Usually this is the host (without common prefixes).
- */
- private static String getRepresentativeSnippet(@NonNull String url) {
- Uri uri = Uri.parse(url);
-
- // Use the host if available
- String snippet = uri.getHost();
-
- if (TextUtils.isEmpty(snippet)) {
- // If the uri does not have a host (e.g. file:// uris) then use the path
- snippet = uri.getPath();
- }
-
- if (TextUtils.isEmpty(snippet)) {
- // If we still have no snippet then just return the question mark
- return "?";
- }
-
- // Strip common prefixes that we do not want to use to determine the representative character
- for (String prefix : COMMON_PREFIXES) {
- if (snippet.startsWith(prefix)) {
- snippet = snippet.substring(prefix.length());
- }
- }
-
- return snippet;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconLoader.java b/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconLoader.java
deleted file mode 100644
index 8158babc3..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconLoader.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.loader;
-
-import android.support.annotation.Nullable;
-
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.IconResponse;
-
-/**
- * Generic interface for classes that can load icons.
- */
-public interface IconLoader {
- /**
- * Loads the icon for this request or returns null if this loader can't load an icon for this
- * request or just failed this time.
- */
- @Nullable
- IconResponse load(IconRequest request);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/loader/JarLoader.java b/mobile/android/base/java/org/mozilla/gecko/icons/loader/JarLoader.java
deleted file mode 100644
index 882d32da5..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/loader/JarLoader.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.loader;
-
-import android.content.Context;
-import android.util.Log;
-
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.util.GeckoJarReader;
-
-/**
- * Loader implementation for loading icons from the omni.ja (jar:jar: URLs).
- *
- * https://developer.mozilla.org/en-US/docs/Mozilla/About_omni.ja_(formerly_omni.jar)
- */
-public class JarLoader implements IconLoader {
- private static final String LOGTAG = "Gecko/JarLoader";
-
- @Override
- public IconResponse load(IconRequest request) {
- if (request.shouldSkipDisk()) {
- return null;
- }
-
- final String iconUrl = request.getBestIcon().getUrl();
-
- if (!iconUrl.startsWith("jar:jar:")) {
- return null;
- }
-
- try {
- final Context context = request.getContext();
- return IconResponse.create(
- GeckoJarReader.getBitmap(context, context.getResources(), iconUrl));
- } catch (Exception e) {
- // Just about anything could happen here.
- Log.w(LOGTAG, "Error fetching favicon from JAR.", e);
- return null;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/loader/LegacyLoader.java b/mobile/android/base/java/org/mozilla/gecko/icons/loader/LegacyLoader.java
deleted file mode 100644
index d1efc3ad9..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/loader/LegacyLoader.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.loader;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.graphics.Bitmap;
-
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.icons.decoders.LoadFaviconResult;
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.IconResponse;
-
-/**
- * This legacy loader loads icons from the abandoned database storage. This loader should only exist
- * for a couple of releases and be removed afterwards.
- *
- * When updating to an app version with the new loaders our initial storage won't have any data so
- * we need to continue loading from the database storage until the new storage has a good set of data.
- */
-public class LegacyLoader implements IconLoader {
- @Override
- public IconResponse load(IconRequest request) {
- if (!request.shouldSkipNetwork()) {
- // If we are allowed to load from the network for this request then just ommit the legacy
- // loader and fetch a fresh new icon.
- return null;
- }
-
- if (request.shouldSkipDisk()) {
- return null;
- }
-
- if (request.getIconCount() > 1) {
- // There are still other icon URLs to try. Let's try to load from the legacy loader only
- // if there's one icon left and the other loads have failed. We will ignore the icon URL
- // anyways and try to receive the legacy icon URL from the database.
- return null;
- }
-
- final Bitmap bitmap = loadBitmapFromDatabase(request);
-
- if (bitmap == null) {
- return null;
- }
-
- return IconResponse.create(bitmap);
- }
-
- /* package-private */ Bitmap loadBitmapFromDatabase(IconRequest request) {
- final Context context = request.getContext();
- final ContentResolver contentResolver = context.getContentResolver();
- final BrowserDB db = BrowserDB.from(context);
-
- // We ask the database for the favicon URL and ignore the icon URL in the request object:
- // As we are not updating the database anymore the icon might be stored under a different URL.
- final String legacyFaviconUrl = db.getFaviconURLFromPageURL(contentResolver, request.getPageUrl());
- if (legacyFaviconUrl == null) {
- // No URL -> Nothing to load.
- return null;
- }
-
- final LoadFaviconResult result = db.getFaviconForUrl(context, context.getContentResolver(), legacyFaviconUrl);
- if (result == null) {
- return null;
- }
-
- return result.getBestBitmap(request.getTargetSize());
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/loader/MemoryLoader.java b/mobile/android/base/java/org/mozilla/gecko/icons/loader/MemoryLoader.java
deleted file mode 100644
index 98b651fc7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/loader/MemoryLoader.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.loader;
-
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.storage.MemoryStorage;
-
-/**
- * Loader implementation for loading icons from an in-memory cached (Implemented by MemoryStorage).
- */
-public class MemoryLoader implements IconLoader {
- private final MemoryStorage storage;
-
- public MemoryLoader() {
- storage = MemoryStorage.get();
- }
-
- @Override
- public IconResponse load(IconRequest request) {
- if (request.shouldSkipMemory()) {
- return null;
- }
-
- final String iconUrl = request.getBestIcon().getUrl();
- return storage.getIcon(iconUrl);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/preparation/AboutPagesPreparer.java b/mobile/android/base/java/org/mozilla/gecko/icons/preparation/AboutPagesPreparer.java
deleted file mode 100644
index d335cbf51..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/preparation/AboutPagesPreparer.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.preparation;
-
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.icons.IconDescriptor;
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.util.GeckoJarReader;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Preparer implementation for adding the omni.ja URL for internal about: pages.
- */
-public class AboutPagesPreparer implements Preparer {
- private Set<String> aboutUrls;
-
- public AboutPagesPreparer() {
- aboutUrls = new HashSet<>();
-
- Collections.addAll(aboutUrls, AboutPages.DEFAULT_ICON_PAGES);
- }
-
- @Override
- public void prepare(IconRequest request) {
- if (aboutUrls.contains(request.getPageUrl())) {
- final String iconUrl = GeckoJarReader.getJarURL(request.getContext(), "chrome/chrome/content/branding/favicon64.png");
-
- request.modify()
- .icon(IconDescriptor.createLookupIcon(iconUrl))
- .deferBuild();
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/preparation/AddDefaultIconUrl.java b/mobile/android/base/java/org/mozilla/gecko/icons/preparation/AddDefaultIconUrl.java
deleted file mode 100644
index 5bc7d1c1f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/preparation/AddDefaultIconUrl.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.preparation;
-
-import android.text.TextUtils;
-
-import org.mozilla.gecko.icons.IconDescriptor;
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.IconsHelper;
-import org.mozilla.gecko.util.StringUtils;
-
-/**
- * Preparer to add the "default/guessed" favicon URL (domain/favicon.ico) to the list of URLs to
- * try loading the favicon from.
- *
- * The default URL will be added with a very low priority so that we will only try to load from this
- * URL if all other options failed.
- */
-public class AddDefaultIconUrl implements Preparer {
- @Override
- public void prepare(IconRequest request) {
- if (!StringUtils.isHttpOrHttps(request.getPageUrl())) {
- return;
- }
-
- final String defaultFaviconUrl = IconsHelper.guessDefaultFaviconURL(request.getPageUrl());
- if (TextUtils.isEmpty(defaultFaviconUrl)) {
- // We couldn't generate a default favicon URL for this URL. Nothing to do here.
- return;
- }
-
- request.modify()
- .icon(IconDescriptor.createGenericIcon(defaultFaviconUrl))
- .deferBuild();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/preparation/FilterKnownFailureUrls.java b/mobile/android/base/java/org/mozilla/gecko/icons/preparation/FilterKnownFailureUrls.java
deleted file mode 100644
index effd31a03..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/preparation/FilterKnownFailureUrls.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.preparation;
-
-import org.mozilla.gecko.icons.IconDescriptor;
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.storage.FailureCache;
-
-import java.util.Iterator;
-
-public class FilterKnownFailureUrls implements Preparer {
- @Override
- public void prepare(IconRequest request) {
- final FailureCache failureCache = FailureCache.get();
- final Iterator<IconDescriptor> iterator = request.getIconIterator();
-
- while (iterator.hasNext()) {
- final IconDescriptor descriptor = iterator.next();
-
- if (failureCache.isKnownFailure(descriptor.getUrl())) {
- // Loading from this URL has failed in the past. Do not try again.
- iterator.remove();
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/preparation/FilterMimeTypes.java b/mobile/android/base/java/org/mozilla/gecko/icons/preparation/FilterMimeTypes.java
deleted file mode 100644
index a12dad2ad..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/preparation/FilterMimeTypes.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.preparation;
-
-import android.text.TextUtils;
-
-import org.mozilla.gecko.icons.IconDescriptor;
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.IconsHelper;
-
-import java.util.Iterator;
-
-/**
- * Preparer implementation to filter unknown MIME types to avoid loading images that we cannot decode.
- */
-public class FilterMimeTypes implements Preparer {
- @Override
- public void prepare(IconRequest request) {
- final Iterator<IconDescriptor> iterator = request.getIconIterator();
-
- while (iterator.hasNext()) {
- final IconDescriptor descriptor = iterator.next();
- final String mimeType = descriptor.getMimeType();
-
- if (TextUtils.isEmpty(mimeType)) {
- // We do not have a MIME type for this icon, so we cannot know in advance if we are able
- // to decode it. Let's just continue.
- continue;
- }
-
- if (!IconsHelper.canDecodeType(mimeType)) {
- iterator.remove();
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/preparation/FilterPrivilegedUrls.java b/mobile/android/base/java/org/mozilla/gecko/icons/preparation/FilterPrivilegedUrls.java
deleted file mode 100644
index abf34c038..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/preparation/FilterPrivilegedUrls.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.mozilla.gecko.icons.preparation;
-
-import org.mozilla.gecko.icons.IconDescriptor;
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.util.StringUtils;
-
-import java.util.Iterator;
-
-/**
- * Filter non http/https URLs if the request is not from privileged code.
- */
-public class FilterPrivilegedUrls implements Preparer {
- @Override
- public void prepare(IconRequest request) {
- if (request.isPrivileged()) {
- // This request is privileged. No need to filter anything.
- return;
- }
-
- final Iterator<IconDescriptor> iterator = request.getIconIterator();
-
- while (iterator.hasNext()) {
- IconDescriptor descriptor = iterator.next();
-
- if (!StringUtils.isHttpOrHttps(descriptor.getUrl())) {
- iterator.remove();
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/preparation/LookupIconUrl.java b/mobile/android/base/java/org/mozilla/gecko/icons/preparation/LookupIconUrl.java
deleted file mode 100644
index 0c7641112..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/preparation/LookupIconUrl.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.preparation;
-
-import org.mozilla.gecko.icons.IconDescriptor;
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.storage.DiskStorage;
-import org.mozilla.gecko.icons.storage.MemoryStorage;
-
-/**
- * Preparer implementation to lookup the icon URL for the page URL in the request. This class tries
- * to locate the icon URL by looking through previously stored mappings on disk and in memory.
- */
-public class LookupIconUrl implements Preparer {
- @Override
- public void prepare(IconRequest request) {
- if (lookupFromMemory(request)) {
- return;
- }
-
- lookupFromDisk(request);
- }
-
- private boolean lookupFromMemory(IconRequest request) {
- final String iconUrl = MemoryStorage.get()
- .getMapping(request.getPageUrl());
-
- if (iconUrl != null) {
- request.modify()
- .icon(IconDescriptor.createLookupIcon(iconUrl))
- .deferBuild();
-
- return true;
- }
-
- return false;
- }
-
- private boolean lookupFromDisk(IconRequest request) {
- final String iconUrl = DiskStorage.get(request.getContext())
- .getMapping(request.getPageUrl());
-
- if (iconUrl != null) {
- request.modify()
- .icon(IconDescriptor.createLookupIcon(iconUrl))
- .deferBuild();
-
- return true;
- }
-
- return false;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/preparation/Preparer.java b/mobile/android/base/java/org/mozilla/gecko/icons/preparation/Preparer.java
deleted file mode 100644
index 466307ead..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/preparation/Preparer.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.preparation;
-
-import org.mozilla.gecko.icons.IconRequest;
-
-/**
- * Generic interface for a class "preparing" a request before we try to load icons. A class
- * implementing this interface can modify the request (e.g. filter or add icon URLs).
- */
-public interface Preparer {
- /**
- * Inspects or modifies the request before any icon is loaded.
- */
- void prepare(IconRequest request);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/processing/ColorProcessor.java b/mobile/android/base/java/org/mozilla/gecko/icons/processing/ColorProcessor.java
deleted file mode 100644
index 3f7110034..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/processing/ColorProcessor.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.processing;
-
-import android.support.v7.graphics.Palette;
-import android.util.Log;
-
-import org.mozilla.gecko.gfx.BitmapUtils;
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.util.HardwareUtils;
-
-/**
- * Processor implementation to extract the dominant color from the icon and attach it to the icon
- * response object.
- */
-public class ColorProcessor implements Processor {
- private static final String LOGTAG = "GeckoColorProcessor";
- private static final int DEFAULT_COLOR = 0; // 0 == No color
-
- @Override
- public void process(IconRequest request, IconResponse response) {
- if (response.hasColor()) {
- return;
- }
-
- if (HardwareUtils.isX86System()) {
- // (Bug 1318667) We are running into crashes when using the palette library with
- // specific icons on x86 devices. They take down the whole VM and are not recoverable.
- // Unfortunately our release icon is triggering this crash. Until we can switch to a
- // newer version of the support library where this does not happen, we are using our
- // own slower implementation.
- extractColorUsingCustomImplementation(response);
- } else {
- extractColorUsingPaletteSupportLibrary(response);
- }
- }
-
- private void extractColorUsingPaletteSupportLibrary(final IconResponse response) {
- try {
- final Palette palette = Palette.from(response.getBitmap()).generate();
- response.updateColor(palette.getVibrantColor(DEFAULT_COLOR));
- } catch (ArrayIndexOutOfBoundsException e) {
- // We saw the palette library fail with an ArrayIndexOutOfBoundsException intermittently
- // in automation. In this case lets just swallow the exception and move on without a
- // color. This is a valid condition and callers should handle this gracefully (Bug 1318560).
- Log.e(LOGTAG, "Palette generation failed with ArrayIndexOutOfBoundsException", e);
-
- response.updateColor(DEFAULT_COLOR);
- }
- }
-
- private void extractColorUsingCustomImplementation(final IconResponse response) {
- final int dominantColor = BitmapUtils.getDominantColor(response.getBitmap());
-
- response.updateColor(dominantColor);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/processing/DiskProcessor.java b/mobile/android/base/java/org/mozilla/gecko/icons/processing/DiskProcessor.java
deleted file mode 100644
index 150aa503b..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/processing/DiskProcessor.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.mozilla.gecko.icons.processing;
-
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.storage.DiskStorage;
-import org.mozilla.gecko.util.StringUtils;
-
-public class DiskProcessor implements Processor {
- @Override
- public void process(IconRequest request, IconResponse response) {
- if (request.shouldSkipDisk()) {
- return;
- }
-
- if (!response.hasUrl() || !StringUtils.isHttpOrHttps(response.getUrl())) {
- // If the response does not contain an URL from which the icon was loaded or if this is
- // not a http(s) URL then we cannot store this or do not need to (because it's already
- // stored somewhere else, like for URLs pointing inside the omni.ja).
- return;
- }
-
- final DiskStorage storage = DiskStorage.get(request.getContext());
-
- if (response.isFromNetwork()) {
- // The icon has been loaded from the network. Store it on the disk now.
- storage.putIcon(response);
- }
-
- if (response.isFromMemory() || response.isFromDisk() || response.isFromNetwork()) {
- // Remember mapping between page URL and storage URL. Even when this icon has been loaded
- // from memory or disk this does not mean that we stored this mapping already: We could
- // have loaded this icon for a different page URL previously.
- storage.putMapping(request, response.getUrl());
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/processing/MemoryProcessor.java b/mobile/android/base/java/org/mozilla/gecko/icons/processing/MemoryProcessor.java
deleted file mode 100644
index 245faded5..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/processing/MemoryProcessor.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.processing;
-
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.storage.MemoryStorage;
-
-public class MemoryProcessor implements Processor {
- private final MemoryStorage storage;
-
- public MemoryProcessor() {
- storage = MemoryStorage.get();
- }
-
- @Override
- public void process(IconRequest request, IconResponse response) {
- if (request.shouldSkipMemory() || request.getIconCount() == 0 || response.isGenerated()) {
- // Do not cache this icon in memory if we should skip the memory cache or if this icon
- // has been generated. We can re-generate it if needed.
- return;
- }
-
- final String iconUrl = request.getBestIcon().getUrl();
-
- if (iconUrl.startsWith("data:image/")) {
- // The image data is encoded in the URL. It doesn't make sense to store the URL and the
- // bitmap in cache.
- return;
- }
-
- storage.putMapping(request, iconUrl);
- storage.putIcon(iconUrl, response);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/processing/Processor.java b/mobile/android/base/java/org/mozilla/gecko/icons/processing/Processor.java
deleted file mode 100644
index df7d63c6c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/processing/Processor.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.processing;
-
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.IconResponse;
-
-/**
- * Generic interface for a class that processes a response object after an icon has been loaded and
- * decoded. A class implementing this interface can attach additional data to the response or modify
- * the bitmap (e.g. resizing).
- */
-public interface Processor {
- /**
- * Process a response object containing an icon loaded for this request.
- */
- void process(IconRequest request, IconResponse response);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/processing/ResizingProcessor.java b/mobile/android/base/java/org/mozilla/gecko/icons/processing/ResizingProcessor.java
deleted file mode 100644
index 63b479021..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/processing/ResizingProcessor.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.processing;
-
-import android.graphics.Bitmap;
-import android.support.annotation.VisibleForTesting;
-
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.IconResponse;
-
-/**
- * Processor implementation for resizing the loaded icon based on the target size.
- */
-public class ResizingProcessor implements Processor {
- @Override
- public void process(IconRequest request, IconResponse response) {
- if (response.isFromMemory()) {
- // This bitmap has been loaded from memory, so it has already gone through the resizing
- // process. We do not want to resize the image every time we hit the memory cache.
- return;
- }
-
- final Bitmap originalBitmap = response.getBitmap();
- final int size = originalBitmap.getWidth();
-
- final int targetSize = request.getTargetSize();
-
- if (size == targetSize) {
- // The bitmap has exactly the size we are looking for.
- return;
- }
-
- final Bitmap resizedBitmap;
-
- if (size > targetSize) {
- resizedBitmap = resize(originalBitmap, targetSize);
- } else {
- // Our largest primary is smaller than the desired size. Upscale by a maximum of 2x.
- // 'largestSize' now reflects the maximum size we can upscale to.
- final int largestSize = size * 2;
-
- if (largestSize > targetSize) {
- // Perfect! We can upscale by less than 2x and reach the needed size. Do it.
- resizedBitmap = resize(originalBitmap, targetSize);
- } else {
- // We don't have enough information to make the target size look non terrible. Best effort:
- resizedBitmap = resize(originalBitmap, largestSize);
- }
- }
-
- response.updateBitmap(resizedBitmap);
-
- originalBitmap.recycle();
- }
-
- @VisibleForTesting Bitmap resize(Bitmap bitmap, int targetSize) {
- try {
- return Bitmap.createScaledBitmap(bitmap, targetSize, targetSize, true);
- } catch (OutOfMemoryError error) {
- // There's not enough memory to create a resized copy of the bitmap in memory. Let's just
- // use what we have.
- return bitmap;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/storage/DiskStorage.java b/mobile/android/base/java/org/mozilla/gecko/icons/storage/DiskStorage.java
deleted file mode 100644
index 3c0e2a554..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/storage/DiskStorage.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.storage;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.support.annotation.CheckResult;
-import android.support.annotation.Nullable;
-import android.util.Log;
-
-import com.jakewharton.disklrucache.DiskLruCache;
-
-import org.mozilla.gecko.background.nativecode.NativeCrypto;
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.util.IOUtils;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.security.MessageDigest;
-
-/**
- * Least Recently Used (LRU) disk cache for icons and the mappings from page URLs to icon URLs.
- */
-public class DiskStorage {
- private static final String LOGTAG = "Gecko/DiskStorage";
-
- /**
- * Maximum size (in bytes) of the cache. This cache is located in the cache directory of the
- * application and can be cleared by the user.
- */
- private static final int DISK_CACHE_SIZE = 50 * 1024 * 1024;
-
- /**
- * Version of the cache. Updating the version will invalidate all existing items.
- */
- private static final int CACHE_VERSION = 1;
-
- private static final String KEY_PREFIX_ICON = "icon:";
- private static final String KEY_PREFIX_MAPPING = "mapping:";
-
- private static DiskStorage instance;
-
- public static DiskStorage get(Context context) {
- if (instance == null) {
- instance = new DiskStorage(context);
- }
-
- return instance;
- }
-
- private Context context;
- private DiskLruCache cache;
-
- private DiskStorage(Context context) {
- this.context = context.getApplicationContext();
- }
-
- @CheckResult
- private synchronized DiskLruCache ensureCacheIsReady() throws IOException {
- if (cache == null || cache.isClosed()) {
- cache = DiskLruCache.open(
- new File(context.getCacheDir(), "icons"),
- CACHE_VERSION,
- 1,
- DISK_CACHE_SIZE);
- }
-
- return cache;
- }
-
- /**
- * Store a mapping from page URL to icon URL in the cache.
- */
- public void putMapping(IconRequest request, String iconUrl) {
- putMapping(request.getPageUrl(), iconUrl);
- }
-
- /**
- * Store a mapping from page URL to icon URL in the cache.
- */
- public void putMapping(String pageUrl, String iconUrl) {
- DiskLruCache.Editor editor = null;
-
- try {
- final DiskLruCache cache = ensureCacheIsReady();
-
- final String key = createKey(KEY_PREFIX_MAPPING, pageUrl);
- if (key == null) {
- return;
- }
-
- editor = cache.edit(key);
- if (editor == null) {
- return;
- }
-
- editor.set(0, iconUrl);
- editor.commit();
- } catch (IOException e) {
- Log.w(LOGTAG, "IOException while accessing disk cache", e);
-
- abortSilently(editor);
- }
- }
-
- /**
- * Store an icon in the cache (uses the icon URL as key).
- */
- public void putIcon(IconResponse response) {
- putIcon(response.getUrl(), response.getBitmap());
- }
-
- /**
- * Store an icon in the cache (uses the icon URL as key).
- */
- public void putIcon(String iconUrl, Bitmap bitmap) {
- OutputStream outputStream = null;
- DiskLruCache.Editor editor = null;
-
- try {
- final DiskLruCache cache = ensureCacheIsReady();
-
- final String key = createKey(KEY_PREFIX_ICON, iconUrl);
- if (key == null) {
- return;
- }
-
- editor = cache.edit(key);
- if (editor == null) {
- return;
- }
-
- outputStream = editor.newOutputStream(0);
- boolean success = bitmap.compress(Bitmap.CompressFormat.PNG, 100 /* quality; ignored. PNG is lossless */, outputStream);
-
- outputStream.close();
-
- if (success) {
- editor.commit();
- } else {
- editor.abort();
- }
- } catch (IOException e) {
- Log.w(LOGTAG, "IOException while accessing disk cache", e);
-
- abortSilently(editor);
- } finally {
- IOUtils.safeStreamClose(outputStream);
- }
- }
-
-
-
- /**
- * Get an icon for the icon URL from the cache. Returns null if no icon is cached for this URL.
- */
- @Nullable
- public IconResponse getIcon(String iconUrl) {
- InputStream inputStream = null;
-
- try {
- final DiskLruCache cache = ensureCacheIsReady();
-
- final String key = createKey(KEY_PREFIX_ICON, iconUrl);
- if (key == null) {
- return null;
- }
-
- if (cache.isClosed()) {
- throw new RuntimeException("CLOSED");
- }
-
- final DiskLruCache.Snapshot snapshot = cache.get(key);
- if (snapshot == null) {
- return null;
- }
-
- inputStream = snapshot.getInputStream(0);
-
- final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
- if (bitmap == null) {
- return null;
- }
-
- return IconResponse.createFromDisk(bitmap, iconUrl);
- } catch (IOException e) {
- Log.w(LOGTAG, "IOException while accessing disk cache", e);
- } finally {
- IOUtils.safeStreamClose(inputStream);
- }
-
- return null;
- }
-
- /**
- * Get the icon URL for this page URL. Returns null if no mapping is in the cache.
- */
- @Nullable
- public String getMapping(String pageUrl) {
- try {
- final DiskLruCache cache = ensureCacheIsReady();
-
- final String key = createKey(KEY_PREFIX_MAPPING, pageUrl);
- if (key == null) {
- return null;
- }
-
- DiskLruCache.Snapshot snapshot = cache.get(key);
- if (snapshot == null) {
- return null;
- }
-
- return snapshot.getString(0);
- } catch (IOException e) {
- Log.w(LOGTAG, "IOException while accessing disk cache", e);
- }
-
- return null;
- }
-
- /**
- * Remove all entries from this cache.
- */
- public void evictAll() {
- try {
- final DiskLruCache cache = ensureCacheIsReady();
-
- cache.delete();
-
- } catch (IOException e) {
- Log.w(LOGTAG, "IOException while accessing disk cache", e);
- }
- }
-
- /**
- * Create a key for this URL using the given prefix.
- *
- * The disk cache requires valid file names to be used as key. Therefore we hash the created key
- * (SHA-256).
- */
- @Nullable
- private String createKey(String prefix, String url) {
- try {
- // We use our own crypto implementation to avoid the penalty of loading the java crypto
- // framework.
- byte[] ctx = NativeCrypto.sha256init();
- if (ctx == null) {
- return null;
- }
-
- byte[] data = prefix.getBytes("UTF-8");
- NativeCrypto.sha256update(ctx, data, data.length);
-
- data = url.getBytes("UTF-8");
- NativeCrypto.sha256update(ctx, data, data.length);
- return Utils.byte2Hex(NativeCrypto.sha256finalize(ctx));
- } catch (NoClassDefFoundError | ExceptionInInitializerError error) {
- // We could not load libmozglue.so. Let's use Java's MessageDigest as fallback. We do
- // this primarily for our unit tests that can't load native libraries. On an device
- // we will have a lot of other problems if we can't load libmozglue.so
- try {
- MessageDigest md = MessageDigest.getInstance("SHA-256");
- md.update(prefix.getBytes("UTF-8"));
- md.update(url.getBytes("UTF-8"));
- return Utils.byte2Hex(md.digest());
- } catch (Exception e) {
- // Just give up. And let everyone know.
- throw new RuntimeException(e);
- }
- } catch (UnsupportedEncodingException e) {
- throw new AssertionError("Should not happen: Device does not understand UTF-8");
- }
- }
-
- private void abortSilently(DiskLruCache.Editor editor) {
- if (editor != null) {
- try {
- editor.abort();
- } catch (IOException e) {
- // Ignore
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/storage/FailureCache.java b/mobile/android/base/java/org/mozilla/gecko/icons/storage/FailureCache.java
deleted file mode 100644
index b45cb0fce..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/storage/FailureCache.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.storage;
-
-import android.os.SystemClock;
-import android.support.annotation.VisibleForTesting;
-import android.util.LruCache;
-
-/**
- * In-memory cache to remember URLs from which loading icons has failed recently.
- */
-public class FailureCache {
- /**
- * Retry loading failed icons after 4 hours.
- */
- private static final long FAILURE_RETRY_MILLISECONDS = 1000 * 60 * 60 * 4;
-
- private static final int MAX_ENTRIES = 25;
-
- private static FailureCache instance;
-
- public static synchronized FailureCache get() {
- if (instance == null) {
- instance = new FailureCache();
- }
-
- return instance;
- }
-
- private final LruCache<String, Long> cache;
-
- private FailureCache() {
- cache = new LruCache<>(MAX_ENTRIES);
- }
-
- /**
- * Remember this icon URL after loading from it (over the network) has failed.
- */
- public void rememberFailure(String iconUrl) {
- cache.put(iconUrl, SystemClock.elapsedRealtime());
- }
-
- /**
- * Has loading from this URL failed previously and recently?
- */
- public boolean isKnownFailure(String iconUrl) {
- synchronized (cache) {
- final Long failedAt = cache.get(iconUrl);
- if (failedAt == null) {
- return false;
- }
-
- if (failedAt + FAILURE_RETRY_MILLISECONDS < SystemClock.elapsedRealtime()) {
- // The wait time has passed and we can retry loading from this URL.
- cache.remove(iconUrl);
- return false;
- }
- }
-
- return true;
- }
-
- @VisibleForTesting
- public void evictAll() {
- cache.evictAll();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/icons/storage/MemoryStorage.java b/mobile/android/base/java/org/mozilla/gecko/icons/storage/MemoryStorage.java
deleted file mode 100644
index e0a96f7c7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/icons/storage/MemoryStorage.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.icons.storage;
-
-import android.graphics.Bitmap;
-import android.support.annotation.Nullable;
-import android.util.Log;
-import android.util.LruCache;
-
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.IconResponse;
-
-/**
- * Least Recently Used (LRU) memory cache for icons and the mappings from page URLs to icon URLs.
- */
-public class MemoryStorage {
- /**
- * Maximum number of items in the cache for mapping page URLs to icon URLs.
- */
- private static final int MAPPING_CACHE_SIZE = 500;
-
- private static MemoryStorage instance;
-
- public static synchronized MemoryStorage get() {
- if (instance == null) {
- instance = new MemoryStorage();
- }
-
- return instance;
- }
-
- /**
- * Class representing an cached icon. We store the original bitmap and the color in cache only.
- */
- private static class CacheEntry {
- private final Bitmap bitmap;
- private final int color;
-
- private CacheEntry(Bitmap bitmap, int color) {
- this.bitmap = bitmap;
- this.color = color;
- }
- }
-
- private final LruCache<String, CacheEntry> iconCache; // Guarded by 'this'
- private final LruCache<String, String> mappingCache; // Guarded by 'this'
-
- private MemoryStorage() {
- iconCache = new LruCache<String, CacheEntry>(calculateCacheSize()) {
- @Override
- protected int sizeOf(String key, CacheEntry value) {
- return value.bitmap.getByteCount() / 1024;
- }
- };
-
- mappingCache = new LruCache<>(MAPPING_CACHE_SIZE);
- }
-
- private int calculateCacheSize() {
- // Use a maximum of 1/8 of the available memory for storing cached icons.
- int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
- return maxMemory / 8;
- }
-
- /**
- * Store a mapping from page URL to icon URL in the cache.
- */
- public synchronized void putMapping(IconRequest request, String iconUrl) {
- mappingCache.put(request.getPageUrl(), iconUrl);
- }
-
- /**
- * Get the icon URL for this page URL. Returns null if no mapping is in the cache.
- */
- @Nullable
- public synchronized String getMapping(String pageUrl) {
- return mappingCache.get(pageUrl);
- }
-
- /**
- * Store an icon in the cache (uses the icon URL as key).
- */
- public synchronized void putIcon(String url, IconResponse response) {
- final CacheEntry entry = new CacheEntry(response.getBitmap(), response.getColor());
-
- iconCache.put(url, entry);
- }
-
- /**
- * Get an icon for the icon URL from the cache. Returns null if no icon is cached for this URL.
- */
- @Nullable
- public synchronized IconResponse getIcon(String iconUrl) {
- final CacheEntry entry = iconCache.get(iconUrl);
- if (entry == null) {
- return null;
- }
-
- return IconResponse.createFromMemory(entry.bitmap, iconUrl, entry.color);
- }
-
- /**
- * Remove all entries from this cache.
- */
- public synchronized void evictAll() {
- iconCache.evictAll();
- mappingCache.evictAll();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/javaaddons/JavaAddonManager.java b/mobile/android/base/java/org/mozilla/gecko/javaaddons/JavaAddonManager.java
deleted file mode 100644
index 33a97955f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/javaaddons/JavaAddonManager.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.javaaddons;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-import dalvik.system.DexClassLoader;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.util.GeckoEventListener;
-
-import java.io.File;
-import java.lang.reflect.Constructor;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * The manager for addon-provided Java code.
- *
- * Java code in addons can be loaded using the Dex:Load message, and unloaded
- * via the Dex:Unload message. Addon classes loaded are checked for a constructor
- * that takes a Map&lt;String, Handler.Callback&gt;. If such a constructor
- * exists, it is called and the objects populated into the map by the constructor
- * are registered as event listeners. If no such constructor exists, the default
- * constructor is invoked instead.
- *
- * Note: The Map and Handler.Callback classes were used in this API definition
- * rather than defining a custom class. This was done explicitly so that the
- * addon code can be compiled against the android.jar provided in the Android
- * SDK, rather than having to be compiled against Fennec source code.
- *
- * The Handler.Callback instances provided (as described above) are invoked with
- * Message objects when the corresponding events are dispatched. The Bundle
- * object attached to the Message will contain the "primitive" values from the
- * JSON of the event. ("primitive" includes bool/int/long/double/String). If
- * the addon callback wishes to synchronously return a value back to the event
- * dispatcher, they can do so by inserting the response string into the bundle
- * under the key "response".
- */
-public class JavaAddonManager implements GeckoEventListener {
- private static final String LOGTAG = "GeckoJavaAddonManager";
-
- private static JavaAddonManager sInstance;
-
- private final EventDispatcher mDispatcher;
- private final Map<String, Map<String, GeckoEventListener>> mAddonCallbacks;
-
- private Context mApplicationContext;
-
- public static JavaAddonManager getInstance() {
- if (sInstance == null) {
- sInstance = new JavaAddonManager();
- }
- return sInstance;
- }
-
- private JavaAddonManager() {
- mDispatcher = EventDispatcher.getInstance();
- mAddonCallbacks = new HashMap<String, Map<String, GeckoEventListener>>();
- }
-
- public void init(Context applicationContext) {
- if (mApplicationContext != null) {
- // we've already done this registration. don't do it again
- return;
- }
- mApplicationContext = applicationContext;
- mDispatcher.registerGeckoThreadListener(this,
- "Dex:Load",
- "Dex:Unload");
- JavaAddonManagerV1.getInstance().init(applicationContext);
- }
-
- @Override
- public void handleMessage(String event, JSONObject message) {
- try {
- if (event.equals("Dex:Load")) {
- String zipFile = message.getString("zipfile");
- String implClass = message.getString("impl");
- Log.d(LOGTAG, "Attempting to load classes.dex file from " + zipFile + " and instantiate " + implClass);
- try {
- File tmpDir = mApplicationContext.getDir("dex", 0);
- DexClassLoader loader = new DexClassLoader(zipFile, tmpDir.getAbsolutePath(), null, mApplicationContext.getClassLoader());
- Class<?> c = loader.loadClass(implClass);
- try {
- Constructor<?> constructor = c.getDeclaredConstructor(Map.class);
- Map<String, Handler.Callback> callbacks = new HashMap<String, Handler.Callback>();
- constructor.newInstance(callbacks);
- registerCallbacks(zipFile, callbacks);
- } catch (NoSuchMethodException nsme) {
- Log.d(LOGTAG, "Did not find constructor with parameters Map<String, Handler.Callback>. Falling back to default constructor...");
- // fallback for instances with no constructor that takes a Map<String, Handler.Callback>
- c.newInstance();
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Unable to load dex successfully", e);
- }
- } else if (event.equals("Dex:Unload")) {
- String zipFile = message.getString("zipfile");
- unregisterCallbacks(zipFile);
- }
- } catch (JSONException e) {
- Log.e(LOGTAG, "Exception handling message [" + event + "]:", e);
- }
- }
-
- private void registerCallbacks(String zipFile, Map<String, Handler.Callback> callbacks) {
- Map<String, GeckoEventListener> addonCallbacks = mAddonCallbacks.get(zipFile);
- if (addonCallbacks != null) {
- Log.w(LOGTAG, "Found pre-existing callbacks for zipfile [" + zipFile + "]; aborting re-registration!");
- return;
- }
- addonCallbacks = new HashMap<String, GeckoEventListener>();
- for (String event : callbacks.keySet()) {
- CallbackWrapper wrapper = new CallbackWrapper(callbacks.get(event));
- mDispatcher.registerGeckoThreadListener(wrapper, event);
- addonCallbacks.put(event, wrapper);
- }
- mAddonCallbacks.put(zipFile, addonCallbacks);
- }
-
- private void unregisterCallbacks(String zipFile) {
- Map<String, GeckoEventListener> callbacks = mAddonCallbacks.remove(zipFile);
- if (callbacks == null) {
- Log.w(LOGTAG, "Attempting to unregister callbacks from zipfile [" + zipFile + "] which has no callbacks registered.");
- return;
- }
- for (String event : callbacks.keySet()) {
- mDispatcher.unregisterGeckoThreadListener(callbacks.get(event), event);
- }
- }
-
- private static class CallbackWrapper implements GeckoEventListener {
- private final Handler.Callback mDelegate;
- private Bundle mBundle;
-
- CallbackWrapper(Handler.Callback delegate) {
- mDelegate = delegate;
- }
-
- private Bundle jsonToBundle(JSONObject json) {
- // XXX right now we only support primitive types;
- // we don't recurse down into JSONArray or JSONObject instances
- Bundle b = new Bundle();
- for (Iterator<?> keys = json.keys(); keys.hasNext(); ) {
- try {
- String key = (String)keys.next();
- Object value = json.get(key);
- if (value instanceof Integer) {
- b.putInt(key, (Integer)value);
- } else if (value instanceof String) {
- b.putString(key, (String)value);
- } else if (value instanceof Boolean) {
- b.putBoolean(key, (Boolean)value);
- } else if (value instanceof Long) {
- b.putLong(key, (Long)value);
- } else if (value instanceof Double) {
- b.putDouble(key, (Double)value);
- }
- } catch (JSONException e) {
- Log.d(LOGTAG, "Error during JSON->bundle conversion", e);
- }
- }
- return b;
- }
-
- @Override
- public void handleMessage(String event, JSONObject json) {
- try {
- if (mBundle != null) {
- Log.w(LOGTAG, "Event [" + event + "] handler is re-entrant; response messages may be lost");
- }
- mBundle = jsonToBundle(json);
- Message msg = new Message();
- msg.setData(mBundle);
- mDelegate.handleMessage(msg);
-
- JSONObject obj = new JSONObject();
- obj.put("response", mBundle.getString("response"));
- EventDispatcher.sendResponse(json, obj);
- mBundle = null;
- } catch (Exception e) {
- Log.e(LOGTAG, "Caught exception thrown from wrapped addon message handler", e);
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/javaaddons/JavaAddonManagerV1.java b/mobile/android/base/java/org/mozilla/gecko/javaaddons/JavaAddonManagerV1.java
deleted file mode 100644
index f361773ca..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/javaaddons/JavaAddonManagerV1.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.javaaddons;
-
-import android.content.Context;
-import android.util.Log;
-import android.util.Pair;
-import dalvik.system.DexClassLoader;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.util.GeckoJarReader;
-import org.mozilla.gecko.util.GeckoRequest;
-import org.mozilla.gecko.util.NativeEventListener;
-import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.javaaddons.JavaAddonInterfaceV1;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-import java.util.Map;
-
-public class JavaAddonManagerV1 implements NativeEventListener {
- private static final String LOGTAG = "GeckoJavaAddonMgrV1";
- public static final String MESSAGE_LOAD = "JavaAddonManagerV1:Load";
- public static final String MESSAGE_UNLOAD = "JavaAddonManagerV1:Unload";
-
- private static JavaAddonManagerV1 sInstance;
-
- // Protected by static synchronized.
- private Context mApplicationContext;
-
- private final org.mozilla.gecko.EventDispatcher mDispatcher;
-
- // Protected by synchronized (this).
- private final Map<String, EventDispatcherImpl> mGUIDToDispatcherMap = new HashMap<>();
-
- public static synchronized JavaAddonManagerV1 getInstance() {
- if (sInstance == null) {
- sInstance = new JavaAddonManagerV1();
- }
- return sInstance;
- }
-
- private JavaAddonManagerV1() {
- mDispatcher = org.mozilla.gecko.EventDispatcher.getInstance();
- }
-
- public synchronized void init(Context applicationContext) {
- if (mApplicationContext != null) {
- // We've already registered; don't register again.
- return;
- }
- mApplicationContext = applicationContext;
- mDispatcher.registerGeckoThreadListener(this,
- MESSAGE_LOAD,
- MESSAGE_UNLOAD);
- }
-
- protected String getExtension(String filename) {
- if (filename == null) {
- return "";
- }
- final int last = filename.lastIndexOf(".");
- if (last < 0) {
- return "";
- }
- return filename.substring(last);
- }
-
- protected synchronized EventDispatcherImpl registerNewInstance(String classname, String filename)
- throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, IOException {
- Log.d(LOGTAG, "Attempting to instantiate " + classname + "from filename " + filename);
-
- // It's important to maintain the extension, either .dex, .apk, .jar.
- final String extension = getExtension(filename);
- final File dexFile = GeckoJarReader.extractStream(mApplicationContext, filename, mApplicationContext.getCacheDir(), "." + extension);
- try {
- if (dexFile == null) {
- throw new IOException("Could not find file " + filename);
- }
- final File tmpDir = mApplicationContext.getDir("dex", 0); // We'd prefer getCodeCacheDir but it's API 21+.
- final DexClassLoader loader = new DexClassLoader(dexFile.getAbsolutePath(), tmpDir.getAbsolutePath(), null, mApplicationContext.getClassLoader());
- final Class<?> c = loader.loadClass(classname);
- final Constructor<?> constructor = c.getDeclaredConstructor(Context.class, JavaAddonInterfaceV1.EventDispatcher.class);
- final String guid = Utils.generateGuid();
- final EventDispatcherImpl dispatcher = new EventDispatcherImpl(guid, filename);
- final Object instance = constructor.newInstance(mApplicationContext, dispatcher);
- mGUIDToDispatcherMap.put(guid, dispatcher);
- return dispatcher;
- } finally {
- // DexClassLoader writes an optimized version, so we can get rid of our temporary extracted version.
- if (dexFile != null) {
- dexFile.delete();
- }
- }
- }
-
- @Override
- public synchronized void handleMessage(String event, NativeJSObject message, org.mozilla.gecko.util.EventCallback callback) {
- try {
- switch (event) {
- case MESSAGE_LOAD: {
- if (callback == null) {
- throw new IllegalArgumentException("callback must not be null");
- }
- final String classname = message.getString("classname");
- final String filename = message.getString("filename");
- final EventDispatcherImpl dispatcher = registerNewInstance(classname, filename);
- callback.sendSuccess(dispatcher.guid);
- }
- break;
- case MESSAGE_UNLOAD: {
- if (callback == null) {
- throw new IllegalArgumentException("callback must not be null");
- }
- final String guid = message.getString("guid");
- final EventDispatcherImpl dispatcher = mGUIDToDispatcherMap.remove(guid);
- if (dispatcher == null) {
- Log.w(LOGTAG, "Attempting to unload addon with unknown associated dispatcher; ignoring.");
- callback.sendSuccess(false);
- }
- dispatcher.unregisterAllEventListeners();
- callback.sendSuccess(true);
- }
- break;
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception handling message [" + event + "]", e);
- if (callback != null) {
- callback.sendError("Exception handling message [" + event + "]: " + e.toString());
- }
- }
- }
-
- /**
- * An event dispatcher is tied to a single Java Addon instance. It serves to prefix all
- * messages with its unique GUID.
- * <p/>
- * Curiously, the dispatcher does not hold a direct reference to its add-on instance. It will
- * likely hold indirect instances through its wrapping map, since the instance will probably
- * register event listeners that hold a reference to itself. When these listeners are
- * unregistered, any link will be broken, allowing the instances to be garbage collected.
- */
- private class EventDispatcherImpl implements JavaAddonInterfaceV1.EventDispatcher {
- private final String guid;
- private final String dexFileName;
-
- // Protected by synchronized (this).
- private final Map<JavaAddonInterfaceV1.EventListener, Pair<NativeEventListener, String[]>> mListenerToWrapperMap = new IdentityHashMap<>();
-
- public EventDispatcherImpl(String guid, String dexFileName) {
- this.guid = guid;
- this.dexFileName = dexFileName;
- }
-
- protected class ListenerWrapper implements NativeEventListener {
- private final JavaAddonInterfaceV1.EventListener listener;
-
- public ListenerWrapper(JavaAddonInterfaceV1.EventListener listener) {
- this.listener = listener;
- }
-
- @Override
- public void handleMessage(String prefixedEvent, NativeJSObject message, final org.mozilla.gecko.util.EventCallback callback) {
- if (!prefixedEvent.startsWith(guid + ":")) {
- return;
- }
- final String event = prefixedEvent.substring(guid.length() + 1); // Skip "guid:".
- try {
- JavaAddonInterfaceV1.EventCallback callbackAdapter = null;
- if (callback != null) {
- callbackAdapter = new JavaAddonInterfaceV1.EventCallback() {
- @Override
- public void sendSuccess(Object response) {
- callback.sendSuccess(response);
- }
-
- @Override
- public void sendError(Object response) {
- callback.sendError(response);
- }
- };
- }
- final JSONObject json = new JSONObject(message.toString());
- listener.handleMessage(mApplicationContext, event, json, callbackAdapter);
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception handling message [" + prefixedEvent + "]", e);
- if (callback != null) {
- callback.sendError("Got exception handling message [" + prefixedEvent + "]: " + e.toString());
- }
- }
- }
- }
-
- @Override
- public synchronized void registerEventListener(final JavaAddonInterfaceV1.EventListener listener, String... events) {
- if (mListenerToWrapperMap.containsKey(listener)) {
- Log.e(LOGTAG, "Attempting to register listener which is already registered; ignoring.");
- return;
- }
-
- final NativeEventListener listenerWrapper = new ListenerWrapper(listener);
-
- final String[] prefixedEvents = new String[events.length];
- for (int i = 0; i < events.length; i++) {
- prefixedEvents[i] = this.guid + ":" + events[i];
- }
- mDispatcher.registerGeckoThreadListener(listenerWrapper, prefixedEvents);
- mListenerToWrapperMap.put(listener, new Pair<>(listenerWrapper, prefixedEvents));
- }
-
- @Override
- public synchronized void unregisterEventListener(final JavaAddonInterfaceV1.EventListener listener) {
- final Pair<NativeEventListener, String[]> pair = mListenerToWrapperMap.remove(listener);
- if (pair == null) {
- Log.e(LOGTAG, "Attempting to unregister listener which is not registered; ignoring.");
- return;
- }
- mDispatcher.unregisterGeckoThreadListener(pair.first, pair.second);
- }
-
-
- protected synchronized void unregisterAllEventListeners() {
- // Unregister everything, then forget everything.
- for (Pair<NativeEventListener, String[]> pair : mListenerToWrapperMap.values()) {
- mDispatcher.unregisterGeckoThreadListener(pair.first, pair.second);
- }
- mListenerToWrapperMap.clear();
- }
-
- @Override
- public void sendRequestToGecko(final String event, final JSONObject message, final JavaAddonInterfaceV1.RequestCallback callback) {
- final String prefixedEvent = guid + ":" + event;
- GeckoAppShell.sendRequestToGecko(new GeckoRequest(prefixedEvent, message) {
- @Override
- public void onResponse(NativeJSObject nativeJSObject) {
- if (callback == null) {
- // Nothing to do.
- return;
- }
- try {
- final JSONObject json = new JSONObject(nativeJSObject.toString());
- callback.onResponse(GeckoAppShell.getContext(), json);
- } catch (JSONException e) {
- // No way to report failure.
- Log.e(LOGTAG, "Exception handling response to request [" + event + "]:", e);
- }
- }
- });
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/lwt/LightweightTheme.java b/mobile/android/base/java/org/mozilla/gecko/lwt/LightweightTheme.java
deleted file mode 100644
index 0f27c1feb..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/lwt/LightweightTheme.java
+++ /dev/null
@@ -1,455 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.lwt;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.json.JSONObject;
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.gfx.BitmapUtils;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.WindowUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.ThreadUtils.AssertBehavior;
-
-import android.app.Application;
-import android.content.SharedPreferences;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Shader;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewParent;
-
-public class LightweightTheme implements GeckoEventListener {
- private static final String LOGTAG = "GeckoLightweightTheme";
-
- private static final String PREFS_URL = "lightweightTheme.headerURL";
- private static final String PREFS_COLOR = "lightweightTheme.color";
-
- private static final String ASSETS_PREFIX = "resource://android/assets/";
-
- private final Application mApplication;
-
- private Bitmap mBitmap;
- private int mColor;
- private boolean mIsLight;
-
- public static interface OnChangeListener {
- // The View should change its background/text color.
- public void onLightweightThemeChanged();
-
- // The View should reset to its default background/text color.
- public void onLightweightThemeReset();
- }
-
- private final List<OnChangeListener> mListeners;
-
- class LightweightThemeRunnable implements Runnable {
- private String mHeaderURL;
- private String mColor;
-
- private String mSavedURL;
- private String mSavedColor;
-
- LightweightThemeRunnable() {
- }
-
- LightweightThemeRunnable(final String headerURL, final String color) {
- mHeaderURL = headerURL;
- mColor = color;
- }
-
- private void loadFromPrefs() {
- SharedPreferences prefs = GeckoSharedPrefs.forProfile(mApplication);
- mSavedURL = prefs.getString(PREFS_URL, null);
- mSavedColor = prefs.getString(PREFS_COLOR, null);
- }
-
- private void saveToPrefs() {
- GeckoSharedPrefs.forProfile(mApplication)
- .edit()
- .putString(PREFS_URL, mHeaderURL)
- .putString(PREFS_COLOR, mColor)
- .apply();
-
- // Let's keep the saved data in sync.
- mSavedURL = mHeaderURL;
- mSavedColor = mColor;
- }
-
- @Override
- public void run() {
- // Load the data from preferences, if it exists.
- loadFromPrefs();
-
- if (TextUtils.isEmpty(mHeaderURL)) {
- // mHeaderURL is null is this is the early startup path. Use
- // the saved values, if we have any.
- mHeaderURL = mSavedURL;
- mColor = mSavedColor;
- if (TextUtils.isEmpty(mHeaderURL)) {
- // We don't have any saved values, so we probably don't have
- // any lightweight theme set yet.
- return;
- }
- } else if (TextUtils.equals(mHeaderURL, mSavedURL)) {
- // If we are already using the given header, just return
- // without doing any work.
- return;
- } else {
- // mHeaderURL and mColor probably need to be saved if we get here.
- saveToPrefs();
- }
-
- String croppedURL = mHeaderURL;
- int mark = croppedURL.indexOf('?');
- if (mark != -1) {
- croppedURL = croppedURL.substring(0, mark);
- }
-
- if (croppedURL.startsWith(ASSETS_PREFIX)) {
- onBitmapLoaded(loadFromAssets(croppedURL));
- } else {
- onBitmapLoaded(BitmapUtils.decodeUrl(croppedURL));
- }
- }
-
- private Bitmap loadFromAssets(String url) {
- InputStream stream = null;
-
- try {
- stream = mApplication.getAssets().open(url.substring(ASSETS_PREFIX.length()));
- return BitmapFactory.decodeStream(stream);
- } catch (IOException e) {
- return null;
- } finally {
- if (stream != null) {
- try {
- stream.close();
- } catch (IOException e) { }
- }
- }
- }
-
- private void onBitmapLoaded(final Bitmap bitmap) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- setLightweightTheme(bitmap, mColor);
- }
- });
- }
- }
-
- public LightweightTheme(Application application) {
- mApplication = application;
- mListeners = new ArrayList<OnChangeListener>();
-
- // unregister isn't needed as the lifetime is same as the application.
- EventDispatcher.getInstance().registerGeckoThreadListener(this,
- "LightweightTheme:Update",
- "LightweightTheme:Disable");
-
- ThreadUtils.postToBackgroundThread(new LightweightThemeRunnable());
- }
-
- public void addListener(final OnChangeListener listener) {
- // Don't inform the listeners that attached late.
- // Their onLayout() will take care of them before their onDraw();
- mListeners.add(listener);
- }
-
- public void removeListener(OnChangeListener listener) {
- mListeners.remove(listener);
- }
-
- @Override
- public void handleMessage(String event, JSONObject message) {
- try {
- if (event.equals("LightweightTheme:Update")) {
- JSONObject lightweightTheme = message.getJSONObject("data");
- final String headerURL = lightweightTheme.getString("headerURL");
- final String color = lightweightTheme.optString("accentcolor");
-
- ThreadUtils.postToBackgroundThread(new LightweightThemeRunnable(headerURL, color));
- } else if (event.equals("LightweightTheme:Disable")) {
- // Clear the saved data when a theme is disabled.
- // Called on the Gecko thread, but should be very lightweight.
- clearPrefs();
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- resetLightweightTheme();
- }
- });
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
- }
- }
-
- /**
- * Clear the data stored in preferences for fast path loading during startup
- */
- private void clearPrefs() {
- GeckoSharedPrefs.forProfile(mApplication)
- .edit()
- .remove(PREFS_URL)
- .remove(PREFS_COLOR)
- .apply();
- }
-
- /**
- * Set a new lightweight theme with the given bitmap.
- * Note: This should be called on the UI thread to restrict accessing the
- * bitmap to a single thread.
- *
- * @param bitmap The bitmap used for the lightweight theme.
- * @param color The background/accent color used for the lightweight theme.
- */
- private void setLightweightTheme(Bitmap bitmap, String color) {
- if (bitmap == null || bitmap.getWidth() == 0 || bitmap.getHeight() == 0) {
- mBitmap = null;
- return;
- }
-
- // Get the max display dimension so we can crop or expand the theme.
- final int maxWidth = WindowUtils.getLargestDimension(mApplication);
-
- // The lightweight theme image's width and height.
- final int bitmapWidth = bitmap.getWidth();
- final int bitmapHeight = bitmap.getHeight();
-
- try {
- mColor = Color.parseColor(color);
- } catch (Exception e) {
- // Malformed or missing color.
- // Default to TRANSPARENT.
- mColor = Color.TRANSPARENT;
- }
-
- // Calculate the luminance to determine if it's a light or a dark theme.
- double luminance = (0.2125 * ((mColor & 0x00FF0000) >> 16)) +
- (0.7154 * ((mColor & 0x0000FF00) >> 8)) +
- (0.0721 * (mColor & 0x000000FF));
- mIsLight = luminance > 110;
-
- // The bitmap image might be smaller than the device's width.
- // If it's smaller, fill the extra space on the left with the dominant color.
- if (bitmapWidth >= maxWidth) {
- mBitmap = Bitmap.createBitmap(bitmap, bitmapWidth - maxWidth, 0, maxWidth, bitmapHeight);
- } else {
- Paint paint = new Paint();
- paint.setAntiAlias(true);
-
- // Create a bigger image that can fill the device width.
- // By creating a canvas for the bitmap, anything drawn on the canvas
- // will be drawn on the bitmap.
- mBitmap = Bitmap.createBitmap(maxWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(mBitmap);
-
- // Fill the canvas with dominant color.
- canvas.drawColor(mColor);
-
- // The image should be top-right aligned.
- Rect rect = new Rect();
- Gravity.apply(Gravity.TOP | Gravity.RIGHT,
- bitmapWidth,
- bitmapHeight,
- new Rect(0, 0, maxWidth, bitmapHeight),
- rect);
-
- // Draw the bitmap.
- canvas.drawBitmap(bitmap, null, rect, paint);
- }
-
- for (OnChangeListener listener : mListeners) {
- listener.onLightweightThemeChanged();
- }
- }
-
- /**
- * Reset the lightweight theme.
- * Note: This should be called on the UI thread to restrict accessing the
- * bitmap to a single thread.
- */
- private void resetLightweightTheme() {
- ThreadUtils.assertOnUiThread(AssertBehavior.NONE);
- if (mBitmap == null) {
- return;
- }
-
- // Reset the bitmap.
- mBitmap = null;
-
- for (OnChangeListener listener : mListeners) {
- listener.onLightweightThemeReset();
- }
- }
-
- /**
- * A lightweight theme is enabled only if there is an active bitmap.
- *
- * @return True if the theme is enabled.
- */
- public boolean isEnabled() {
- return (mBitmap != null);
- }
-
- /**
- * Based on the luminance of the domanint color, a theme is classified as light or dark.
- *
- * @return True if the theme is light.
- */
- public boolean isLightTheme() {
- return mIsLight;
- }
-
- /**
- * Crop the image based on the position of the view on the window.
- * Either the View or one of its ancestors might have scrolled or translated.
- * This value should be taken into account while mapping the View to the Bitmap.
- *
- * @param view The view requesting a cropped bitmap.
- */
- private Bitmap getCroppedBitmap(View view) {
- if (mBitmap == null || view == null) {
- return null;
- }
-
- // Get the global position of the view on the entire screen.
- Rect rect = new Rect();
- view.getGlobalVisibleRect(rect);
-
- // Get the activity's window position. This does an IPC call, may be expensive.
- Rect window = new Rect();
- view.getWindowVisibleDisplayFrame(window);
-
- // Calculate the coordinates for the cropped bitmap.
- int screenWidth = view.getContext().getResources().getDisplayMetrics().widthPixels;
- int left = mBitmap.getWidth() - screenWidth + rect.left;
- int right = mBitmap.getWidth() - screenWidth + rect.right;
- int top = rect.top - window.top;
- int bottom = rect.bottom - window.top;
-
- int offsetX = 0;
- int offsetY = 0;
-
- // Find if this view or any of its ancestors has been translated or scrolled.
- ViewParent parent;
- View curView = view;
- do {
- offsetX += (int) curView.getTranslationX() - curView.getScrollX();
- offsetY += (int) curView.getTranslationY() - curView.getScrollY();
-
- parent = curView.getParent();
-
- if (parent instanceof View) {
- curView = (View) parent;
- }
-
- } while (parent instanceof View);
-
- // Adjust the coordinates for the offset.
- left -= offsetX;
- right -= offsetX;
- top -= offsetY;
- bottom -= offsetY;
-
- // The either the required height may be less than the available image height or more than it.
- // If the height required is more, crop only the available portion on the image.
- int width = right - left;
- int height = (bottom > mBitmap.getHeight() ? mBitmap.getHeight() - top : bottom - top);
-
- // There is a chance that the view is not visible or doesn't fall within the phone's size.
- // In this case, 'rect' will have all values as '0'. Hence 'top' and 'bottom' may be negative,
- // and createBitmap() will fail.
- // The view will get a background in its next layout pass.
- try {
- return Bitmap.createBitmap(mBitmap, left, top, width, height);
- } catch (Exception e) {
- return null;
- }
- }
-
- /**
- * Converts the cropped bitmap to a BitmapDrawable and returns the same.
- *
- * @param view The view for which a background drawable is required.
- * @return Either the cropped bitmap as a Drawable or null.
- */
- public Drawable getDrawable(View view) {
- Bitmap bitmap = getCroppedBitmap(view);
- if (bitmap == null) {
- return null;
- }
-
- BitmapDrawable drawable = new BitmapDrawable(view.getContext().getResources(), bitmap);
- drawable.setGravity(Gravity.TOP | Gravity.RIGHT);
- drawable.setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
- return drawable;
- }
-
- /**
- * Converts the cropped bitmap to a LightweightThemeDrawable, placing it over the dominant color.
- *
- * @param view The view for which a background drawable is required.
- * @return Either the cropped bitmap as a Drawable or null.
- */
- public LightweightThemeDrawable getColorDrawable(View view) {
- return getColorDrawable(view, mColor, false);
- }
-
- /**
- * Converts the cropped bitmap to a LightweightThemeDrawable, placing it over the required color.
- *
- * @param view The view for which a background drawable is required.
- * @param color The color over which the drawable should be drawn.
- * @return Either the cropped bitmap as a Drawable or null.
- */
- public LightweightThemeDrawable getColorDrawable(View view, int color) {
- return getColorDrawable(view, color, false);
- }
-
- /**
- * Converts the cropped bitmap to a LightweightThemeDrawable, placing it over the required color.
- *
- * @param view The view for which a background drawable is required.
- * @param color The color over which the drawable should be drawn.
- * @param needsDominantColor A layer of dominant color is needed or not.
- * @return Either the cropped bitmap as a Drawable or null.
- */
- public LightweightThemeDrawable getColorDrawable(View view, int color, boolean needsDominantColor) {
- Bitmap bitmap = getCroppedBitmap(view);
- if (bitmap == null) {
- return null;
- }
-
- LightweightThemeDrawable drawable = new LightweightThemeDrawable(view.getContext().getResources(), bitmap);
- if (needsDominantColor) {
- drawable.setColorWithFilter(color, (mColor & 0x22FFFFFF));
- } else {
- drawable.setColor(color);
- }
-
- return drawable;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/lwt/LightweightThemeDrawable.java b/mobile/android/base/java/org/mozilla/gecko/lwt/LightweightThemeDrawable.java
deleted file mode 100644
index c0ae6eaed..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/lwt/LightweightThemeDrawable.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.lwt;
-
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.ComposeShader;
-import android.graphics.LinearGradient;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
-import android.graphics.Rect;
-import android.graphics.Shader;
-import android.graphics.drawable.Drawable;
-
-/**
- * A special drawable used with lightweight themes. This draws a color
- * (with an optional color-filter) and a bitmap (with a linear gradient
- * to specify the alpha) in order.
- */
-public class LightweightThemeDrawable extends Drawable {
- private final Paint mPaint;
- private Paint mColorPaint;
-
- private final Bitmap mBitmap;
- private final Resources mResources;
-
- private int mStartColor;
- private int mEndColor;
-
- public LightweightThemeDrawable(Resources resources, Bitmap bitmap) {
- mBitmap = bitmap;
- mResources = resources;
-
- mPaint = new Paint();
- mPaint.setAntiAlias(true);
- mPaint.setStrokeWidth(0.0f);
- }
-
- @Override
- protected void onBoundsChange(Rect bounds) {
- super.onBoundsChange(bounds);
- initializeBitmapShader();
- }
-
- @Override
- public void draw(Canvas canvas) {
- // Draw the colors, if available.
- if (mColorPaint != null) {
- canvas.drawPaint(mColorPaint);
- }
-
- // Draw the bitmap.
- canvas.drawPaint(mPaint);
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.TRANSLUCENT;
- }
-
- @Override
- public void setAlpha(int alpha) {
- // A StateListDrawable will reset the alpha value with 255.
- // We cannot use to be the bitmap alpha.
- mPaint.setAlpha(alpha);
- }
-
- @Override
- public void setColorFilter(ColorFilter filter) {
- mPaint.setColorFilter(filter);
- }
-
- /**
- * Creates a paint that paint a particular color.
- *
- * Note that the given color should include an alpha value.
- *
- * @param color The color to be painted.
- */
- public void setColor(int color) {
- mColorPaint = new Paint(mPaint);
- mColorPaint.setColor(color);
- }
-
- /**
- * Creates a paint that paint a particular color, and a filter for the color.
- *
- * Note that the given color should include an alpha value.
- *
- * @param color The color to be painted.
- * @param filter The filter color to be applied using SRC_OVER mode.
- */
- public void setColorWithFilter(int color, int filter) {
- mColorPaint = new Paint(mPaint);
- mColorPaint.setColor(color);
- mColorPaint.setColorFilter(new PorterDuffColorFilter(filter, PorterDuff.Mode.SRC_OVER));
- }
-
- /**
- * Set the alpha for the linear gradient used with the bitmap's shader.
- *
- * @param startAlpha The starting alpha (0..255) value to be applied to the LinearGradient.
- * @param startAlpha The ending alpha (0..255) value to be applied to the LinearGradient.
- */
- public void setAlpha(int startAlpha, int endAlpha) {
- mStartColor = startAlpha << 24;
- mEndColor = endAlpha << 24;
- initializeBitmapShader();
- }
-
- private void initializeBitmapShader() {
- // A bitmap-shader to draw the bitmap.
- // Clamp mode will repeat the last row of pixels.
- // Hence its better to have an endAlpha of 0 for the linear-gradient.
- BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
-
- // A linear-gradient to specify the opacity of the bitmap.
- LinearGradient gradient = new LinearGradient(0, 0, 0, mBitmap.getHeight(), mStartColor, mEndColor, Shader.TileMode.CLAMP);
-
- // Make a combined shader -- a performance win.
- // The linear-gradient is the 'SRC' and the bitmap-shader is the 'DST'.
- // Drawing the DST in the SRC will provide the opacity.
- mPaint.setShader(new ComposeShader(bitmapShader, gradient, PorterDuff.Mode.DST_IN));
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/mdns/MulticastDNSManager.java b/mobile/android/base/java/org/mozilla/gecko/mdns/MulticastDNSManager.java
deleted file mode 100644
index 6f23790b9..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/mdns/MulticastDNSManager.java
+++ /dev/null
@@ -1,535 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.mdns;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.GeckoRequest;
-import org.mozilla.gecko.util.NativeEventListener;
-import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.net.nsd.NsdManager;
-import android.net.nsd.NsdServiceInfo;
-import android.support.annotation.UiThread;
-import android.util.Log;
-
-import java.net.InetAddress;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * This class is the bridge between XPCOM mDNS module and NsdManager.
- *
- * @See nsIDNSServiceDiscovery.idl
- */
-public abstract class MulticastDNSManager {
- protected static final String LOGTAG = "GeckoMDNSManager";
- private static MulticastDNSManager instance = null;
-
- public static MulticastDNSManager getInstance(final Context context) {
- if (instance == null) {
- instance = new DummyMulticastDNSManager();
- }
- return instance;
- }
-
- public abstract void init();
- public abstract void tearDown();
-}
-
-/**
- * Mix-in class for MulticastDNSManagers to call EventDispatcher.
- */
-class MulticastDNSEventManager {
- private NativeEventListener mListener = null;
- private boolean mEventsRegistered = false;
-
- MulticastDNSEventManager(NativeEventListener listener) {
- mListener = listener;
- }
-
- @UiThread
- public void init() {
- ThreadUtils.assertOnUiThread();
-
- if (mEventsRegistered || mListener == null) {
- return;
- }
-
- registerEvents();
- mEventsRegistered = true;
- }
-
- @UiThread
- public void tearDown() {
- ThreadUtils.assertOnUiThread();
-
- if (!mEventsRegistered || mListener == null) {
- return;
- }
-
- unregisterEvents();
- mEventsRegistered = false;
- }
-
- private void registerEvents() {
- EventDispatcher.getInstance().registerGeckoThreadListener(mListener,
- "NsdManager:DiscoverServices",
- "NsdManager:StopServiceDiscovery",
- "NsdManager:RegisterService",
- "NsdManager:UnregisterService",
- "NsdManager:ResolveService");
- }
-
- private void unregisterEvents() {
- EventDispatcher.getInstance().unregisterGeckoThreadListener(mListener,
- "NsdManager:DiscoverServices",
- "NsdManager:StopServiceDiscovery",
- "NsdManager:RegisterService",
- "NsdManager:UnregisterService",
- "NsdManager:ResolveService");
- }
-}
-
-class NsdMulticastDNSManager extends MulticastDNSManager implements NativeEventListener {
- private final NsdManager nsdManager;
- private final MulticastDNSEventManager mEventManager;
- private Map<String, DiscoveryListener> mDiscoveryListeners = null;
- private Map<String, RegistrationListener> mRegistrationListeners = null;
-
- @TargetApi(16)
- public NsdMulticastDNSManager(final Context context) {
- nsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
- mEventManager = new MulticastDNSEventManager(this);
- mDiscoveryListeners = new ConcurrentHashMap<String, DiscoveryListener>();
- mRegistrationListeners = new ConcurrentHashMap<String, RegistrationListener>();
- }
-
- @Override
- public void init() {
- mEventManager.init();
- }
-
- @Override
- public void tearDown() {
- mDiscoveryListeners.clear();
- mRegistrationListeners.clear();
-
- mEventManager.tearDown();
- }
-
- @Override
- public void handleMessage(final String event, final NativeJSObject message, final EventCallback callback) {
- Log.v(LOGTAG, "handleMessage: " + event);
-
- switch (event) {
- case "NsdManager:DiscoverServices": {
- DiscoveryListener listener = new DiscoveryListener(nsdManager);
- listener.discoverServices(message.getString("serviceType"), callback);
- mDiscoveryListeners.put(message.getString("uniqueId"), listener);
- break;
- }
- case "NsdManager:StopServiceDiscovery": {
- String uuid = message.getString("uniqueId");
- DiscoveryListener listener = mDiscoveryListeners.remove(uuid);
- if (listener == null) {
- Log.e(LOGTAG, "DiscoveryListener " + uuid + " was not found.");
- return;
- }
- listener.stopServiceDiscovery(callback);
- break;
- }
- case "NsdManager:RegisterService": {
- RegistrationListener listener = new RegistrationListener(nsdManager);
- listener.registerService(message.getInt("port"),
- message.optString("serviceName", android.os.Build.MODEL),
- message.getString("serviceType"),
- parseAttributes(message.optObjectArray("attributes", null)),
- callback);
- mRegistrationListeners.put(message.getString("uniqueId"), listener);
- break;
- }
- case "NsdManager:UnregisterService": {
- String uuid = message.getString("uniqueId");
- RegistrationListener listener = mRegistrationListeners.remove(uuid);
- if (listener == null) {
- Log.e(LOGTAG, "RegistrationListener " + uuid + " was not found.");
- return;
- }
- listener.unregisterService(callback);
- break;
- }
- case "NsdManager:ResolveService": {
- (new ResolveListener(nsdManager)).resolveService(message.getString("serviceName"),
- message.getString("serviceType"),
- callback);
- break;
- }
- }
- }
-
- private Map<String, String> parseAttributes(final NativeJSObject[] jsobjs) {
- if (jsobjs == null || jsobjs.length == 0 || !Versions.feature21Plus) {
- return null;
- }
-
- Map<String, String> attributes = new HashMap<String, String>(jsobjs.length);
- for (NativeJSObject obj : jsobjs) {
- attributes.put(obj.getString("name"), obj.getString("value"));
- }
-
- return attributes;
- }
-
- @TargetApi(16)
- public static JSONObject toJSON(final NsdServiceInfo serviceInfo) throws JSONException {
- JSONObject obj = new JSONObject();
-
- InetAddress host = serviceInfo.getHost();
- if (host != null) {
- obj.put("host", host.getCanonicalHostName());
- obj.put("address", host.getHostAddress());
- }
-
- int port = serviceInfo.getPort();
- if (port != 0) {
- obj.put("port", port);
- }
-
- String serviceName = serviceInfo.getServiceName();
- if (serviceName != null) {
- obj.put("serviceName", serviceName);
- }
-
- String serviceType = serviceInfo.getServiceType();
- if (serviceType != null) {
- obj.put("serviceType", serviceType);
- }
-
- return obj;
- }
-}
-
-class DummyMulticastDNSManager extends MulticastDNSManager implements NativeEventListener {
- static final int FAILURE_UNSUPPORTED = -65544;
- private final MulticastDNSEventManager mEventManager;
-
- public DummyMulticastDNSManager() {
- mEventManager = new MulticastDNSEventManager(this);
- }
-
- @Override
- public void init() {
- mEventManager.init();
- }
-
- @Override
- public void tearDown() {
- mEventManager.tearDown();
- }
-
- @Override
- public void handleMessage(final String event, final NativeJSObject message, final EventCallback callback) {
- Log.v(LOGTAG, "handleMessage: " + event);
- callback.sendError(FAILURE_UNSUPPORTED);
- }
-}
-
-@TargetApi(16)
-class DiscoveryListener implements NsdManager.DiscoveryListener {
- private static final String LOGTAG = "GeckoMDNSManager";
- private final NsdManager nsdManager;
-
- // Callbacks are called from different thread, and every callback can be called only once.
- private EventCallback mStartCallback = null;
- private EventCallback mStopCallback = null;
-
- DiscoveryListener(final NsdManager nsdManager) {
- this.nsdManager = nsdManager;
- }
-
- public void discoverServices(final String serviceType, final EventCallback callback) {
- synchronized (this) {
- mStartCallback = callback;
- }
- nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, this);
- }
-
- public void stopServiceDiscovery(final EventCallback callback) {
- synchronized (this) {
- mStopCallback = callback;
- }
- nsdManager.stopServiceDiscovery(this);
- }
-
- @Override
- public synchronized void onDiscoveryStarted(final String serviceType) {
- Log.d(LOGTAG, "onDiscoveryStarted: " + serviceType);
-
- EventCallback callback;
- synchronized (this) {
- callback = mStartCallback;
- }
-
- if (callback == null) {
- return;
- }
-
- callback.sendSuccess(serviceType);
- }
-
- @Override
- public synchronized void onStartDiscoveryFailed(final String serviceType, final int errorCode) {
- Log.e(LOGTAG, "onStartDiscoveryFailed: " + serviceType + "(" + errorCode + ")");
-
- EventCallback callback;
- synchronized (this) {
- callback = mStartCallback;
- }
-
- callback.sendError(errorCode);
- }
-
- @Override
- public synchronized void onDiscoveryStopped(final String serviceType) {
- Log.d(LOGTAG, "onDiscoveryStopped: " + serviceType);
-
- EventCallback callback;
- synchronized (this) {
- callback = mStopCallback;
- }
-
- if (callback == null) {
- return;
- }
-
- callback.sendSuccess(serviceType);
- }
-
- @Override
- public synchronized void onStopDiscoveryFailed(final String serviceType, final int errorCode) {
- Log.e(LOGTAG, "onStopDiscoveryFailed: " + serviceType + "(" + errorCode + ")");
-
- EventCallback callback;
- synchronized (this) {
- callback = mStopCallback;
- }
-
- if (callback == null) {
- return;
- }
-
- callback.sendError(errorCode);
- }
-
- @Override
- public void onServiceFound(final NsdServiceInfo serviceInfo) {
- Log.d(LOGTAG, "onServiceFound: " + serviceInfo.getServiceName());
- JSONObject json;
- try {
- json = NsdMulticastDNSManager.toJSON(serviceInfo);
- } catch (JSONException e) {
- throw new RuntimeException(e);
- }
- GeckoAppShell.sendRequestToGecko(new GeckoRequest("NsdManager:ServiceFound", json) {
- @Override
- public void onResponse(NativeJSObject nativeJSObject) {
- // don't care return value.
- }
- });
- }
-
- @Override
- public void onServiceLost(final NsdServiceInfo serviceInfo) {
- Log.d(LOGTAG, "onServiceLost: " + serviceInfo.getServiceName());
- JSONObject json;
- try {
- json = NsdMulticastDNSManager.toJSON(serviceInfo);
- } catch (JSONException e) {
- throw new RuntimeException(e);
- }
- GeckoAppShell.sendRequestToGecko(new GeckoRequest("NsdManager:ServiceLost", json) {
- @Override
- public void onResponse(NativeJSObject nativeJSObject) {
- // don't care return value.
- }
- });
- }
-}
-
-@TargetApi(16)
-class RegistrationListener implements NsdManager.RegistrationListener {
- private static final String LOGTAG = "GeckoMDNSManager";
- private final NsdManager nsdManager;
-
- // Callbacks are called from different thread, and every callback can be called only once.
- private EventCallback mStartCallback = null;
- private EventCallback mStopCallback = null;
-
- RegistrationListener(final NsdManager nsdManager) {
- this.nsdManager = nsdManager;
- }
-
- public void registerService(final int port, final String serviceName, final String serviceType, final Map<String, String> attributes, final EventCallback callback) {
- Log.d(LOGTAG, "registerService: " + serviceName + "." + serviceType + ":" + port);
-
- NsdServiceInfo serviceInfo = new NsdServiceInfo();
- serviceInfo.setPort(port);
- serviceInfo.setServiceName(serviceName);
- serviceInfo.setServiceType(serviceType);
- setAttributes(serviceInfo, attributes);
-
- synchronized (this) {
- mStartCallback = callback;
- }
- nsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, this);
- }
-
- @TargetApi(21)
- private void setAttributes(final NsdServiceInfo serviceInfo, final Map<String, String> attributes) {
- if (attributes == null || !Versions.feature21Plus) {
- return;
- }
-
- for (Map.Entry<String, String> entry : attributes.entrySet()) {
- serviceInfo.setAttribute(entry.getKey(), entry.getValue());
- }
- }
-
- public void unregisterService(final EventCallback callback) {
- Log.d(LOGTAG, "unregisterService");
- synchronized (this) {
- mStopCallback = callback;
- }
-
- nsdManager.unregisterService(this);
- }
-
- @Override
- public synchronized void onServiceRegistered(final NsdServiceInfo serviceInfo) {
- Log.d(LOGTAG, "onServiceRegistered: " + serviceInfo.getServiceName());
-
- EventCallback callback;
- synchronized (this) {
- callback = mStartCallback;
- }
-
- if (callback == null) {
- return;
- }
-
- try {
- callback.sendSuccess(NsdMulticastDNSManager.toJSON(serviceInfo));
- } catch (JSONException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public synchronized void onRegistrationFailed(final NsdServiceInfo serviceInfo, final int errorCode) {
- Log.e(LOGTAG, "onRegistrationFailed: " + serviceInfo.getServiceName() + "(" + errorCode + ")");
-
- EventCallback callback;
- synchronized (this) {
- callback = mStartCallback;
- }
-
- callback.sendError(errorCode);
- }
-
- @Override
- public synchronized void onServiceUnregistered(final NsdServiceInfo serviceInfo) {
- Log.d(LOGTAG, "onServiceUnregistered: " + serviceInfo.getServiceName());
-
- EventCallback callback;
- synchronized (this) {
- callback = mStopCallback;
- }
-
- if (callback == null) {
- return;
- }
-
- try {
- callback.sendSuccess(NsdMulticastDNSManager.toJSON(serviceInfo));
- } catch (JSONException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public synchronized void onUnregistrationFailed(final NsdServiceInfo serviceInfo, final int errorCode) {
- Log.e(LOGTAG, "onUnregistrationFailed: " + serviceInfo.getServiceName() + "(" + errorCode + ")");
-
- EventCallback callback;
- synchronized (this) {
- callback = mStopCallback;
- }
-
- if (callback == null) {
- return;
- }
-
- callback.sendError(errorCode);
- }
-}
-
-@TargetApi(16)
-class ResolveListener implements NsdManager.ResolveListener {
- private static final String LOGTAG = "GeckoMDNSManager";
- private final NsdManager nsdManager;
-
- // Callback is called from different thread, and the callback can be called only once.
- private EventCallback mCallback = null;
-
- public ResolveListener(final NsdManager nsdManager) {
- this.nsdManager = nsdManager;
- }
-
- public void resolveService(final String serviceName, final String serviceType, final EventCallback callback) {
- NsdServiceInfo serviceInfo = new NsdServiceInfo();
- serviceInfo.setServiceName(serviceName);
- serviceInfo.setServiceType(serviceType);
-
- mCallback = callback;
- nsdManager.resolveService(serviceInfo, this);
- }
-
-
- @Override
- public synchronized void onResolveFailed(final NsdServiceInfo serviceInfo, final int errorCode) {
- Log.e(LOGTAG, "onResolveFailed: " + serviceInfo.getServiceName() + "(" + errorCode + ")");
-
- if (mCallback == null) {
- return;
- }
- mCallback.sendError(errorCode);
- }
-
- @Override
- public synchronized void onServiceResolved(final NsdServiceInfo serviceInfo) {
- Log.d(LOGTAG, "onServiceResolved: " + serviceInfo.getServiceName());
-
- if (mCallback == null) {
- return;
- }
-
- try {
- mCallback.sendSuccess(NsdMulticastDNSManager.toJSON(serviceInfo));
- } catch (JSONException e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/AsyncCodec.java b/mobile/android/base/java/org/mozilla/gecko/media/AsyncCodec.java
deleted file mode 100644
index c9c620606..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/AsyncCodec.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-import android.media.MediaCodec.BufferInfo;
-import android.media.MediaFormat;
-import android.os.Handler;
-import android.view.Surface;
-
-import java.nio.ByteBuffer;
-
-// A wrapper interface that mimics the new {@link android.media.MediaCodec}
-// asynchronous mode API in Lollipop.
-public interface AsyncCodec {
- public interface Callbacks {
- void onInputBufferAvailable(AsyncCodec codec, int index);
- void onOutputBufferAvailable(AsyncCodec codec, int index, BufferInfo info);
- void onError(AsyncCodec codec, int error);
- void onOutputFormatChanged(AsyncCodec codec, MediaFormat format);
- }
-
- public abstract void setCallbacks(Callbacks callbacks, Handler handler);
- public abstract void configure(MediaFormat format, Surface surface, int flags);
- public abstract void start();
- public abstract void stop();
- public abstract void flush();
- public abstract void release();
- public abstract ByteBuffer getInputBuffer(int index);
- public abstract ByteBuffer getOutputBuffer(int index);
- public abstract void queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags);
- public abstract void releaseOutputBuffer(int index, boolean render);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/AsyncCodecFactory.java b/mobile/android/base/java/org/mozilla/gecko/media/AsyncCodecFactory.java
deleted file mode 100644
index fd670e21b..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/AsyncCodecFactory.java
+++ /dev/null
@@ -1,14 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-import java.io.IOException;
-
-public final class AsyncCodecFactory {
- public static AsyncCodec create(String name) throws IOException {
- // TODO: create (to be implemented) LollipopAsyncCodec when running on Lollipop or later devices.
- return new JellyBeanAsyncCodec(name);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/AudioFocusAgent.java b/mobile/android/base/java/org/mozilla/gecko/media/AudioFocusAgent.java
deleted file mode 100644
index 93a63bcb5..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/AudioFocusAgent.java
+++ /dev/null
@@ -1,135 +0,0 @@
-package org.mozilla.gecko.media;
-
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.annotation.WrapForJNI;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoAppShell;
-
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioManager;
-import android.media.AudioManager.OnAudioFocusChangeListener;
-import android.util.Log;
-
-public class AudioFocusAgent {
- private static final String LOGTAG = "AudioFocusAgent";
-
- private static Context mContext;
- private AudioManager mAudioManager;
- private OnAudioFocusChangeListener mAfChangeListener;
-
- public static final String OWN_FOCUS = "own_focus";
- public static final String LOST_FOCUS = "lost_focus";
- public static final String LOST_FOCUS_TRANSIENT = "lost_focus_transient";
-
- private String mAudioFocusState = LOST_FOCUS;
-
- @WrapForJNI(calledFrom = "gecko")
- public static void notifyStartedPlaying() {
- if (!isAttachedToContext()) {
- return;
- }
- Log.d(LOGTAG, "NotifyStartedPlaying");
- AudioFocusAgent.getInstance().requestAudioFocusIfNeeded();
- }
-
- @WrapForJNI(calledFrom = "gecko")
- public static void notifyStoppedPlaying() {
- if (!isAttachedToContext()) {
- return;
- }
- Log.d(LOGTAG, "NotifyStoppedPlaying");
- AudioFocusAgent.getInstance().abandonAudioFocusIfNeeded();
- }
-
- public synchronized void attachToContext(Context context) {
- if (isAttachedToContext()) {
- return;
- }
-
- mContext = context;
- mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-
- mAfChangeListener = new OnAudioFocusChangeListener() {
- public void onAudioFocusChange(int focusChange) {
- switch (focusChange) {
- case AudioManager.AUDIOFOCUS_LOSS:
- Log.d(LOGTAG, "onAudioFocusChange, AUDIOFOCUS_LOSS");
- notifyObservers("AudioFocusChanged", "lostAudioFocus");
- notifyMediaControlService(MediaControlService.ACTION_PAUSE_BY_AUDIO_FOCUS);
- mAudioFocusState = LOST_FOCUS;
- break;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- Log.d(LOGTAG, "onAudioFocusChange, AUDIOFOCUS_LOSS_TRANSIENT");
- notifyObservers("AudioFocusChanged", "lostAudioFocusTransiently");
- notifyMediaControlService(MediaControlService.ACTION_PAUSE_BY_AUDIO_FOCUS);
- mAudioFocusState = LOST_FOCUS_TRANSIENT;
- break;
- case AudioManager.AUDIOFOCUS_GAIN:
- if (!mAudioFocusState.equals(LOST_FOCUS_TRANSIENT)) {
- return;
- }
- Log.d(LOGTAG, "onAudioFocusChange, AUDIOFOCUS_GAIN");
- notifyObservers("AudioFocusChanged", "gainAudioFocus");
- notifyMediaControlService(MediaControlService.ACTION_RESUME_BY_AUDIO_FOCUS);
- mAudioFocusState = OWN_FOCUS;
- break;
- default:
- }
- }
- };
- notifyMediaControlService(MediaControlService.ACTION_INIT);
- }
-
- @RobocopTarget
- public static AudioFocusAgent getInstance() {
- return AudioFocusAgent.SingletonHolder.INSTANCE;
- }
-
- private static class SingletonHolder {
- private static final AudioFocusAgent INSTANCE = new AudioFocusAgent();
- }
-
- private static boolean isAttachedToContext() {
- return (mContext != null);
- }
-
- private void notifyObservers(String topic, String data) {
- GeckoAppShell.notifyObservers(topic, data);
- }
-
- private AudioFocusAgent() {}
-
- private void requestAudioFocusIfNeeded() {
- if (mAudioFocusState.equals(OWN_FOCUS)) {
- return;
- }
-
- int result = mAudioManager.requestAudioFocus(mAfChangeListener,
- AudioManager.STREAM_MUSIC,
- AudioManager.AUDIOFOCUS_GAIN);
-
- String focusMsg = (result == AudioManager.AUDIOFOCUS_GAIN) ?
- "AudioFocus request granted" : "AudioFoucs request failed";
- Log.d(LOGTAG, focusMsg);
- if (result == AudioManager.AUDIOFOCUS_GAIN) {
- mAudioFocusState = OWN_FOCUS;
- }
- }
-
- private void abandonAudioFocusIfNeeded() {
- if (!mAudioFocusState.equals(OWN_FOCUS)) {
- return;
- }
-
- Log.d(LOGTAG, "Abandon AudioFocus");
- mAudioManager.abandonAudioFocus(mAfChangeListener);
- mAudioFocusState = LOST_FOCUS;
- }
-
- private void notifyMediaControlService(String action) {
- Intent intent = new Intent(mContext, MediaControlService.class);
- intent.setAction(action);
- mContext.startService(intent);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/Codec.java b/mobile/android/base/java/org/mozilla/gecko/media/Codec.java
deleted file mode 100644
index b0a26dfb3..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/Codec.java
+++ /dev/null
@@ -1,366 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
-import android.media.MediaFormat;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.TransactionTooLargeException;
-import android.util.Log;
-import android.view.Surface;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.LinkedList;
-import java.util.NoSuchElementException;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-/* package */ final class Codec extends ICodec.Stub implements IBinder.DeathRecipient {
- private static final String LOGTAG = "GeckoRemoteCodec";
- private static final boolean DEBUG = false;
-
- public enum Error {
- DECODE, FATAL
- };
-
- private final class Callbacks implements AsyncCodec.Callbacks {
- private ICodecCallbacks mRemote;
- private boolean mHasInputCapacitySet;
- private boolean mHasOutputCapacitySet;
-
- public Callbacks(ICodecCallbacks remote) {
- mRemote = remote;
- }
-
- @Override
- public void onInputBufferAvailable(AsyncCodec codec, int index) {
- if (mFlushing) {
- // Flush invalidates all buffers.
- return;
- }
- if (!mHasInputCapacitySet) {
- int capacity = codec.getInputBuffer(index).capacity();
- if (capacity > 0) {
- mSamplePool.setInputBufferSize(capacity);
- mHasInputCapacitySet = true;
- }
- }
- if (!mInputProcessor.onBuffer(index)) {
- reportError(Error.FATAL, new Exception("FAIL: input buffer queue is full"));
- }
- }
-
- @Override
- public void onOutputBufferAvailable(AsyncCodec codec, int index, MediaCodec.BufferInfo info) {
- if (mFlushing) {
- // Flush invalidates all buffers.
- return;
- }
- ByteBuffer output = codec.getOutputBuffer(index);
- if (!mHasOutputCapacitySet) {
- int capacity = output.capacity();
- if (capacity > 0) {
- mSamplePool.setOutputBufferSize(capacity);
- mHasOutputCapacitySet = true;
- }
- }
- Sample copy = mSamplePool.obtainOutput(info);
- try {
- if (info.size > 0) {
- copy.buffer.readFromByteBuffer(output, info.offset, info.size);
- }
- mSentOutputs.add(copy);
- mRemote.onOutput(copy);
- } catch (IOException e) {
- Log.e(LOGTAG, "Fail to read output buffer:" + e.getMessage());
- outputDummy(info);
- } catch (TransactionTooLargeException ttle) {
- Log.e(LOGTAG, "Output is too large:" + ttle.getMessage());
- outputDummy(info);
- } catch (RemoteException e) {
- // Dead recipient.
- e.printStackTrace();
- }
-
- mCodec.releaseOutputBuffer(index, true);
- boolean eos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
- if (DEBUG && eos) {
- Log.d(LOGTAG, "output EOS");
- }
- }
-
- private void outputDummy(MediaCodec.BufferInfo info) {
- try {
- if (DEBUG) Log.d(LOGTAG, "return dummy sample");
- mRemote.onOutput(Sample.create(null, info, null));
- } catch (RemoteException e) {
- // Dead recipient.
- e.printStackTrace();
- }
- }
-
- @Override
- public void onError(AsyncCodec codec, int error) {
- reportError(Error.FATAL, new Exception("codec error:" + error));
- }
-
- @Override
- public void onOutputFormatChanged(AsyncCodec codec, MediaFormat format) {
- try {
- mRemote.onOutputFormatChanged(new FormatParam(format));
- } catch (RemoteException re) {
- // Dead recipient.
- re.printStackTrace();
- }
- }
- }
-
- private final class InputProcessor {
- private Queue<Sample> mInputSamples = new LinkedList<>();
- private Queue<Integer> mAvailableInputBuffers = new LinkedList<>();
- private Queue<Sample> mDequeuedSamples = new LinkedList<>();
-
- private synchronized Sample onAllocate(int size) {
- Sample sample = mSamplePool.obtainInput(size);
- mDequeuedSamples.add(sample);
- return sample;
- }
-
- private synchronized boolean onSample(Sample sample) {
- if (sample == null) {
- return false;
- }
-
- if (!sample.isEOS()) {
- Sample temp = sample;
- sample = mDequeuedSamples.remove();
- sample.info = temp.info;
- sample.cryptoInfo = temp.cryptoInfo;
- temp.dispose();
- }
-
- if (!mInputSamples.offer(sample)) {
- return false;
- }
- feedSampleToBuffer();
- return true;
- }
-
- private synchronized boolean onBuffer(int index) {
- if (!mAvailableInputBuffers.offer(index)) {
- return false;
- }
- feedSampleToBuffer();
- return true;
- }
-
- private void feedSampleToBuffer() {
- while (!mAvailableInputBuffers.isEmpty() && !mInputSamples.isEmpty()) {
- int index = mAvailableInputBuffers.poll();
- int len = 0;
- Sample sample = mInputSamples.poll();
- long pts = sample.info.presentationTimeUs;
- int flags = sample.info.flags;
- if (!sample.isEOS() && sample.buffer != null) {
- len = sample.info.size;
- ByteBuffer buf = mCodec.getInputBuffer(index);
- try {
- sample.writeToByteBuffer(buf);
- mCallbacks.onInputExhausted();
- } catch (IOException e) {
- e.printStackTrace();
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- mSamplePool.recycleInput(sample);
- }
- mCodec.queueInputBuffer(index, 0, len, pts, flags);
- }
- }
-
- private synchronized void reset() {
- mInputSamples.clear();
- mAvailableInputBuffers.clear();
- }
- }
-
- private volatile ICodecCallbacks mCallbacks;
- private AsyncCodec mCodec;
- private InputProcessor mInputProcessor;
- private volatile boolean mFlushing = false;
- private SamplePool mSamplePool;
- private Queue<Sample> mSentOutputs = new ConcurrentLinkedQueue<>();
-
- public synchronized void setCallbacks(ICodecCallbacks callbacks) throws RemoteException {
- mCallbacks = callbacks;
- callbacks.asBinder().linkToDeath(this, 0);
- }
-
- // IBinder.DeathRecipient
- @Override
- public synchronized void binderDied() {
- Log.e(LOGTAG, "Callbacks is dead");
- try {
- release();
- } catch (RemoteException e) {
- // Nowhere to report the error.
- }
- }
-
- @Override
- public synchronized boolean configure(FormatParam format, Surface surface, int flags) throws RemoteException {
- if (mCallbacks == null) {
- Log.e(LOGTAG, "FAIL: callbacks must be set before calling configure()");
- return false;
- }
-
- if (mCodec != null) {
- if (DEBUG) Log.d(LOGTAG, "release existing codec: " + mCodec);
- releaseCodec();
- }
-
- if (DEBUG) Log.d(LOGTAG, "configure " + this);
-
- MediaFormat fmt = format.asFormat();
- String codecName = getDecoderForFormat(fmt);
- if (codecName == null) {
- Log.e(LOGTAG, "FAIL: cannot find codec");
- return false;
- }
-
- try {
- AsyncCodec codec = AsyncCodecFactory.create(codecName);
- codec.setCallbacks(new Callbacks(mCallbacks), null);
- codec.configure(fmt, surface, flags);
- mCodec = codec;
- mInputProcessor = new InputProcessor();
- mSamplePool = new SamplePool(codecName);
- if (DEBUG) Log.d(LOGTAG, codec.toString() + " created");
- return true;
- } catch (Exception e) {
- if (DEBUG) Log.d(LOGTAG, "FAIL: cannot create codec -- " + codecName);
- e.printStackTrace();
- return false;
- }
- }
-
- private void releaseCodec() {
- mInputProcessor.reset();
- try {
- mCodec.release();
- } catch (Exception e) {
- reportError(Error.FATAL, e);
- }
- mCodec = null;
- }
-
- private String getDecoderForFormat(MediaFormat format) {
- String mime = format.getString(MediaFormat.KEY_MIME);
- if (mime == null) {
- return null;
- }
- int numCodecs = MediaCodecList.getCodecCount();
- for (int i = 0; i < numCodecs; i++) {
- MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
- if (info.isEncoder()) {
- continue;
- }
- String[] types = info.getSupportedTypes();
- for (String t : types) {
- if (t.equalsIgnoreCase(mime)) {
- return info.getName();
- }
- }
- }
- return null;
- // TODO: API 21+ is simpler.
- //static MediaCodecList sCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
- //return sCodecList.findDecoderForFormat(format);
- }
-
- @Override
- public synchronized void start() throws RemoteException {
- if (DEBUG) Log.d(LOGTAG, "start " + this);
- mFlushing = false;
- try {
- mCodec.start();
- } catch (Exception e) {
- reportError(Error.FATAL, e);
- }
- }
-
- private void reportError(Error error, Exception e) {
- if (e != null) {
- e.printStackTrace();
- }
- try {
- mCallbacks.onError(error == Error.FATAL);
- } catch (RemoteException re) {
- re.printStackTrace();
- }
- }
-
- @Override
- public synchronized void stop() throws RemoteException {
- if (DEBUG) Log.d(LOGTAG, "stop " + this);
- try {
- mCodec.stop();
- } catch (Exception e) {
- reportError(Error.FATAL, e);
- }
- }
-
- @Override
- public synchronized void flush() throws RemoteException {
- mFlushing = true;
- if (DEBUG) Log.d(LOGTAG, "flush " + this);
- mInputProcessor.reset();
- try {
- mCodec.flush();
- } catch (Exception e) {
- reportError(Error.FATAL, e);
- }
-
- mFlushing = false;
- if (DEBUG) Log.d(LOGTAG, "flushed " + this);
- }
-
- @Override
- public synchronized Sample dequeueInput(int size) {
- return mInputProcessor.onAllocate(size);
- }
-
- @Override
- public synchronized void queueInput(Sample sample) throws RemoteException {
- if (!mInputProcessor.onSample(sample)) {
- reportError(Error.FATAL, new Exception("FAIL: input sample queue is full"));
- }
- }
-
- @Override
- public synchronized void releaseOutput(Sample sample) {
- try {
- mSamplePool.recycleOutput(mSentOutputs.remove());
- } catch (Exception e) {
- Log.e(LOGTAG, "failed to release output:" + sample);
- e.printStackTrace();
- }
- sample.dispose();
- }
-
- @Override
- public synchronized void release() throws RemoteException {
- if (DEBUG) Log.d(LOGTAG, "release " + this);
- releaseCodec();
- mSamplePool.reset();
- mSamplePool = null;
- mCallbacks.asBinder().unlinkToDeath(this, 0);
- mCallbacks = null;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/CodecProxy.java b/mobile/android/base/java/org/mozilla/gecko/media/CodecProxy.java
deleted file mode 100644
index 3025c14d0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/CodecProxy.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-import android.media.MediaCodec;
-import android.media.MediaCodec.BufferInfo;
-import android.media.MediaCodec.CryptoInfo;
-import android.media.MediaFormat;
-import android.os.DeadObjectException;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.Surface;
-
-import org.mozilla.gecko.annotation.WrapForJNI;
-import org.mozilla.gecko.mozglue.JNIObject;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-// Proxy class of ICodec binder.
-public final class CodecProxy {
- private static final String LOGTAG = "GeckoRemoteCodecProxy";
- private static final boolean DEBUG = false;
-
- private ICodec mRemote;
- private FormatParam mFormat;
- private Surface mOutputSurface;
- private CallbacksForwarder mCallbacks;
-
- public interface Callbacks {
- void onInputExhausted();
- void onOutputFormatChanged(MediaFormat format);
- void onOutput(Sample output);
- void onError(boolean fatal);
- }
-
- @WrapForJNI
- public static class NativeCallbacks extends JNIObject implements Callbacks {
- public native void onInputExhausted();
- public native void onOutputFormatChanged(MediaFormat format);
- public native void onOutput(Sample output);
- public native void onError(boolean fatal);
-
- @Override // JNIObject
- protected native void disposeNative();
- }
-
- private class CallbacksForwarder extends ICodecCallbacks.Stub {
- private final Callbacks mCallbacks;
-
- CallbacksForwarder(Callbacks callbacks) {
- mCallbacks = callbacks;
- }
-
- @Override
- public void onInputExhausted() throws RemoteException {
- mCallbacks.onInputExhausted();
- }
-
- @Override
- public void onOutputFormatChanged(FormatParam format) throws RemoteException {
- mCallbacks.onOutputFormatChanged(format.asFormat());
- }
-
- @Override
- public void onOutput(Sample sample) throws RemoteException {
- mCallbacks.onOutput(sample);
- mRemote.releaseOutput(sample);
- sample.dispose();
- }
-
- @Override
- public void onError(boolean fatal) throws RemoteException {
- reportError(fatal);
- }
-
- public void reportError(boolean fatal) {
- mCallbacks.onError(fatal);
- }
- }
-
- @WrapForJNI
- public static CodecProxy create(MediaFormat format, Surface surface, Callbacks callbacks) {
- return RemoteManager.getInstance().createCodec(format, surface, callbacks);
- }
-
- public static CodecProxy createCodecProxy(MediaFormat format, Surface surface, Callbacks callbacks) {
- return new CodecProxy(format, surface, callbacks);
- }
-
- private CodecProxy(MediaFormat format, Surface surface, Callbacks callbacks) {
- mFormat = new FormatParam(format);
- mOutputSurface = surface;
- mCallbacks = new CallbacksForwarder(callbacks);
- }
-
- boolean init(ICodec remote) {
- try {
- remote.setCallbacks(mCallbacks);
- remote.configure(mFormat, mOutputSurface, 0);
- remote.start();
- } catch (RemoteException e) {
- e.printStackTrace();
- return false;
- }
-
- mRemote = remote;
- return true;
- }
-
- boolean deinit() {
- try {
- mRemote.stop();
- mRemote.release();
- mRemote = null;
- return true;
- } catch (RemoteException e) {
- e.printStackTrace();
- return false;
- }
- }
-
- @WrapForJNI
- public synchronized boolean input(ByteBuffer bytes, BufferInfo info, CryptoInfo cryptoInfo) {
- if (mRemote == null) {
- Log.e(LOGTAG, "cannot send input to an ended codec");
- return false;
- }
-
- try {
- Sample sample = (info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) ?
- Sample.EOS : mRemote.dequeueInput(info.size).set(bytes, info, cryptoInfo);
- mRemote.queueInput(sample);
- sample.dispose();
- } catch (IOException e) {
- e.printStackTrace();
- return false;
- } catch (DeadObjectException e) {
- return false;
- } catch (RemoteException e) {
- e.printStackTrace();
- Log.e(LOGTAG, "fail to input sample: size=" + info.size +
- ", pts=" + info.presentationTimeUs +
- ", flags=" + Integer.toHexString(info.flags));
- return false;
- }
- return true;
- }
-
- @WrapForJNI
- public synchronized boolean flush() {
- if (mRemote == null) {
- Log.e(LOGTAG, "cannot flush an ended codec");
- return false;
- }
- try {
- if (DEBUG) Log.d(LOGTAG, "flush " + this);
- mRemote.flush();
- } catch (DeadObjectException e) {
- return false;
- } catch (RemoteException e) {
- e.printStackTrace();
- return false;
- }
- return true;
- }
-
- @WrapForJNI
- public synchronized boolean release() {
- if (mRemote == null) {
- Log.w(LOGTAG, "codec already ended");
- return true;
- }
- if (DEBUG) Log.d(LOGTAG, "release " + this);
- try {
- RemoteManager.getInstance().releaseCodec(this);
- } catch (DeadObjectException e) {
- return false;
- } catch (RemoteException e) {
- e.printStackTrace();
- return false;
- }
- return true;
- }
-
- public synchronized void reportError(boolean fatal) {
- mCallbacks.reportError(fatal);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/FormatParam.java b/mobile/android/base/java/org/mozilla/gecko/media/FormatParam.java
deleted file mode 100644
index c6762672d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/FormatParam.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-import android.media.MediaFormat;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.nio.ByteBuffer;
-
-/** A wrapper to make {@link MediaFormat} parcelable.
- * Supports following keys:
- * <ul>
- * <li>{@link MediaFormat#KEY_MIME}</li>
- * <li>{@link MediaFormat#KEY_WIDTH}</li>
- * <li>{@link MediaFormat#KEY_HEIGHT}</li>
- * <li>{@link MediaFormat#KEY_CHANNEL_COUNT}</li>
- * <li>{@link MediaFormat#KEY_SAMPLE_RATE}</li>
- * <li>"csd-0"</li>
- * <li>"csd-1"</li>
- * </ul>
- */
-public final class FormatParam implements Parcelable {
- // Keys for codec specific config bits not exposed in {@link MediaFormat}.
- private static final String KEY_CONFIG_0 = "csd-0";
- private static final String KEY_CONFIG_1 = "csd-1";
-
- private MediaFormat mFormat;
-
- public MediaFormat asFormat() {
- return mFormat;
- }
-
- public FormatParam(MediaFormat format) {
- mFormat = format;
- }
-
- protected FormatParam(Parcel in) {
- mFormat = new MediaFormat();
- readFromParcel(in);
- }
-
- public static final Creator<FormatParam> CREATOR = new Creator<FormatParam>() {
- @Override
- public FormatParam createFromParcel(Parcel in) {
- return new FormatParam(in);
- }
-
- @Override
- public FormatParam[] newArray(int size) {
- return new FormatParam[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- public void readFromParcel(Parcel in) {
- Bundle bundle = in.readBundle();
- fromBundle(bundle);
- }
-
- private void fromBundle(Bundle bundle) {
- if (bundle.containsKey(MediaFormat.KEY_MIME)) {
- mFormat.setString(MediaFormat.KEY_MIME,
- bundle.getString(MediaFormat.KEY_MIME));
- }
- if (bundle.containsKey(MediaFormat.KEY_WIDTH)) {
- mFormat.setInteger(MediaFormat.KEY_WIDTH,
- bundle.getInt(MediaFormat.KEY_WIDTH));
- }
- if (bundle.containsKey(MediaFormat.KEY_HEIGHT)) {
- mFormat.setInteger(MediaFormat.KEY_HEIGHT,
- bundle.getInt(MediaFormat.KEY_HEIGHT));
- }
- if (bundle.containsKey(MediaFormat.KEY_CHANNEL_COUNT)) {
- mFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT,
- bundle.getInt(MediaFormat.KEY_CHANNEL_COUNT));
- }
- if (bundle.containsKey(MediaFormat.KEY_SAMPLE_RATE)) {
- mFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE,
- bundle.getInt(MediaFormat.KEY_SAMPLE_RATE));
- }
- if (bundle.containsKey(KEY_CONFIG_0)) {
- mFormat.setByteBuffer(KEY_CONFIG_0,
- ByteBuffer.wrap(bundle.getByteArray(KEY_CONFIG_0)));
- }
- if (bundle.containsKey(KEY_CONFIG_1)) {
- mFormat.setByteBuffer(KEY_CONFIG_1,
- ByteBuffer.wrap(bundle.getByteArray((KEY_CONFIG_1))));
- }
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeBundle(toBundle());
- }
-
- private Bundle toBundle() {
- Bundle bundle = new Bundle();
- if (mFormat.containsKey(MediaFormat.KEY_MIME)) {
- bundle.putString(MediaFormat.KEY_MIME, mFormat.getString(MediaFormat.KEY_MIME));
- }
- if (mFormat.containsKey(MediaFormat.KEY_WIDTH)) {
- bundle.putInt(MediaFormat.KEY_WIDTH, mFormat.getInteger(MediaFormat.KEY_WIDTH));
- }
- if (mFormat.containsKey(MediaFormat.KEY_HEIGHT)) {
- bundle.putInt(MediaFormat.KEY_HEIGHT, mFormat.getInteger(MediaFormat.KEY_HEIGHT));
- }
- if (mFormat.containsKey(MediaFormat.KEY_CHANNEL_COUNT)) {
- bundle.putInt(MediaFormat.KEY_CHANNEL_COUNT, mFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
- }
- if (mFormat.containsKey(MediaFormat.KEY_SAMPLE_RATE)) {
- bundle.putInt(MediaFormat.KEY_SAMPLE_RATE, mFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
- }
- if (mFormat.containsKey(KEY_CONFIG_0)) {
- ByteBuffer bytes = mFormat.getByteBuffer(KEY_CONFIG_0);
- bundle.putByteArray(KEY_CONFIG_0,
- Sample.byteArrayFromBuffer(bytes, 0, bytes.capacity()));
- }
- if (mFormat.containsKey(KEY_CONFIG_1)) {
- ByteBuffer bytes = mFormat.getByteBuffer(KEY_CONFIG_1);
- bundle.putByteArray(KEY_CONFIG_1,
- Sample.byteArrayFromBuffer(bytes, 0, bytes.capacity()));
- }
- return bundle;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrm.java b/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrm.java
deleted file mode 100644
index 7b3bda3fd..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrm.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-import android.media.MediaCrypto;
-
-public interface GeckoMediaDrm {
- public interface Callbacks {
- void onSessionCreated(int createSessionToken,
- int promiseId,
- byte[] sessionId,
- byte[] request);
- void onSessionUpdated(int promiseId, byte[] sessionId);
- void onSessionClosed(int promiseId, byte[] sessionId);
- void onSessionMessage(byte[] sessionId,
- int sessionMessageType,
- byte[] request);
- void onSessionError(byte[] sessionId, String message);
- void onSessionBatchedKeyChanged(byte[] sessionId,
- SessionKeyInfo[] keyInfos);
- // All failure cases should go through this function.
- void onRejectPromise(int promiseId, String message);
- }
- void setCallbacks(Callbacks callbacks);
- void createSession(int createSessionToken,
- int promiseId,
- String initDataType,
- byte[] initData);
- void updateSession(int promiseId, String sessionId, byte[] response);
- void closeSession(int promiseId, String sessionId);
- void release();
- MediaCrypto getMediaCrypto();
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java b/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java
deleted file mode 100644
index 6ccaf80df..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java
+++ /dev/null
@@ -1,627 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-import java.lang.*;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.URLEncoder;
-import java.nio.ByteBuffer;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.UUID;
-import java.util.ArrayDeque;
-
-import android.annotation.SuppressLint;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.media.MediaCrypto;
-import android.media.MediaCryptoException;
-import android.media.MediaDrm;
-import android.media.MediaDrmException;
-import android.util.Log;
-
-public class GeckoMediaDrmBridgeV21 implements GeckoMediaDrm {
- private static final String LOGTAG = "GeckoMediaDrmBridgeV21";
- private static final String INVALID_SESSION_ID = "Invalid";
- private static final String WIDEVINE_KEY_SYSTEM = "com.widevine.alpha";
- private static final boolean DEBUG = false;
- private static final UUID WIDEVINE_SCHEME_UUID =
- new UUID(0xedef8ba979d64aceL, 0xa3c827dcd51d21edL);
- // MediaDrm.KeyStatus information listener is supported on M+, adding a
- // dummy key id to report key status.
- private static final byte[] DUMMY_KEY_ID = new byte[] {0};
-
- private UUID mSchemeUUID;
- private Handler mHandler;
- private HandlerThread mHandlerThread;
- private ByteBuffer mCryptoSessionId;
-
- // mProvisioningPromiseId is great than 0 only during provisioning.
- private int mProvisioningPromiseId;
- private HashSet<ByteBuffer> mSessionIds;
- private HashMap<ByteBuffer, String> mSessionMIMETypes;
- private ArrayDeque<PendingCreateSessionData> mPendingCreateSessionDataQueue;
- private GeckoMediaDrm.Callbacks mCallbacks;
-
- private MediaCrypto mCrypto;
- protected MediaDrm mDrm;
-
- public static int LICENSE_REQUEST_INITIAL = 0; /*MediaKeyMessageType::License_request*/
- public static int LICENSE_REQUEST_RENEWAL = 1; /*MediaKeyMessageType::License_renewal*/
- public static int LICENSE_REQUEST_RELEASE = 2; /*MediaKeyMessageType::License_release*/
-
- // Store session data while provisioning
- private static class PendingCreateSessionData {
- public final int mToken;
- public final int mPromiseId;
- public final byte[] mInitData;
- public final String mMimeType;
-
- private PendingCreateSessionData(int token, int promiseId,
- byte[] initData, String mimeType) {
- mToken = token;
- mPromiseId = promiseId;
- mInitData = initData;
- mMimeType = mimeType;
- }
- }
-
- public boolean isSecureDecoderComonentRequired(String mimeType) {
- if (mCrypto != null) {
- return mCrypto.requiresSecureDecoderComponent(mimeType);
- }
- return false;
- }
-
- private static void assertTrue(boolean condition) {
- if (DEBUG && !condition) {
- throw new AssertionError("Expected condition to be true");
- }
- }
-
- @SuppressLint("WrongConstant")
- private void configureVendorSpecificProperty() {
- assertTrue(mDrm != null);
- // Support L3 for now
- mDrm.setPropertyString("securityLevel", "L3");
- // Refer to chromium, set multi-session mode for Widevine.
- if (mSchemeUUID.equals(WIDEVINE_SCHEME_UUID)) {
- mDrm.setPropertyString("sessionSharing", "enable");
- }
- }
-
- GeckoMediaDrmBridgeV21(String keySystem) throws Exception {
- if (DEBUG) Log.d(LOGTAG, "GeckoMediaDrmBridgeV21()");
-
- mProvisioningPromiseId = 0;
- mSessionIds = new HashSet<ByteBuffer>();
- mSessionMIMETypes = new HashMap<ByteBuffer, String>();
- mPendingCreateSessionDataQueue = new ArrayDeque<PendingCreateSessionData>();
-
- mSchemeUUID = convertKeySystemToSchemeUUID(keySystem);
- mCryptoSessionId = null;
-
- if (DEBUG) Log.d(LOGTAG, "mSchemeUUID : " + mSchemeUUID.toString());
-
- // The caller of GeckoMediaDrmBridgeV21 ctor should handle exceptions
- // threw by the following steps.
- mDrm = new MediaDrm(mSchemeUUID);
- configureVendorSpecificProperty();
- mDrm.setOnEventListener(new MediaDrmListener());
- }
-
- @Override
- public void setCallbacks(GeckoMediaDrm.Callbacks callbacks) {
- assertTrue(callbacks != null);
- mCallbacks = callbacks;
- }
-
- @Override
- public void createSession(int createSessionToken,
- int promiseId,
- String initDataType,
- byte[] initData) {
- if (DEBUG) Log.d(LOGTAG, "createSession()");
- if (mDrm == null) {
- onRejectPromise(promiseId, "MediaDrm instance doesn't exist !!");
- return;
- }
-
- if (mProvisioningPromiseId > 0 && mCrypto == null) {
- if (DEBUG) Log.d(LOGTAG, "Pending createSession because it's provisioning !");
- savePendingCreateSessionData(createSessionToken, promiseId,
- initData, initDataType);
- return;
- }
-
- ByteBuffer sessionId = null;
- String strSessionId = null;
- try {
- boolean hasMediaCrypto = ensureMediaCryptoCreated();
- if (!hasMediaCrypto) {
- onRejectPromise(promiseId, "MediaCrypto intance is not created !");
- return;
- }
-
- sessionId = openSession();
- if (sessionId == null) {
- onRejectPromise(promiseId, "Cannot get a session id from MediaDrm !");
- return;
- }
-
- MediaDrm.KeyRequest request = getKeyRequest(sessionId, initData, initDataType);
- if (request == null) {
- mDrm.closeSession(sessionId.array());
- onRejectPromise(promiseId, "Cannot get a key request from MediaDrm !");
- return;
- }
- onSessionCreated(createSessionToken,
- promiseId,
- sessionId.array(),
- request.getData());
- onSessionMessage(sessionId.array(),
- LICENSE_REQUEST_INITIAL,
- request.getData());
- mSessionMIMETypes.put(sessionId, initDataType);
- strSessionId = new String(sessionId.array());
- mSessionIds.add(sessionId);
- if (DEBUG) Log.d(LOGTAG, " StringID : " + strSessionId + " is put into mSessionIds ");
- } catch (android.media.NotProvisionedException e) {
- if (DEBUG) Log.d(LOGTAG, "Device not provisioned:" + e.getMessage());
- if (sessionId != null) {
- // The promise of this createSession will be either resolved
- // or rejected after provisioning.
- mDrm.closeSession(sessionId.array());
- }
- savePendingCreateSessionData(createSessionToken, promiseId,
- initData, initDataType);
- startProvisioning(promiseId);
- }
- }
-
- @Override
- public void updateSession(int promiseId,
- String sessionId,
- byte[] response) {
- if (DEBUG) Log.d(LOGTAG, "updateSession(), sessionId = " + sessionId);
- if (mDrm == null) {
- onRejectPromise(promiseId, "MediaDrm instance doesn't exist !!");
- return;
- }
-
- ByteBuffer session = ByteBuffer.wrap(sessionId.getBytes());
- if (!sessionExists(session)) {
- onRejectPromise(promiseId, "Invalid session during updateSession.");
- return;
- }
-
- try {
- final byte [] keySetId = mDrm.provideKeyResponse(session.array(), response);
- if (DEBUG) {
- HashMap<String, String> infoMap = mDrm.queryKeyStatus(session.array());
- for (String strKey : infoMap.keySet()) {
- String strValue = infoMap.get(strKey);
- Log.d(LOGTAG, "InfoMap : key(" + strKey + ")/value(" + strValue + ")");
- }
- }
- SessionKeyInfo[] keyInfos = new SessionKeyInfo[1];
- keyInfos[0] = new SessionKeyInfo(DUMMY_KEY_ID,
- MediaDrm.KeyStatus.STATUS_USABLE);
- onSessionBatchedKeyChanged(session.array(), keyInfos);
- if (DEBUG) Log.d(LOGTAG, "Key successfully added for session " + sessionId);
- onSessionUpdated(promiseId, session.array());
- return;
- } catch (android.media.NotProvisionedException e) {
- if (DEBUG) Log.d(LOGTAG, "Failed to provide key response:" + e.getMessage());
- onSessionError(session.array(), "Got NotProvisionedException.");
- onRejectPromise(promiseId, "Not provisioned during updateSession.");
- } catch (android.media.DeniedByServerException e) {
- if (DEBUG) Log.d(LOGTAG, "Failed to provide key response:" + e.getMessage());
- onSessionError(session.array(), "Got DeniedByServerException.");
- onRejectPromise(promiseId, "Denied by server during updateSession.");
- } catch (java.lang.IllegalStateException e) {
- if (DEBUG) Log.d(LOGTAG, "Exception when calling provideKeyResponse():" + e.getMessage());
- onSessionError(session.array(), "Got IllegalStateException.");
- onRejectPromise(promiseId, "Rejected during updateSession.");
- }
- release();
- return;
- }
-
- @Override
- public void closeSession(int promiseId, String sessionId) {
- if (DEBUG) Log.d(LOGTAG, "closeSession()");
- if (mDrm == null) {
- onRejectPromise(promiseId, "MediaDrm instance doesn't exist !!");
- return;
- }
-
- ByteBuffer session = ByteBuffer.wrap(sessionId.getBytes());
- mSessionIds.remove(session);
- mDrm.closeSession(session.array());
- onSessionClosed(promiseId, session.array());
- }
-
- @Override
- public void release() {
- if (DEBUG) Log.d(LOGTAG, "release()");
- if (mProvisioningPromiseId > 0) {
- onRejectPromise(mProvisioningPromiseId, "Releasing ... reject provisioning session.");
- mProvisioningPromiseId = 0;
- }
- while (!mPendingCreateSessionDataQueue.isEmpty()) {
- PendingCreateSessionData pendingData = mPendingCreateSessionDataQueue.poll();
- onRejectPromise(pendingData.mPromiseId, "Releasing ... reject all pending sessions.");
- }
- mPendingCreateSessionDataQueue = null;
-
- if (mDrm != null) {
- for (ByteBuffer session : mSessionIds) {
- mDrm.closeSession(session.array());
- }
- mDrm.release();
- mDrm = null;
- }
- mSessionIds.clear();
- mSessionIds = null;
- mSessionMIMETypes.clear();
- mSessionMIMETypes = null;
-
- mCryptoSessionId = null;
- if (mCrypto != null) {
- mCrypto.release();
- mCrypto = null;
- }
- if (mHandlerThread != null) {
- mHandlerThread.quitSafely();
- mHandlerThread = null;
- }
- mHandler = null;
- }
-
- @Override
- public MediaCrypto getMediaCrypto() {
- if (DEBUG) Log.d(LOGTAG, "getMediaCrypto()");
- return mCrypto;
- }
-
- protected void onSessionCreated(int createSessionToken,
- int promiseId,
- byte[] sessionId,
- byte[] request) {
- assertTrue(mCallbacks != null);
- mCallbacks.onSessionCreated(createSessionToken, promiseId, sessionId, request);
- }
-
- protected void onSessionUpdated(int promiseId, byte[] sessionId) {
- assertTrue(mCallbacks != null);
- mCallbacks.onSessionUpdated(promiseId, sessionId);
- }
-
- protected void onSessionClosed(int promiseId, byte[] sessionId) {
- assertTrue(mCallbacks != null);
- mCallbacks.onSessionClosed(promiseId, sessionId);
- }
-
- protected void onSessionMessage(byte[] sessionId,
- int sessionMessageType,
- byte[] request) {
- assertTrue(mCallbacks != null);
- mCallbacks.onSessionMessage(sessionId, sessionMessageType, request);
- }
-
- protected void onSessionError(byte[] sessionId, String message) {
- assertTrue(mCallbacks != null);
- mCallbacks.onSessionError(sessionId, message);
- }
-
- protected void onSessionBatchedKeyChanged(byte[] sessionId,
- SessionKeyInfo[] keyInfos) {
- assertTrue(mCallbacks != null);
- mCallbacks.onSessionBatchedKeyChanged(sessionId, keyInfos);
- }
-
- protected void onRejectPromise(int promiseId, String message) {
- assertTrue(mCallbacks != null);
- mCallbacks.onRejectPromise(promiseId, message);
- }
-
- private MediaDrm.KeyRequest getKeyRequest(ByteBuffer aSession,
- byte[] data,
- String mimeType)
- throws android.media.NotProvisionedException {
- if (mProvisioningPromiseId > 0) {
- // Now provisioning.
- return null;
- }
-
- try {
- HashMap<String, String> optionalParameters = new HashMap<String, String>();
- return mDrm.getKeyRequest(aSession.array(),
- data,
- mimeType,
- MediaDrm.KEY_TYPE_STREAMING,
- optionalParameters);
- } catch (Exception e) {
- Log.e(LOGTAG, "Got excpetion during MediaDrm.getKeyRequest", e);
- }
- return null;
- }
-
- private class MediaDrmListener implements MediaDrm.OnEventListener {
- @Override
- public void onEvent(MediaDrm mediaDrm, byte[] sessionArray, int event,
- int extra, byte[] data) {
- if (DEBUG) Log.d(LOGTAG, "MediaDrmListener.onEvent()");
- if (sessionArray == null) {
- if (DEBUG) Log.d(LOGTAG, "MediaDrmListener: Null session.");
- return;
- }
- ByteBuffer session = ByteBuffer.wrap(sessionArray);
- if (!sessionExists(session)) {
- if (DEBUG) Log.d(LOGTAG, "MediaDrmListener: Invalid session.");
- return;
- }
- // On L, these events are treated as exceptions and handled correspondingly.
- // Leaving this code block for logging message.
- String sessionId = new String(session.array());
- switch (event) {
- case MediaDrm.EVENT_PROVISION_REQUIRED:
- if (DEBUG) Log.d(LOGTAG, "MediaDrm.EVENT_PROVISION_REQUIRED");
- break;
- case MediaDrm.EVENT_KEY_REQUIRED:
- if (DEBUG) Log.d(LOGTAG, "MediaDrm.EVENT_KEY_REQUIRED");
- // No need to handle here if we're not in privacy mode.
- break;
- case MediaDrm.EVENT_KEY_EXPIRED:
- if (DEBUG) Log.d(LOGTAG, "MediaDrm.EVENT_KEY_EXPIRED, sessionId=" + sessionId);
- break;
- case MediaDrm.EVENT_VENDOR_DEFINED:
- if (DEBUG) Log.d(LOGTAG, "MediaDrm.EVENT_VENDOR_DEFINED, sessionId=" + sessionId);
- break;
- default:
- if (DEBUG) Log.d(LOGTAG, "Invalid DRM event " + event);
- return;
- }
- }
- }
-
- private ByteBuffer openSession() throws android.media.NotProvisionedException {
- try {
- byte[] sessionId = mDrm.openSession();
- // ByteBuffer.wrap() is backed by the byte[]. Make a clone here in
- // case the underlying byte[] is modified.
- return ByteBuffer.wrap(sessionId.clone());
- } catch (android.media.NotProvisionedException e) {
- // Throw NotProvisionedException so that we can startProvisioning().
- throw e;
- } catch (java.lang.RuntimeException e) {
- if (DEBUG) Log.d(LOGTAG, "Cannot open a new session:" + e.getMessage());
- release();
- return null;
- } catch (android.media.MediaDrmException e) {
- // Other MediaDrmExceptions (e.g. ResourceBusyException) are not
- // recoverable.
- release();
- return null;
- }
- }
-
- private boolean sessionExists(ByteBuffer session) {
- if (mCryptoSessionId == null) {
- if (DEBUG) Log.d(LOGTAG, "Session doesn't exist because media crypto session is not created.");
- return false;
- }
- if (session == null) {
- if (DEBUG) Log.d(LOGTAG, "Session is null, not in map !");
- return false;
- }
- return !session.equals(mCryptoSessionId) && mSessionIds.contains(session);
- }
-
- private class PostRequestTask extends AsyncTask<Void, Void, Void> {
- private static final String LOGTAG = "PostRequestTask";
-
- private int mPromiseId;
- private String mURL;
- private byte[] mDrmRequest;
- private byte[] mResponseBody;
-
- PostRequestTask(int promiseId, String url, byte[] drmRequest) {
- this.mPromiseId = promiseId;
- this.mURL = url;
- this.mDrmRequest = drmRequest;
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- try {
- URL finalURL = new URL(mURL + "&signedRequest=" + URLEncoder.encode(new String(mDrmRequest), "UTF-8"));
- HttpURLConnection urlConnection = (HttpURLConnection) finalURL.openConnection();
- urlConnection.setRequestMethod("POST");
- if (DEBUG) Log.d(LOGTAG, "Provisioning, posting url =" + finalURL.toString());
-
- // Add data
- urlConnection.setRequestProperty("Accept", "*/*");
- urlConnection.setRequestProperty("User-Agent", getCDMUserAgent());
- urlConnection.setRequestProperty("Content-Type", "application/json");
-
- // Execute HTTP Post Request
- urlConnection.connect();
-
- int responseCode = urlConnection.getResponseCode();
- if (responseCode == HttpURLConnection.HTTP_OK) {
- BufferedReader in =
- new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
- String inputLine;
- StringBuffer response = new StringBuffer();
-
- while ((inputLine = in.readLine()) != null) {
- response.append(inputLine);
- }
- in.close();
- mResponseBody = String.valueOf(response).getBytes();
- if (DEBUG) Log.d(LOGTAG, "Provisioning, response received.");
- if (mResponseBody != null) Log.d(LOGTAG, "response length=" + mResponseBody.length);
- } else {
- Log.d(LOGTAG, "Provisioning, server returned HTTP error code :" + responseCode);
- }
- } catch (IOException e) {
- Log.e(LOGTAG, "Got exception during posting provisioning request ...", e);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Void v) {
- onProvisionResponse(mPromiseId, mResponseBody);
- }
- }
-
- private boolean provideProvisionResponse(byte[] response) {
- if (response == null || response.length == 0) {
- if (DEBUG) Log.d(LOGTAG, "Invalid provision response.");
- return false;
- }
-
- try {
- mDrm.provideProvisionResponse(response);
- return true;
- } catch (android.media.DeniedByServerException e) {
- if (DEBUG) Log.d(LOGTAG, "Failed to provide provision response:" + e.getMessage());
- } catch (java.lang.IllegalStateException e) {
- if (DEBUG) Log.d(LOGTAG, "Failed to provide provision response:" + e.getMessage());
- }
- return false;
- }
-
- private void savePendingCreateSessionData(int token,
- int promiseId,
- byte[] initData,
- String mime) {
- if (DEBUG) Log.d(LOGTAG, "savePendingCreateSessionData, promiseId : " + promiseId);
- mPendingCreateSessionDataQueue.offer(new PendingCreateSessionData(token, promiseId, initData, mime));
- }
-
- private void processPendingCreateSessionData() {
- if (DEBUG) Log.d(LOGTAG, "processPendingCreateSessionData ... ");
-
- assertTrue(mProvisioningPromiseId == 0);
- try {
- while (!mPendingCreateSessionDataQueue.isEmpty()) {
- PendingCreateSessionData pendingData = mPendingCreateSessionDataQueue.poll();
- if (DEBUG) Log.d(LOGTAG, "processPendingCreateSessionData, promiseId : " + pendingData.mPromiseId);
-
- createSession(pendingData.mToken,
- pendingData.mPromiseId,
- pendingData.mMimeType,
- pendingData.mInitData);
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Got excpetion during processPendingCreateSessionData ...", e);
- }
- }
-
- private void resumePendingOperations() {
- if (mHandlerThread == null) {
- mHandlerThread = new HandlerThread("PendingSessionOpsThread");
- mHandlerThread.start();
- }
- if (mHandler == null) {
- mHandler = new Handler(mHandlerThread.getLooper());
- }
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- processPendingCreateSessionData();
- }
- });
- }
-
- // Only triggered when failed on {openSession, getKeyRequest}
- private void startProvisioning(int promiseId) {
- if (DEBUG) Log.d(LOGTAG, "startProvisioning()");
- if (mProvisioningPromiseId > 0) {
- // Already in provisioning.
- return;
- }
- try {
- mProvisioningPromiseId = promiseId;
- MediaDrm.ProvisionRequest request = mDrm.getProvisionRequest();
- PostRequestTask postTask =
- new PostRequestTask(promiseId, request.getDefaultUrl(), request.getData());
- postTask.execute();
- } catch (Exception e) {
- onRejectPromise(promiseId, "Exception happened in startProvisioning !");
- mProvisioningPromiseId = 0;
- }
- }
-
- private void onProvisionResponse(int promiseId, byte[] response) {
- if (DEBUG) Log.d(LOGTAG, "onProvisionResponse()");
-
- mProvisioningPromiseId = 0;
- boolean success = provideProvisionResponse(response);
- if (success) {
- // Promise will either be resovled / rejected in createSession during
- // resuming operations.
- resumePendingOperations();
- } else {
- onRejectPromise(promiseId, "Failed to provide provision response.");
- }
- }
-
- private boolean ensureMediaCryptoCreated() throws android.media.NotProvisionedException {
- if (mCrypto != null) {
- return true;
- }
- try {
- mCryptoSessionId = openSession();
- if (mCryptoSessionId == null) {
- if (DEBUG) Log.d(LOGTAG, "Cannot open session for MediaCrypto");
- return false;
- }
-
- if (MediaCrypto.isCryptoSchemeSupported(mSchemeUUID)) {
- final byte [] cryptoSessionId = mCryptoSessionId.array();
- mCrypto = new MediaCrypto(mSchemeUUID, cryptoSessionId);
- String strCryptoSessionId = new String(cryptoSessionId);
- mSessionIds.add(mCryptoSessionId);
- if (DEBUG) Log.d(LOGTAG, "MediaCrypto successfully created! - SId " + INVALID_SESSION_ID + ", " + strCryptoSessionId);
- return true;
- } else {
- if (DEBUG) Log.d(LOGTAG, "Cannot create MediaCrypto for unsupported scheme.");
- return false;
- }
- } catch (android.media.MediaCryptoException e) {
- if (DEBUG) Log.d(LOGTAG, "Cannot create MediaCrypto:" + e.getMessage());
- release();
- return false;
- } catch (android.media.NotProvisionedException e) {
- if (DEBUG) Log.d(LOGTAG, "ensureMediaCryptoCreated::Device not provisioned:" + e.getMessage());
- throw e;
- }
- }
-
- private UUID convertKeySystemToSchemeUUID(String keySystem) {
- if (WIDEVINE_KEY_SYSTEM.equals(keySystem)) {
- return WIDEVINE_SCHEME_UUID;
- }
- if (DEBUG) Log.d(LOGTAG, "Cannot convert unsupported key system : " + keySystem);
- return null;
- }
-
- private String getCDMUserAgent() {
- // This user agent is found and hard-coded in Android(L) source code and
- // Chromium project. Not sure if it's gonna change in the future.
- String ua = "Widevine CDM v1.0";
- return ua;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV23.java b/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV23.java
deleted file mode 100644
index 74144f28e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV23.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-import android.annotation.TargetApi;
-import static android.os.Build.VERSION_CODES.M;
-import android.media.MediaDrm;
-import android.util.Log;
-import java.util.List;
-
-public class GeckoMediaDrmBridgeV23 extends GeckoMediaDrmBridgeV21 {
-
- private static final String LOGTAG = "GeckoMediaDrmBridgeV23";
- private static final boolean DEBUG = false;
-
- GeckoMediaDrmBridgeV23(String keySystem) throws Exception {
- super(keySystem);
- if (DEBUG) Log.d(LOGTAG, "GeckoMediaDrmBridgeV23 ctor");
- mDrm.setOnKeyStatusChangeListener(new KeyStatusChangeListener(), null);
- }
-
- @TargetApi(M)
- private class KeyStatusChangeListener implements MediaDrm.OnKeyStatusChangeListener {
- @Override
- public void onKeyStatusChange(MediaDrm mediaDrm,
- byte[] sessionId,
- List<MediaDrm.KeyStatus> keyInformation,
- boolean hasNewUsableKey) {
- if (DEBUG) Log.d(LOGTAG, "[onKeyStatusChange] hasNewUsableKey = " + hasNewUsableKey);
- if (keyInformation.size() == 0) {
- return;
- }
- SessionKeyInfo[] keyInfos = new SessionKeyInfo[keyInformation.size()];
- for (int i = 0; i < keyInformation.size(); i++) {
- MediaDrm.KeyStatus keyStatus = keyInformation.get(i);
- keyInfos[i] = new SessionKeyInfo(keyStatus.getKeyId(),
- keyStatus.getStatusCode());
- }
- onSessionBatchedKeyChanged(sessionId, keyInfos);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/JellyBeanAsyncCodec.java b/mobile/android/base/java/org/mozilla/gecko/media/JellyBeanAsyncCodec.java
deleted file mode 100644
index 3df01f1fe..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/JellyBeanAsyncCodec.java
+++ /dev/null
@@ -1,405 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-import android.media.MediaCodec;
-import android.media.MediaFormat;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-import android.view.Surface;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-// Implement async API using MediaCodec sync mode (API v16).
-// This class uses internal worker thread/handler (mBufferPoller) to poll
-// input and output buffer and notifies the client through callbacks.
-final class JellyBeanAsyncCodec implements AsyncCodec {
- private static final String LOGTAG = "GeckoAsyncCodecAPIv16";
- private static final boolean DEBUG = false;
-
- private static final int ERROR_CODEC = -10000;
-
- private abstract class CancelableHandler extends Handler {
- private static final int MSG_CANCELLATION = 0x434E434C; // 'CNCL'
-
- protected CancelableHandler(Looper looper) {
- super(looper);
- }
-
- protected void cancel() {
- removeCallbacksAndMessages(null);
- sendEmptyMessage(MSG_CANCELLATION);
- // Wait until handleMessageLocked() is done.
- synchronized (this) { }
- }
-
- protected boolean isCanceled() {
- return hasMessages(MSG_CANCELLATION);
- }
-
- // Subclass should implement this and return true if it handles msg.
- // Warning: Never, ever call super.handleMessage() in this method!
- protected abstract boolean handleMessageLocked(Message msg);
-
- public final void handleMessage(Message msg) {
- // Block cancel() during handleMessageLocked().
- synchronized (this) {
- if (isCanceled() || handleMessageLocked(msg)) {
- return;
- }
- }
-
- switch (msg.what) {
- case MSG_CANCELLATION:
- // Just a marker. Nothing to do here.
- if (DEBUG) Log.d(LOGTAG, "handler " + this + " done cancellation, codec=" + JellyBeanAsyncCodec.this);
- break;
- default:
- super.handleMessage(msg);
- break;
- }
- }
- }
-
- // A handler to invoke AsyncCodec.Callbacks methods.
- private final class CallbackSender extends CancelableHandler {
- private static final int MSG_INPUT_BUFFER_AVAILABLE = 1;
- private static final int MSG_OUTPUT_BUFFER_AVAILABLE = 2;
- private static final int MSG_OUTPUT_FORMAT_CHANGE = 3;
- private static final int MSG_ERROR = 4;
- private Callbacks mCallbacks;
-
- private CallbackSender(Looper looper, Callbacks callbacks) {
- super(looper);
- mCallbacks = callbacks;
- }
-
- public void notifyInputBuffer(int index) {
- if (isCanceled()) {
- return;
- }
-
- Message msg = obtainMessage(MSG_INPUT_BUFFER_AVAILABLE);
- msg.arg1 = index;
- processMessage(msg);
- }
-
- private void processMessage(Message msg) {
- if (Looper.myLooper() == getLooper()) {
- handleMessage(msg);
- } else {
- sendMessage(msg);
- }
- }
-
- public void notifyOutputBuffer(int index, MediaCodec.BufferInfo info) {
- if (isCanceled()) {
- return;
- }
-
- Message msg = obtainMessage(MSG_OUTPUT_BUFFER_AVAILABLE, info);
- msg.arg1 = index;
- processMessage(msg);
- }
-
- public void notifyOutputFormat(MediaFormat format) {
- if (isCanceled()) {
- return;
- }
- processMessage(obtainMessage(MSG_OUTPUT_FORMAT_CHANGE, format));
- }
-
- public void notifyError(int result) {
- Log.e(LOGTAG, "codec error:" + result);
- processMessage(obtainMessage(MSG_ERROR, result, 0));
- }
-
- protected boolean handleMessageLocked(Message msg) {
- switch (msg.what) {
- case MSG_INPUT_BUFFER_AVAILABLE: // arg1: buffer index.
- mCallbacks.onInputBufferAvailable(JellyBeanAsyncCodec.this,
- msg.arg1);
- break;
- case MSG_OUTPUT_BUFFER_AVAILABLE: // arg1: buffer index, obj: info.
- mCallbacks.onOutputBufferAvailable(JellyBeanAsyncCodec.this,
- msg.arg1,
- (MediaCodec.BufferInfo)msg.obj);
- break;
- case MSG_OUTPUT_FORMAT_CHANGE: // obj: output format.
- mCallbacks.onOutputFormatChanged(JellyBeanAsyncCodec.this,
- (MediaFormat)msg.obj);
- break;
- case MSG_ERROR: // arg1: error code.
- mCallbacks.onError(JellyBeanAsyncCodec.this, msg.arg1);
- break;
- default:
- return false;
- }
-
- return true;
- }
- }
-
- // Handler to poll input and output buffers using dequeue(Input|Output)Buffer(),
- // with 10ms time-out. Once triggered and successfully gets a buffer, it
- // will schedule next polling until EOS or failure. To prevent it from
- // automatically polling more buffer, use cancel() it inherits from
- // CancelableHandler.
- private final class BufferPoller extends CancelableHandler {
- private static final int MSG_POLL_INPUT_BUFFERS = 1;
- private static final int MSG_POLL_OUTPUT_BUFFERS = 2;
-
- private static final long DEQUEUE_TIMEOUT_US = 10000;
-
- public BufferPoller(Looper looper) {
- super(looper);
- }
-
- private void schedulePollingIfNotCanceled(int what) {
- if (isCanceled()) {
- return;
- }
-
- schedulePolling(what);
- }
-
- private void schedulePolling(int what) {
- if (needsBuffer(what)) {
- sendEmptyMessage(what);
- }
- }
-
- private boolean needsBuffer(int what) {
- if (mOutputEnded && (what == MSG_POLL_OUTPUT_BUFFERS)) {
- return false;
- }
-
- if (mInputEnded && (what == MSG_POLL_INPUT_BUFFERS)) {
- return false;
- }
-
- return true;
- }
-
- protected boolean handleMessageLocked(Message msg) {
- try {
- switch (msg.what) {
- case MSG_POLL_INPUT_BUFFERS:
- pollInputBuffer();
- break;
- case MSG_POLL_OUTPUT_BUFFERS:
- pollOutputBuffer();
- break;
- default:
- return false;
- }
- } catch (IllegalStateException e) {
- e.printStackTrace();
- mCallbackSender.notifyError(ERROR_CODEC);
- }
-
- return true;
- }
-
- private void pollInputBuffer() {
- int result = mCodec.dequeueInputBuffer(DEQUEUE_TIMEOUT_US);
- if (result >= 0) {
- mCallbackSender.notifyInputBuffer(result);
- schedulePollingIfNotCanceled(BufferPoller.MSG_POLL_INPUT_BUFFERS);
- } else if (result != MediaCodec.INFO_TRY_AGAIN_LATER) {
- mCallbackSender.notifyError(result);
- }
- }
-
- private void pollOutputBuffer() {
- boolean dequeueMoreBuffer = true;
- MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
- int result = mCodec.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT_US);
- if (result >= 0) {
- if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
- mOutputEnded = true;
- }
- mCallbackSender.notifyOutputBuffer(result, info);
- if (!hasMessages(MSG_POLL_INPUT_BUFFERS)) {
- schedulePollingIfNotCanceled(MSG_POLL_INPUT_BUFFERS);
- }
- } else if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
- mOutputBuffers = mCodec.getOutputBuffers();
- } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
- mCallbackSender.notifyOutputFormat(mCodec.getOutputFormat());
- } else if (result == MediaCodec.INFO_TRY_AGAIN_LATER) {
- // When input ended, keep polling remaining output buffer until EOS.
- dequeueMoreBuffer = mInputEnded;
- } else {
- mCallbackSender.notifyError(result);
- dequeueMoreBuffer = false;
- }
-
- if (dequeueMoreBuffer) {
- schedulePollingIfNotCanceled(MSG_POLL_OUTPUT_BUFFERS);
- }
- }
- }
-
- private MediaCodec mCodec;
- private ByteBuffer[] mInputBuffers;
- private ByteBuffer[] mOutputBuffers;
- private AsyncCodec.Callbacks mCallbacks;
- private CallbackSender mCallbackSender;
-
- private BufferPoller mBufferPoller;
- private volatile boolean mInputEnded;
- private volatile boolean mOutputEnded;
-
- // Must be called on a thread with looper.
- /* package */ JellyBeanAsyncCodec(String name) throws IOException {
- mCodec = MediaCodec.createByCodecName(name);
- initBufferPoller(name + " buffer poller");
- }
-
- private void initBufferPoller(String name) {
- if (mBufferPoller != null) {
- Log.e(LOGTAG, "poller already initialized");
- return;
- }
- HandlerThread thread = new HandlerThread(name);
- thread.start();
- mBufferPoller = new BufferPoller(thread.getLooper());
- if (DEBUG) Log.d(LOGTAG, "start poller for codec:" + this + ", thread=" + thread.getThreadId());
- }
-
- @Override
- public void setCallbacks(AsyncCodec.Callbacks callbacks, Handler handler) {
- if (callbacks == null) {
- return;
- }
-
- Looper looper = (handler == null) ? null : handler.getLooper();
- if (looper == null) {
- // Use this thread if no handler supplied.
- looper = Looper.myLooper();
- }
- if (looper == null) {
- // This thread has no looper. Use poller thread.
- looper = mBufferPoller.getLooper();
- }
- mCallbackSender = new CallbackSender(looper, callbacks);
- if (DEBUG) Log.d(LOGTAG, "setCallbacks(): sender=" + mCallbackSender);
- }
-
- @Override
- public void configure(MediaFormat format, Surface surface, int flags) {
- assertCallbacks();
-
- mCodec.configure(format, surface, null, flags);
- }
-
- private void assertCallbacks() {
- if (mCallbackSender == null) {
- throw new IllegalStateException(LOGTAG + ": callback must be supplied with setCallbacks().");
- }
- }
-
- @Override
- public void start() {
- assertCallbacks();
-
- mCodec.start();
- mInputEnded = false;
- mOutputEnded = false;
- mInputBuffers = mCodec.getInputBuffers();
- mOutputBuffers = mCodec.getOutputBuffers();
- mBufferPoller.schedulePolling(BufferPoller.MSG_POLL_INPUT_BUFFERS);
- }
-
- @Override
- public final void queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags) {
- assertCallbacks();
-
- mInputEnded = (flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
-
- try {
- mCodec.queueInputBuffer(index, offset, size, presentationTimeUs, flags);
- } catch (IllegalStateException e) {
- e.printStackTrace();
- mCallbackSender.notifyError(ERROR_CODEC);
- return;
- }
-
- mBufferPoller.schedulePolling(BufferPoller.MSG_POLL_INPUT_BUFFERS);
- mBufferPoller.schedulePolling(BufferPoller.MSG_POLL_OUTPUT_BUFFERS);
- }
-
- @Override
- public final void releaseOutputBuffer(int index, boolean render) {
- assertCallbacks();
-
- mCodec.releaseOutputBuffer(index, render);
- }
-
- @Override
- public final ByteBuffer getInputBuffer(int index) {
- assertCallbacks();
-
- return mInputBuffers[index];
- }
-
- @Override
- public final ByteBuffer getOutputBuffer(int index) {
- assertCallbacks();
-
- return mOutputBuffers[index];
- }
-
- @Override
- public void flush() {
- assertCallbacks();
-
- mInputEnded = false;
- mOutputEnded = false;
- cancelPendingTasks();
- mCodec.flush();
- mBufferPoller.schedulePolling(BufferPoller.MSG_POLL_INPUT_BUFFERS);
- }
-
- private void cancelPendingTasks() {
- mBufferPoller.cancel();
- mCallbackSender.cancel();
- }
-
- @Override
- public void stop() {
- assertCallbacks();
-
- cancelPendingTasks();
- mCodec.stop();
- }
-
- @Override
- public void release() {
- assertCallbacks();
-
- cancelPendingTasks();
- mCallbackSender = null;
- mCodec.release();
- stopBufferPoller();
- }
-
- private void stopBufferPoller() {
- if (mBufferPoller == null) {
- Log.e(LOGTAG, "no initialized poller.");
- return;
- }
-
- mBufferPoller.getLooper().quit();
- mBufferPoller = null;
-
- if (DEBUG) Log.d(LOGTAG, "stop poller " + this);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/LocalMediaDrmBridge.java b/mobile/android/base/java/org/mozilla/gecko/media/LocalMediaDrmBridge.java
deleted file mode 100644
index 2aad674b6..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/LocalMediaDrmBridge.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-import org.mozilla.gecko.AppConstants;
-
-import android.media.MediaCrypto;
-import android.util.Log;
-
-final class LocalMediaDrmBridge implements GeckoMediaDrm {
- private static final String LOGTAG = "GeckoLocalMediaDrmBridge";
- private static final boolean DEBUG = false;
- private GeckoMediaDrm mBridge = null;
- private CallbacksForwarder mCallbacksFwd;
-
- // Forward the callback calls from GeckoMediaDrmBridgeV{21,23}
- // to the callback MediaDrmProxy.Callbacks.
- private class CallbacksForwarder implements GeckoMediaDrm.Callbacks {
- private final GeckoMediaDrm.Callbacks mProxyCallbacks;
-
- CallbacksForwarder(GeckoMediaDrm.Callbacks callbacks) {
- assertTrue(callbacks != null);
- mProxyCallbacks = callbacks;
- }
-
- @Override
- public void onSessionCreated(int createSessionToken,
- int promiseId,
- byte[] sessionId,
- byte[] request) {
- assertTrue(mProxyCallbacks != null);
- mProxyCallbacks.onSessionCreated(createSessionToken,
- promiseId,
- sessionId,
- request);
- }
-
- @Override
- public void onSessionUpdated(int promiseId, byte[] sessionId) {
- assertTrue(mProxyCallbacks != null);
- mProxyCallbacks.onSessionUpdated(promiseId, sessionId);
- }
-
- @Override
- public void onSessionClosed(int promiseId, byte[] sessionId) {
- assertTrue(mProxyCallbacks != null);
- mProxyCallbacks.onSessionClosed(promiseId, sessionId);
- }
-
- @Override
- public void onSessionMessage(byte[] sessionId,
- int sessionMessageType,
- byte[] request) {
- assertTrue(mProxyCallbacks != null);
- mProxyCallbacks.onSessionMessage(sessionId, sessionMessageType, request);
- }
-
- @Override
- public void onSessionError(byte[] sessionId,
- String message) {
- assertTrue(mProxyCallbacks != null);
- mProxyCallbacks.onSessionError(sessionId, message);
- }
-
- @Override
- public void onSessionBatchedKeyChanged(byte[] sessionId,
- SessionKeyInfo[] keyInfos) {
- assertTrue(mProxyCallbacks != null);
- mProxyCallbacks.onSessionBatchedKeyChanged(sessionId, keyInfos);
- }
-
- @Override
- public void onRejectPromise(int promiseId, String message) {
- if (DEBUG) Log.d(LOGTAG, message);
- assertTrue(mProxyCallbacks != null);
- mProxyCallbacks.onRejectPromise(promiseId, message);
- }
- } // CallbacksForwarder
-
- private static void assertTrue(boolean condition) {
- if (DEBUG && !condition) {
- throw new AssertionError("Expected condition to be true");
- }
- }
-
- LocalMediaDrmBridge(String keySystem) throws Exception {
- if (AppConstants.Versions.preLollipop) {
- mBridge = null;
- } else if (AppConstants.Versions.feature21Plus &&
- AppConstants.Versions.preMarshmallow) {
- mBridge = new GeckoMediaDrmBridgeV21(keySystem);
- } else {
- mBridge = new GeckoMediaDrmBridgeV23(keySystem);
- }
- }
-
- @Override
- public synchronized void setCallbacks(Callbacks callbacks) {
- if (DEBUG) Log.d(LOGTAG, "setCallbacks()");
- mCallbacksFwd = new CallbacksForwarder(callbacks);
- assertTrue(mBridge != null);
- mBridge.setCallbacks(mCallbacksFwd);
- }
-
- @Override
- public synchronized void createSession(int createSessionToken,
- int promiseId,
- String initDataType,
- byte[] initData) {
- if (DEBUG) Log.d(LOGTAG, "createSession()");
- assertTrue(mCallbacksFwd != null);
- try {
- mBridge.createSession(createSessionToken, promiseId, initDataType, initData);
- } catch (Exception e) {
- Log.e(LOGTAG, "Failed to createSession.", e);
- mCallbacksFwd.onRejectPromise(promiseId, "Failed to createSession.");
- }
- }
-
- @Override
- public synchronized void updateSession(int promiseId, String sessionId, byte[] response) {
- if (DEBUG) Log.d(LOGTAG, "updateSession()");
- assertTrue(mCallbacksFwd != null);
- try {
- mBridge.updateSession(promiseId, sessionId, response);
- } catch (Exception e) {
- Log.e(LOGTAG, "Failed to updateSession.", e);
- mCallbacksFwd.onRejectPromise(promiseId, "Failed to updateSession.");
- }
- }
-
- @Override
- public synchronized void closeSession(int promiseId, String sessionId) {
- if (DEBUG) Log.d(LOGTAG, "closeSession()");
- assertTrue(mCallbacksFwd != null);
- try {
- mBridge.closeSession(promiseId, sessionId);
- } catch (Exception e) {
- Log.e(LOGTAG, "Failed to closeSession.", e);
- mCallbacksFwd.onRejectPromise(promiseId, "Failed to closeSession.");
- }
- }
-
- @Override
- public synchronized void release() {
- if (DEBUG) Log.d(LOGTAG, "release()");
- try {
- mBridge.release();
- mBridge = null;
- mCallbacksFwd = null;
- } catch (Exception e) {
- Log.e(LOGTAG, "Failed to release", e);
- }
- }
-
- @Override
- public synchronized MediaCrypto getMediaCrypto() {
- if (DEBUG) Log.d(LOGTAG, "getMediaCrypto()");
- return mBridge != null ? mBridge.getMediaCrypto() : null;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java b/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
deleted file mode 100644
index 2aa783050..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
+++ /dev/null
@@ -1,431 +0,0 @@
-package org.mozilla.gecko.media;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.support.v4.app.NotificationManagerCompat;
-import android.util.Log;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.PrefsHelper;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.lang.ref.WeakReference;
-
-public class MediaControlService extends Service implements Tabs.OnTabsChangedListener {
- private static final String LOGTAG = "MediaControlService";
-
- public static final String ACTION_INIT = "action_init";
- public static final String ACTION_RESUME = "action_resume";
- public static final String ACTION_PAUSE = "action_pause";
- public static final String ACTION_STOP = "action_stop";
- public static final String ACTION_RESUME_BY_AUDIO_FOCUS = "action_resume_audio_focus";
- public static final String ACTION_PAUSE_BY_AUDIO_FOCUS = "action_pause_audio_focus";
-
- private static final int MEDIA_CONTROL_ID = 1;
- private static final String MEDIA_CONTROL_PREF = "dom.audiochannel.mediaControl";
-
- private String mActionState = ACTION_STOP;
-
- private MediaSession mSession;
- private MediaController mController;
-
- private PrefsHelper.PrefHandler mPrefsObserver;
- private final String[] mPrefs = { MEDIA_CONTROL_PREF };
-
- private boolean mInitialize = false;
- private boolean mIsMediaControlPrefOn = true;
-
- private static WeakReference<Tab> mTabReference = new WeakReference<>(null);
-
- private int minCoverSize;
- private int coverSize;
-
- @Override
- public void onCreate() {
- initialize();
- }
-
- @Override
- public void onDestroy() {
- shutdown();
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- handleIntent(intent);
- return START_NOT_STICKY;
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public boolean onUnbind(Intent intent) {
- mSession.release();
- return super.onUnbind(intent);
- }
-
- @Override
- public void onTaskRemoved(Intent rootIntent) {
- shutdown();
- }
-
- @Override
- public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
- if (!mInitialize) {
- return;
- }
-
- final Tab playingTab = mTabReference.get();
- switch (msg) {
- case MEDIA_PLAYING_CHANGE:
- // The 'MEDIA_PLAYING_CHANGE' would only be received when the
- // media starts or ends.
- if (playingTab != tab && tab.isMediaPlaying()) {
- mTabReference = new WeakReference<>(tab);
- notifyControlInterfaceChanged(ACTION_PAUSE);
- } else if (playingTab == tab && !tab.isMediaPlaying()) {
- notifyControlInterfaceChanged(ACTION_STOP);
- mTabReference = new WeakReference<>(null);
- }
- break;
- case MEDIA_PLAYING_RESUME:
- // user resume the paused-by-control media from page so that we
- // should make the control interface consistent.
- if (playingTab == tab && !isMediaPlaying()) {
- notifyControlInterfaceChanged(ACTION_PAUSE);
- }
- break;
- case CLOSED:
- if (playingTab == null || playingTab == tab) {
- // Remove the controls when the playing tab disappeared or was closed.
- notifyControlInterfaceChanged(ACTION_STOP);
- }
- break;
- case FAVICON:
- if (playingTab == tab) {
- final String actionForPendingIntent = isMediaPlaying() ?
- ACTION_PAUSE : ACTION_RESUME;
- notifyControlInterfaceChanged(actionForPendingIntent);
- }
- break;
- }
- }
-
- private boolean isMediaPlaying() {
- return mActionState.equals(ACTION_RESUME);
- }
-
- private void initialize() {
- if (mInitialize ||
- !isAndroidVersionLollopopOrHigher()) {
- return;
- }
-
- Log.d(LOGTAG, "initialize");
- getGeckoPreference();
- initMediaSession();
-
- coverSize = (int) getResources().getDimension(R.dimen.notification_media_cover);
- minCoverSize = getResources().getDimensionPixelSize(R.dimen.favicon_bg);
-
- Tabs.registerOnTabsChangedListener(this);
- mInitialize = true;
- }
-
- private void shutdown() {
- if (!mInitialize) {
- return;
- }
-
- Log.d(LOGTAG, "shutdown");
- notifyControlInterfaceChanged(ACTION_STOP);
- PrefsHelper.removeObserver(mPrefsObserver);
-
- Tabs.unregisterOnTabsChangedListener(this);
- mInitialize = false;
- stopSelf();
- }
-
- private boolean isAndroidVersionLollopopOrHigher() {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
- }
-
- private void handleIntent(Intent intent) {
- if (intent == null || intent.getAction() == null || !mInitialize) {
- return;
- }
-
- Log.d(LOGTAG, "HandleIntent, action = " + intent.getAction() + ", actionState = " + mActionState);
- switch (intent.getAction()) {
- case ACTION_INIT :
- // This action is used to create a service and do the initialization,
- // the actual operation would be executed via control interface's
- // pending intent.
- break;
- case ACTION_RESUME :
- mController.getTransportControls().play();
- break;
- case ACTION_PAUSE :
- mController.getTransportControls().pause();
- break;
- case ACTION_STOP :
- mController.getTransportControls().stop();
- break;
- case ACTION_PAUSE_BY_AUDIO_FOCUS :
- mController.getTransportControls().sendCustomAction(ACTION_PAUSE_BY_AUDIO_FOCUS, null);
- break;
- case ACTION_RESUME_BY_AUDIO_FOCUS :
- mController.getTransportControls().sendCustomAction(ACTION_RESUME_BY_AUDIO_FOCUS, null);
- break;
- }
- }
-
- private void getGeckoPreference() {
- mPrefsObserver = new PrefsHelper.PrefHandlerBase() {
- @Override
- public void prefValue(String pref, boolean value) {
- if (pref.equals(MEDIA_CONTROL_PREF)) {
- mIsMediaControlPrefOn = value;
-
- // If media is playing, we just need to create or remove
- // the media control interface.
- if (mActionState.equals(ACTION_RESUME)) {
- notifyControlInterfaceChanged(mIsMediaControlPrefOn ?
- ACTION_PAUSE : ACTION_STOP);
- }
-
- // If turn off pref during pausing, except removing media
- // interface, we also need to stop the service and notify
- // gecko about that.
- if (mActionState.equals(ACTION_PAUSE) &&
- !mIsMediaControlPrefOn) {
- Intent intent = new Intent(getApplicationContext(), MediaControlService.class);
- intent.setAction(ACTION_STOP);
- handleIntent(intent);
- }
- }
- }
- };
- PrefsHelper.addObserver(mPrefs, mPrefsObserver);
- }
-
- private void initMediaSession() {
- // Android MediaSession is introduced since version L.
- mSession = new MediaSession(getApplicationContext(),
- "fennec media session");
- mController = new MediaController(getApplicationContext(),
- mSession.getSessionToken());
-
- mSession.setCallback(new MediaSession.Callback() {
- @Override
- public void onCustomAction(String action, Bundle extras) {
- if (action.equals(ACTION_PAUSE_BY_AUDIO_FOCUS)) {
- Log.d(LOGTAG, "Controller, pause by audio focus changed");
- notifyControlInterfaceChanged(ACTION_RESUME);
- } else if (action.equals(ACTION_RESUME_BY_AUDIO_FOCUS)) {
- Log.d(LOGTAG, "Controller, resume by audio focus changed");
- notifyControlInterfaceChanged(ACTION_PAUSE);
- }
- }
-
- @Override
- public void onPlay() {
- Log.d(LOGTAG, "Controller, onPlay");
- super.onPlay();
- notifyControlInterfaceChanged(ACTION_PAUSE);
- notifyObservers("MediaControl", "resumeMedia");
- // To make sure we always own audio focus during playing.
- AudioFocusAgent.notifyStartedPlaying();
- }
-
- @Override
- public void onPause() {
- Log.d(LOGTAG, "Controller, onPause");
- super.onPause();
- notifyControlInterfaceChanged(ACTION_RESUME);
- notifyObservers("MediaControl", "mediaControlPaused");
- AudioFocusAgent.notifyStoppedPlaying();
- }
-
- @Override
- public void onStop() {
- Log.d(LOGTAG, "Controller, onStop");
- super.onStop();
- notifyControlInterfaceChanged(ACTION_STOP);
- notifyObservers("MediaControl", "mediaControlStopped");
- mTabReference = new WeakReference<>(null);
- }
- });
- }
-
- private void notifyObservers(String topic, String data) {
- GeckoAppShell.notifyObservers(topic, data);
- }
-
- private boolean isNeedToRemoveControlInterface(String action) {
- return action.equals(ACTION_STOP);
- }
-
- private void notifyControlInterfaceChanged(final String uiAction) {
- if (!mInitialize) {
- return;
- }
-
- Log.d(LOGTAG, "notifyControlInterfaceChanged, action = " + uiAction);
-
- if (isNeedToRemoveControlInterface(uiAction)) {
- stopForeground(false);
- NotificationManagerCompat.from(this).cancel(MEDIA_CONTROL_ID);
- setActionState(uiAction);
- return;
- }
-
- if (!mIsMediaControlPrefOn) {
- return;
- }
-
- final Tab tab = mTabReference.get();
-
- if (tab == null) {
- return;
- }
-
- setActionState(uiAction);
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- updateNotification(tab, uiAction);
- }
- });
- }
-
- private void setActionState(final String uiAction) {
- switch (uiAction) {
- case ACTION_PAUSE:
- mActionState = ACTION_RESUME;
- break;
- case ACTION_RESUME:
- mActionState = ACTION_PAUSE;
- break;
- case ACTION_STOP:
- mActionState = ACTION_STOP;
- break;
- }
- }
-
- private void updateNotification(Tab tab, String action) {
- ThreadUtils.assertNotOnUiThread();
-
- final Notification.MediaStyle style = new Notification.MediaStyle();
- style.setShowActionsInCompactView(0);
-
- final boolean isPlaying = isMediaPlaying();
- final int visibility = tab.isPrivate() ?
- Notification.VISIBILITY_PRIVATE : Notification.VISIBILITY_PUBLIC;
-
- final Notification notification = new Notification.Builder(this)
- .setSmallIcon(R.drawable.flat_icon)
- .setLargeIcon(generateCoverArt(tab))
- .setContentTitle(tab.getTitle())
- .setContentText(tab.getURL())
- .setContentIntent(createContentIntent(tab.getId()))
- .setDeleteIntent(createDeleteIntent())
- .setStyle(style)
- .addAction(createNotificationAction(action))
- .setOngoing(isPlaying)
- .setShowWhen(false)
- .setWhen(0)
- .setVisibility(visibility)
- .build();
-
- if (isPlaying) {
- startForeground(MEDIA_CONTROL_ID, notification);
- } else {
- stopForeground(false);
- NotificationManagerCompat.from(this)
- .notify(MEDIA_CONTROL_ID, notification);
- }
- }
-
- private Notification.Action createNotificationAction(String action) {
- boolean isPlayAction = action.equals(ACTION_RESUME);
-
- int icon = isPlayAction ? R.drawable.ic_media_play : R.drawable.ic_media_pause;
- String title = getString(isPlayAction ? R.string.media_play : R.string.media_pause);
-
- final Intent intent = new Intent(getApplicationContext(), MediaControlService.class);
- intent.setAction(action);
- final PendingIntent pendingIntent = PendingIntent.getService(getApplicationContext(), 1, intent, 0);
-
- //noinspection deprecation - The new constructor is only for API > 23
- return new Notification.Action.Builder(icon, title, pendingIntent).build();
- }
-
- private PendingIntent createContentIntent(int tabId) {
- Intent intent = new Intent(getApplicationContext(), BrowserApp.class);
- intent.setAction(GeckoApp.ACTION_SWITCH_TAB);
- intent.putExtra("TabId", tabId);
- return PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- private PendingIntent createDeleteIntent() {
- Intent intent = new Intent(getApplicationContext(), MediaControlService.class);
- intent.setAction(ACTION_STOP);
- return PendingIntent.getService(getApplicationContext(), 1, intent, 0);
- }
-
- private Bitmap generateCoverArt(Tab tab) {
- final Bitmap favicon = tab.getFavicon();
-
- // If we do not have a favicon or if it's smaller than 72 pixels then just use the default icon.
- if (favicon == null || favicon.getWidth() < minCoverSize || favicon.getHeight() < minCoverSize) {
- // Use the launcher icon as fallback
- return BitmapFactory.decodeResource(getResources(), R.drawable.notification_media);
- }
-
- // Favicon should at least have half of the size of the cover
- int width = Math.max(favicon.getWidth(), coverSize / 2);
- int height = Math.max(favicon.getHeight(), coverSize / 2);
-
- final Bitmap coverArt = Bitmap.createBitmap(coverSize, coverSize, Bitmap.Config.ARGB_8888);
- final Canvas canvas = new Canvas(coverArt);
- canvas.drawColor(0xFF777777);
-
- int left = Math.max(0, (coverArt.getWidth() / 2) - (width / 2));
- int right = Math.min(coverSize, left + width);
- int top = Math.max(0, (coverArt.getHeight() / 2) - (height / 2));
- int bottom = Math.min(coverSize, top + height);
-
- final Paint paint = new Paint();
- paint.setAntiAlias(true);
-
- canvas.drawBitmap(favicon,
- new Rect(0, 0, favicon.getWidth(), favicon.getHeight()),
- new Rect(left, top, right, bottom),
- paint);
-
- return coverArt;
- }
-} \ No newline at end of file
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/MediaDrmProxy.java b/mobile/android/base/java/org/mozilla/gecko/media/MediaDrmProxy.java
deleted file mode 100644
index faca2389e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/MediaDrmProxy.java
+++ /dev/null
@@ -1,307 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-
-package org.mozilla.gecko.media;
-
-import java.util.ArrayList;
-import java.util.UUID;
-
-import org.mozilla.gecko.mozglue.JNIObject;
-import org.mozilla.gecko.annotation.WrapForJNI;
-import org.mozilla.gecko.AppConstants;
-
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
-import android.media.MediaCrypto;
-import android.media.MediaDrm;
-import android.util.Log;
-import android.os.Build;
-
-public final class MediaDrmProxy {
- private static final String LOGTAG = "GeckoMediaDrmProxy";
- private static final boolean DEBUG = false;
- private static final UUID WIDEVINE_SCHEME_UUID =
- new UUID(0xedef8ba979d64aceL, 0xa3c827dcd51d21edL);
-
- private static final String WIDEVINE_KEY_SYSTEM = "com.widevine.alpha";
- @WrapForJNI
- private static final String AAC = "audio/mp4a-latm";
- @WrapForJNI
- private static final String AVC = "video/avc";
- @WrapForJNI
- private static final String VORBIS = "audio/vorbis";
- @WrapForJNI
- private static final String VP8 = "video/x-vnd.on2.vp8";
- @WrapForJNI
- private static final String VP9 = "video/x-vnd.on2.vp9";
- @WrapForJNI
- private static final String OPUS = "audio/opus";
-
- // A flag to avoid using the native object that has been destroyed.
- private boolean mDestroyed;
- private GeckoMediaDrm mImpl;
- public static ArrayList<MediaDrmProxy> mProxyList = new ArrayList<MediaDrmProxy>();
-
- private static boolean isSystemSupported() {
- // Support versions >= LOLLIPOP
- if (AppConstants.Versions.preLollipop) {
- if (DEBUG) Log.d(LOGTAG, "System Not supported !!, current SDK version is " + Build.VERSION.SDK_INT);
- return false;
- }
- return true;
- }
-
- @WrapForJNI
- public static boolean isSchemeSupported(String keySystem) {
- if (!isSystemSupported()) {
- return false;
- }
- if (keySystem.equals(WIDEVINE_KEY_SYSTEM)) {
- return MediaDrm.isCryptoSchemeSupported(WIDEVINE_SCHEME_UUID)
- && MediaCrypto.isCryptoSchemeSupported(WIDEVINE_SCHEME_UUID);
- }
- if (DEBUG) Log.d(LOGTAG, "isSchemeSupported key sytem = " + keySystem);
- return false;
- }
-
- @WrapForJNI
- public static boolean IsCryptoSchemeSupported(String keySystem,
- String container) {
- if (!isSystemSupported()) {
- return false;
- }
- if (keySystem.equals(WIDEVINE_KEY_SYSTEM)) {
- return MediaDrm.isCryptoSchemeSupported(WIDEVINE_SCHEME_UUID, container);
- }
- if (DEBUG) Log.d(LOGTAG, "cannot decrypt key sytem = " + keySystem + ", container = " + container);
- return false;
- }
-
- @WrapForJNI
- public static boolean CanDecode(String mimeType) {
- for (int i = 0; i < MediaCodecList.getCodecCount(); ++i) {
- MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
- if (info.isEncoder()) {
- continue;
- }
- for (String m : info.getSupportedTypes()) {
- if (m.equals(mimeType)) {
- return true;
- }
- }
- }
- if (DEBUG) Log.d(LOGTAG, "cannot decode mimetype = " + mimeType);
- return false;
- }
-
- // Interface for callback to native.
- public interface Callbacks {
- void onSessionCreated(int createSessionToken,
- int promiseId,
- byte[] sessionId,
- byte[] request);
-
- void onSessionUpdated(int promiseId, byte[] sessionId);
-
- void onSessionClosed(int promiseId, byte[] sessionId);
-
- void onSessionMessage(byte[] sessionId,
- int sessionMessageType,
- byte[] request);
-
- void onSessionError(byte[] sessionId,
- String message);
-
- // MediaDrm.KeyStatus is available in API level 23(M)
- // https://developer.android.com/reference/android/media/MediaDrm.KeyStatus.html
- // For compatibility between L and M above, we'll unwrap the KeyStatus structure
- // and store the keyid and status into SessionKeyInfo and pass to native(MediaDrmCDMProxy).
- void onSessionBatchedKeyChanged(byte[] sessionId,
- SessionKeyInfo[] keyInfos);
-
- void onRejectPromise(int promiseId,
- String message);
- } // Callbacks
-
- public static class NativeMediaDrmProxyCallbacks extends JNIObject implements Callbacks {
- @WrapForJNI(calledFrom = "gecko")
- NativeMediaDrmProxyCallbacks() {}
-
- @Override
- @WrapForJNI(dispatchTo = "gecko")
- public native void onSessionCreated(int createSessionToken,
- int promiseId,
- byte[] sessionId,
- byte[] request);
-
- @Override
- @WrapForJNI(dispatchTo = "gecko")
- public native void onSessionUpdated(int promiseId, byte[] sessionId);
-
- @Override
- @WrapForJNI(dispatchTo = "gecko")
- public native void onSessionClosed(int promiseId, byte[] sessionId);
-
- @Override
- @WrapForJNI(dispatchTo = "gecko")
- public native void onSessionMessage(byte[] sessionId,
- int sessionMessageType,
- byte[] request);
-
- @Override
- @WrapForJNI(dispatchTo = "gecko")
- public native void onSessionError(byte[] sessionId,
- String message);
-
- @Override
- @WrapForJNI(dispatchTo = "gecko")
- public native void onSessionBatchedKeyChanged(byte[] sessionId,
- SessionKeyInfo[] keyInfos);
-
- @Override
- @WrapForJNI(dispatchTo = "gecko")
- public native void onRejectPromise(int promiseId,
- String message);
-
- @Override // JNIObject
- protected void disposeNative() {
- throw new UnsupportedOperationException();
- }
- } // NativeMediaDrmProxyCallbacks
-
- // A proxy to callback from LocalMediaDrmBridge to native instance.
- public static class MediaDrmProxyCallbacks implements GeckoMediaDrm.Callbacks {
- private final Callbacks mNativeCallbacks;
- private final MediaDrmProxy mProxy;
-
- public MediaDrmProxyCallbacks(MediaDrmProxy proxy, Callbacks callbacks) {
- mNativeCallbacks = callbacks;
- mProxy = proxy;
- }
-
- @Override
- public void onSessionCreated(int createSessionToken,
- int promiseId,
- byte[] sessionId,
- byte[] request) {
- if (!mProxy.isDestroyed()) {
- mNativeCallbacks.onSessionCreated(createSessionToken,
- promiseId,
- sessionId,
- request);
- }
- }
-
- @Override
- public void onSessionUpdated(int promiseId, byte[] sessionId) {
- if (!mProxy.isDestroyed()) {
- mNativeCallbacks.onSessionUpdated(promiseId, sessionId);
- }
- }
-
- @Override
- public void onSessionClosed(int promiseId, byte[] sessionId) {
- if (!mProxy.isDestroyed()) {
- mNativeCallbacks.onSessionClosed(promiseId, sessionId);
- }
- }
-
- @Override
- public void onSessionMessage(byte[] sessionId,
- int sessionMessageType,
- byte[] request) {
- if (!mProxy.isDestroyed()) {
- mNativeCallbacks.onSessionMessage(sessionId, sessionMessageType, request);
- }
- }
-
- @Override
- public void onSessionError(byte[] sessionId,
- String message) {
- if (!mProxy.isDestroyed()) {
- mNativeCallbacks.onSessionError(sessionId, message);
- }
- }
-
- @Override
- public void onSessionBatchedKeyChanged(byte[] sessionId,
- SessionKeyInfo[] keyInfos) {
- if (!mProxy.isDestroyed()) {
- mNativeCallbacks.onSessionBatchedKeyChanged(sessionId, keyInfos);
- }
- }
-
- @Override
- public void onRejectPromise(int promiseId,
- String message) {
- if (!mProxy.isDestroyed()) {
- mNativeCallbacks.onRejectPromise(promiseId, message);
- }
- }
- } // MediaDrmProxyCallbacks
-
- public boolean isDestroyed() {
- return mDestroyed;
- }
-
- @WrapForJNI(calledFrom = "gecko")
- public static MediaDrmProxy create(String keySystem,
- Callbacks nativeCallbacks,
- boolean isRemote) {
- // TODO: Will implement {Local,Remote}MediaDrmBridge instantiation by
- // '''isRemote''' flag in Bug 1307818.
- MediaDrmProxy proxy = new MediaDrmProxy(keySystem, nativeCallbacks);
- return proxy;
- }
-
- MediaDrmProxy(String keySystem, Callbacks nativeCallbacks) {
- if (DEBUG) Log.d(LOGTAG, "Constructing MediaDrmProxy");
- // TODO: Bug 1306185 will implement the LocalMediaDrmBridge as an impl
- // of GeckoMediaDrm for in-process decoding mode.
- //mImpl = new LocalMediaDrmBridge(keySystem);
- mImpl.setCallbacks(new MediaDrmProxyCallbacks(this, nativeCallbacks));
- mProxyList.add(this);
- }
-
- @WrapForJNI
- private void createSession(int createSessionToken,
- int promiseId,
- String initDataType,
- byte[] initData) {
- if (DEBUG) Log.d(LOGTAG, "createSession, promiseId = " + promiseId);
- mImpl.createSession(createSessionToken,
- promiseId,
- initDataType,
- initData);
- }
-
- @WrapForJNI
- private void updateSession(int promiseId, String sessionId, byte[] response) {
- if (DEBUG) Log.d(LOGTAG, "updateSession, primiseId(" + promiseId + "sessionId(" + sessionId + ")");
- mImpl.updateSession(promiseId, sessionId, response);
- }
-
- @WrapForJNI
- private void closeSession(int promiseId, String sessionId) {
- if (DEBUG) Log.d(LOGTAG, "closeSession, primiseId(" + promiseId + "sessionId(" + sessionId + ")");
- mImpl.closeSession(promiseId, sessionId);
- }
-
- @WrapForJNI // Called when natvie object is destroyed.
- private void destroy() {
- if (DEBUG) Log.d(LOGTAG, "destroy!! Native object is destroyed.");
- if (mDestroyed) {
- return;
- }
- mDestroyed = true;
- release();
- }
-
- private void release() {
- if (DEBUG) Log.d(LOGTAG, "release");
- mProxyList.remove(this);
- mImpl.release();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/MediaManager.java b/mobile/android/base/java/org/mozilla/gecko/media/MediaManager.java
deleted file mode 100644
index fcb0fc659..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/MediaManager.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-
-import org.mozilla.gecko.mozglue.GeckoLoader;
-
-public final class MediaManager extends Service {
- private static boolean sNativeLibLoaded;
-
- private Binder mBinder = new IMediaManager.Stub() {
- @Override
- public ICodec createCodec() throws RemoteException {
- return new Codec();
- }
-
- @Override
- public IMediaDrmBridge createRemoteMediaDrmBridge(String keySystem,
- String stubId)
- throws RemoteException {
- return new RemoteMediaDrmBridgeStub(keySystem, stubId);
- }
- };
-
- @Override
- public synchronized void onCreate() {
- if (!sNativeLibLoaded) {
- GeckoLoader.doLoadLibrary(this, "mozglue");
- sNativeLibLoaded = true;
- }
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-} \ No newline at end of file
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/RemoteManager.java b/mobile/android/base/java/org/mozilla/gecko/media/RemoteManager.java
deleted file mode 100644
index 260ca73c1..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/RemoteManager.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.Telemetry;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.media.MediaFormat;
-import android.os.DeadObjectException;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.view.Surface;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.LinkedList;
-import java.util.List;
-
-public final class RemoteManager implements IBinder.DeathRecipient {
- private static final String LOGTAG = "GeckoRemoteManager";
- private static final boolean DEBUG = false;
- private static RemoteManager sRemoteManager = null;
-
- public synchronized static RemoteManager getInstance() {
- if (sRemoteManager == null) {
- sRemoteManager = new RemoteManager();
- }
-
- sRemoteManager.init();
- return sRemoteManager;
- }
-
- private List<CodecProxy> mProxies = new LinkedList<CodecProxy>();
- private volatile IMediaManager mRemote;
- private volatile CountDownLatch mConnectionLatch;
- private final ServiceConnection mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- if (DEBUG) Log.d(LOGTAG, "service connected");
- try {
- service.linkToDeath(RemoteManager.this, 0);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- mRemote = IMediaManager.Stub.asInterface(service);
- if (mConnectionLatch != null) {
- mConnectionLatch.countDown();
- }
- }
-
- /**
- * Called when a connection to the Service has been lost. This typically
- * happens when the process hosting the service has crashed or been killed.
- * This does <em>not</em> remove the ServiceConnection itself -- this
- * binding to the service will remain active, and you will receive a call
- * to {@link #onServiceConnected} when the Service is next running.
- *
- * @param name The concrete component name of the service whose
- * connection has been lost.
- */
- @Override
- public void onServiceDisconnected(ComponentName name) {
- if (DEBUG) Log.d(LOGTAG, "service disconnected");
- mRemote.asBinder().unlinkToDeath(RemoteManager.this, 0);
- mRemote = null;
- if (mConnectionLatch != null) {
- mConnectionLatch.countDown();
- }
- }
- };
-
- private synchronized boolean init() {
- if (mRemote != null) {
- return true;
- }
-
- if (DEBUG) Log.d(LOGTAG, "init remote manager " + this);
- Context appCtxt = GeckoAppShell.getApplicationContext();
- if (DEBUG) Log.d(LOGTAG, "ctxt=" + appCtxt);
- appCtxt.bindService(new Intent(appCtxt, MediaManager.class),
- mConnection, Context.BIND_AUTO_CREATE);
- if (!waitConnection()) {
- appCtxt.unbindService(mConnection);
- return false;
- }
- return true;
- }
-
- private boolean waitConnection() {
- boolean ok = false;
-
- mConnectionLatch = new CountDownLatch(1);
- try {
- int retryCount = 0;
- while (retryCount < 5) {
- if (DEBUG) Log.d(LOGTAG, "waiting for connection latch:" + mConnectionLatch);
- mConnectionLatch.await(1, TimeUnit.SECONDS);
- if (mConnectionLatch.getCount() == 0) {
- break;
- }
- Log.w(LOGTAG, "Creator not connected in 1s. Try again.");
- retryCount++;
- }
- ok = true;
- } catch (InterruptedException e) {
- Log.e(LOGTAG, "service not connected in 5 seconds. Stop waiting.");
- e.printStackTrace();
- }
- mConnectionLatch = null;
-
- return ok;
- }
-
- public synchronized CodecProxy createCodec(MediaFormat format,
- Surface surface,
- CodecProxy.Callbacks callbacks) {
- if (mRemote == null) {
- if (DEBUG) Log.d(LOGTAG, "createCodec failed due to not initialize");
- return null;
- }
- try {
- ICodec remote = mRemote.createCodec();
- CodecProxy proxy = CodecProxy.createCodecProxy(format, surface, callbacks);
- if (proxy.init(remote)) {
- mProxies.add(proxy);
- return proxy;
- } else {
- return null;
- }
- } catch (RemoteException e) {
- e.printStackTrace();
- return null;
- }
- }
-
- private static final String MEDIA_DECODING_PROCESS_CRASH = "MEDIA_DECODING_PROCESS_CRASH";
- private void reportDecodingProcessCrash() {
- Telemetry.addToHistogram(MEDIA_DECODING_PROCESS_CRASH, 1);
- }
-
- public synchronized IMediaDrmBridge createRemoteMediaDrmBridge(String keySystem,
- String stubId) {
- if (mRemote == null) {
- if (DEBUG) Log.d(LOGTAG, "createRemoteMediaDrmBridge failed due to not initialize");
- return null;
- }
- try {
- IMediaDrmBridge remoteBridge =
- mRemote.createRemoteMediaDrmBridge(keySystem, stubId);
- return remoteBridge;
- } catch (RemoteException e) {
- Log.e(LOGTAG, "Got exception during createRemoteMediaDrmBridge().", e);
- return null;
- }
- }
-
- @Override
- public void binderDied() {
- Log.e(LOGTAG, "remote codec is dead");
- reportDecodingProcessCrash();
- handleRemoteDeath();
- }
-
- private synchronized void handleRemoteDeath() {
- // Wait for onServiceDisconnected()
- if (!waitConnection()) {
- notifyError(true);
- return;
- }
- // Restart
- if (init() && recoverRemoteCodec()) {
- notifyError(false);
- } else {
- notifyError(true);
- }
- }
-
- private synchronized void notifyError(boolean fatal) {
- for (CodecProxy proxy : mProxies) {
- proxy.reportError(fatal);
- }
- }
-
- private synchronized boolean recoverRemoteCodec() {
- if (DEBUG) Log.d(LOGTAG, "recover codec");
- boolean ok = true;
- try {
- for (CodecProxy proxy : mProxies) {
- ok &= proxy.init(mRemote.createCodec());
- }
- return ok;
- } catch (RemoteException e) {
- return false;
- }
- }
-
- public void releaseCodec(CodecProxy proxy) throws DeadObjectException, RemoteException {
- if (mRemote == null) {
- if (DEBUG) Log.d(LOGTAG, "releaseCodec called but not initialized yet");
- return;
- }
- proxy.deinit();
- synchronized (this) {
- if (mProxies.remove(proxy) && mProxies.isEmpty()) {
- release();
- }
- }
- }
-
- private void release() {
- if (DEBUG) Log.d(LOGTAG, "release remote manager " + this);
- Context appCtxt = GeckoAppShell.getApplicationContext();
- mRemote.asBinder().unlinkToDeath(this, 0);
- mRemote = null;
- appCtxt.unbindService(mConnection);
- }
-} // RemoteManager \ No newline at end of file
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridge.java b/mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridge.java
deleted file mode 100644
index d65bb7872..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridge.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-import android.media.MediaCrypto;
-import android.util.Log;
-
-final class RemoteMediaDrmBridge implements GeckoMediaDrm {
- private static final String LOGTAG = "GeckoRemoteMediaDrmBridge";
- private static final boolean DEBUG = false;
- private CallbacksForwarder mCallbacksFwd;
- private IMediaDrmBridge mRemote;
-
- // Forward callbacks from remote bridge stub to MediaDrmProxy.
- private static class CallbacksForwarder extends IMediaDrmBridgeCallbacks.Stub {
- private final GeckoMediaDrm.Callbacks mProxyCallbacks;
- CallbacksForwarder(Callbacks callbacks) {
- assertTrue(callbacks != null);
- mProxyCallbacks = callbacks;
- }
-
- @Override
- public void onSessionCreated(int createSessionToken,
- int promiseId,
- byte[] sessionId,
- byte[] request) {
- mProxyCallbacks.onSessionCreated(createSessionToken,
- promiseId,
- sessionId,
- request);
- }
-
- @Override
- public void onSessionUpdated(int promiseId, byte[] sessionId) {
- mProxyCallbacks.onSessionUpdated(promiseId, sessionId);
- }
-
- @Override
- public void onSessionClosed(int promiseId, byte[] sessionId) {
- mProxyCallbacks.onSessionClosed(promiseId, sessionId);
- }
-
- @Override
- public void onSessionMessage(byte[] sessionId,
- int sessionMessageType,
- byte[] request) {
- mProxyCallbacks.onSessionMessage(sessionId, sessionMessageType, request);
- }
-
- @Override
- public void onSessionError(byte[] sessionId, String message) {
- mProxyCallbacks.onSessionError(sessionId, message);
- }
-
- @Override
- public void onSessionBatchedKeyChanged(byte[] sessionId,
- SessionKeyInfo[] keyInfos) {
- mProxyCallbacks.onSessionBatchedKeyChanged(sessionId, keyInfos);
- }
-
- @Override
- public void onRejectPromise(int promiseId, String message) {
- mProxyCallbacks.onRejectPromise(promiseId, message);
- }
- } // CallbacksForwarder
-
- /* package-private */ static void assertTrue(boolean condition) {
- if (DEBUG && !condition) {
- throw new AssertionError("Expected condition to be true");
- }
- }
-
- public RemoteMediaDrmBridge(IMediaDrmBridge remoteBridge) {
- assertTrue(remoteBridge != null);
- mRemote = remoteBridge;
- }
-
- @Override
- public synchronized void setCallbacks(Callbacks callbacks) {
- if (DEBUG) Log.d(LOGTAG, "setCallbacks()");
- assertTrue(callbacks != null);
- assertTrue(mRemote != null);
-
- mCallbacksFwd = new CallbacksForwarder(callbacks);
- try {
- mRemote.setCallbacks(mCallbacksFwd);
- } catch (Exception e) {
- Log.e(LOGTAG, "Got exception during setCallbacks", e);
- }
- }
-
- @Override
- public synchronized void createSession(int createSessionToken,
- int promiseId,
- String initDataType,
- byte[] initData) {
- if (DEBUG) Log.d(LOGTAG, "createSession()");
-
- try {
- mRemote.createSession(createSessionToken, promiseId, initDataType, initData);
- } catch (Exception e) {
- Log.e(LOGTAG, "Got exception while creating remote session.", e);
- mCallbacksFwd.onRejectPromise(promiseId, "Failed to create session.");
- }
- }
-
- @Override
- public synchronized void updateSession(int promiseId, String sessionId, byte[] response) {
- if (DEBUG) Log.d(LOGTAG, "updateSession()");
-
- try {
- mRemote.updateSession(promiseId, sessionId, response);
- } catch (Exception e) {
- Log.e(LOGTAG, "Got exception while updating remote session.", e);
- mCallbacksFwd.onRejectPromise(promiseId, "Failed to update session.");
- }
- }
-
- @Override
- public synchronized void closeSession(int promiseId, String sessionId) {
- if (DEBUG) Log.d(LOGTAG, "closeSession()");
-
- try {
- mRemote.closeSession(promiseId, sessionId);
- } catch (Exception e) {
- Log.e(LOGTAG, "Got exception while closing remote session.", e);
- mCallbacksFwd.onRejectPromise(promiseId, "Failed to close session.");
- }
- }
-
- @Override
- public synchronized void release() {
- if (DEBUG) Log.d(LOGTAG, "release()");
-
- try {
- mRemote.release();
- } catch (Exception e) {
- Log.e(LOGTAG, "Got exception while releasing RemoteDrmBridge.", e);
- }
- mRemote = null;
- mCallbacksFwd = null;
- }
-
- @Override
- public synchronized MediaCrypto getMediaCrypto() {
- if (DEBUG) Log.d(LOGTAG, "getMediaCrypto(), should not enter here!");
- assertTrue(false);
- return null;
- }
-} \ No newline at end of file
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridgeStub.java b/mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridgeStub.java
deleted file mode 100644
index 8aed0f851..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridgeStub.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-import org.mozilla.gecko.AppConstants;
-
-import java.util.ArrayList;
-
-import android.media.MediaCrypto;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-final class RemoteMediaDrmBridgeStub extends IMediaDrmBridge.Stub implements IBinder.DeathRecipient {
- private static final String LOGTAG = "GeckoRemoteMediaDrmBridgeStub";
- private static final boolean DEBUG = false;
- private volatile IMediaDrmBridgeCallbacks mCallbacks = null;
-
- // Underlying bridge implmenetaion, i.e. GeckoMediaDrmBrdigeV21.
- private GeckoMediaDrm mBridge = null;
-
- // mStubId is initialized during stub construction. It should be a unique
- // string which is generated in MediaDrmProxy in Fennec App process and is
- // used for Codec to obtain corresponding MediaCrypto as input to achieve
- // decryption.
- // The generated stubId will be delivered to Codec via a code path starting
- // from MediaDrmProxy -> MediaDrmCDMProxy -> RemoteDataDecoder => IPC => Codec.
- private String mStubId = "";
-
- public static ArrayList<RemoteMediaDrmBridgeStub> mBridgeStubs =
- new ArrayList<RemoteMediaDrmBridgeStub>();
-
- private String getId() {
- return mStubId;
- }
-
- private MediaCrypto getMediaCryptoFromBridge() {
- return mBridge != null ? mBridge.getMediaCrypto() : null;
- }
-
- public static synchronized MediaCrypto getMediaCrypto(String stubId) {
- if (DEBUG) Log.d(LOGTAG, "getMediaCrypto()");
-
- for (int i = 0; i < mBridgeStubs.size(); i++) {
- if (mBridgeStubs.get(i) != null &&
- mBridgeStubs.get(i).getId().equals(stubId)) {
- return mBridgeStubs.get(i).getMediaCryptoFromBridge();
- }
- }
- return null;
- }
-
- // Callback to RemoteMediaDrmBridge.
- private final class Callbacks implements GeckoMediaDrm.Callbacks {
- private IMediaDrmBridgeCallbacks mRemoteCallbacks;
-
- public Callbacks(IMediaDrmBridgeCallbacks remote) {
- mRemoteCallbacks = remote;
- }
-
- @Override
- public void onSessionCreated(int createSessionToken,
- int promiseId,
- byte[] sessionId,
- byte[] request) {
- if (DEBUG) Log.d(LOGTAG, "onSessionCreated()");
- try {
- mRemoteCallbacks.onSessionCreated(createSessionToken,
- promiseId,
- sessionId,
- request);
- } catch (RemoteException e) {
- Log.e(LOGTAG, "Exception ! Dead recipient !!", e);
- }
- }
-
- @Override
- public void onSessionUpdated(int promiseId, byte[] sessionId) {
- if (DEBUG) Log.d(LOGTAG, "onSessionUpdated()");
- try {
- mRemoteCallbacks.onSessionUpdated(promiseId, sessionId);
- } catch (RemoteException e) {
- Log.e(LOGTAG, "Exception ! Dead recipient !!", e);
- }
- }
-
- @Override
- public void onSessionClosed(int promiseId, byte[] sessionId) {
- if (DEBUG) Log.d(LOGTAG, "onSessionClosed()");
- try {
- mRemoteCallbacks.onSessionClosed(promiseId, sessionId);
- } catch (RemoteException e) {
- Log.e(LOGTAG, "Exception ! Dead recipient !!", e);
- }
- }
-
- @Override
- public void onSessionMessage(byte[] sessionId,
- int sessionMessageType,
- byte[] request) {
- if (DEBUG) Log.d(LOGTAG, "onSessionMessage()");
- try {
- mRemoteCallbacks.onSessionMessage(sessionId, sessionMessageType, request);
- } catch (RemoteException e) {
- Log.e(LOGTAG, "Exception ! Dead recipient !!", e);
- }
- }
-
- @Override
- public void onSessionError(byte[] sessionId, String message) {
- if (DEBUG) Log.d(LOGTAG, "onSessionError()");
- try {
- mRemoteCallbacks.onSessionError(sessionId, message);
- } catch (RemoteException e) {
- Log.e(LOGTAG, "Exception ! Dead recipient !!", e);
- }
- }
-
- @Override
- public void onSessionBatchedKeyChanged(byte[] sessionId,
- SessionKeyInfo[] keyInfos) {
- if (DEBUG) Log.d(LOGTAG, "onSessionBatchedKeyChanged()");
- try {
- mRemoteCallbacks.onSessionBatchedKeyChanged(sessionId, keyInfos);
- } catch (RemoteException e) {
- Log.e(LOGTAG, "Exception ! Dead recipient !!", e);
- }
- }
-
- @Override
- public void onRejectPromise(int promiseId, String message) {
- if (DEBUG) Log.d(LOGTAG, "onRejectPromise()");
- try {
- mRemoteCallbacks.onRejectPromise(promiseId, message);
- } catch (RemoteException e) {
- Log.e(LOGTAG, "Exception ! Dead recipient !!", e);
- }
- }
- }
-
- /* package-private */ void assertTrue(boolean condition) {
- if (DEBUG && !condition) {
- throw new AssertionError("Expected condition to be true");
- }
- }
-
- RemoteMediaDrmBridgeStub(String keySystem, String stubId) throws RemoteException {
- if (AppConstants.Versions.preLollipop) {
- Log.e(LOGTAG, "Pre-Lollipop should never enter here!!");
- throw new RemoteException("Error, unsupported version!");
- }
- try {
- if (AppConstants.Versions.feature21Plus &&
- AppConstants.Versions.preMarshmallow) {
- mBridge = new GeckoMediaDrmBridgeV21(keySystem);
- } else {
- mBridge = new GeckoMediaDrmBridgeV23(keySystem);
- }
- mStubId = stubId;
- mBridgeStubs.add(this);
- } catch (Exception e) {
- throw new RemoteException("RemoteMediaDrmBridgeStub cannot create bridge implementation.");
- }
- }
-
- @Override
- public synchronized void setCallbacks(IMediaDrmBridgeCallbacks callbacks) throws RemoteException {
- if (DEBUG) Log.d(LOGTAG, "setCallbacks()");
- assertTrue(mBridge != null);
- assertTrue(callbacks != null);
- mCallbacks = callbacks;
- callbacks.asBinder().linkToDeath(this, 0);
- mBridge.setCallbacks(new Callbacks(mCallbacks));
- }
-
- @Override
- public synchronized void createSession(int createSessionToken,
- int promiseId,
- String initDataType,
- byte[] initData) throws RemoteException {
- if (DEBUG) Log.d(LOGTAG, "createSession()");
- try {
- assertTrue(mCallbacks != null);
- assertTrue(mBridge != null);
- mBridge.createSession(createSessionToken,
- promiseId,
- initDataType,
- initData);
- } catch (Exception e) {
- Log.e(LOGTAG, "Failed to createSession.", e);
- mCallbacks.onRejectPromise(promiseId, "Failed to createSession.");
- }
- }
-
- @Override
- public synchronized void updateSession(int promiseId,
- String sessionId,
- byte[] response) throws RemoteException {
- if (DEBUG) Log.d(LOGTAG, "updateSession()");
- try {
- assertTrue(mCallbacks != null);
- assertTrue(mBridge != null);
- mBridge.updateSession(promiseId, sessionId, response);
- } catch (Exception e) {
- Log.e(LOGTAG, "Failed to updateSession.", e);
- mCallbacks.onRejectPromise(promiseId, "Failed to updateSession.");
- }
- }
-
- @Override
- public synchronized void closeSession(int promiseId, String sessionId) throws RemoteException {
- if (DEBUG) Log.d(LOGTAG, "closeSession()");
- try {
- assertTrue(mCallbacks != null);
- assertTrue(mBridge != null);
- mBridge.closeSession(promiseId, sessionId);
- } catch (Exception e) {
- Log.e(LOGTAG, "Failed to closeSession.", e);
- mCallbacks.onRejectPromise(promiseId, "Failed to closeSession.");
- }
- }
-
- // IBinder.DeathRecipient
- @Override
- public synchronized void binderDied() {
- Log.e(LOGTAG, "Binder died !!");
- try {
- release();
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception ! Dead recipient !!", e);
- }
- }
-
- @Override
- public synchronized void release() {
- if (DEBUG) Log.d(LOGTAG, "release()");
- mBridgeStubs.remove(this);
- if (mBridge != null) {
- mBridge.release();
- mBridge = null;
- }
- mCallbacks.asBinder().unlinkToDeath(this, 0);
- mCallbacks = null;
- mStubId = "";
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/Sample.java b/mobile/android/base/java/org/mozilla/gecko/media/Sample.java
deleted file mode 100644
index b7a98da8a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/Sample.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-import android.media.MediaCodec;
-import android.media.MediaCodec.BufferInfo;
-import android.media.MediaCodec.CryptoInfo;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import org.mozilla.gecko.annotation.WrapForJNI;
-import org.mozilla.gecko.mozglue.SharedMemBuffer;
-import org.mozilla.gecko.mozglue.SharedMemory;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-// Parcelable carrying input/output sample data and info cross process.
-public final class Sample implements Parcelable {
- public static final Sample EOS;
- static {
- BufferInfo eosInfo = new BufferInfo();
- eosInfo.set(0, 0, Long.MIN_VALUE, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
- EOS = new Sample(null, eosInfo, null);
- }
-
- public interface Buffer extends Parcelable {
- int capacity();
- void readFromByteBuffer(ByteBuffer src, int offset, int size) throws IOException;
- void writeToByteBuffer(ByteBuffer dest, int offset, int size) throws IOException;
- void dispose();
- }
-
- private static final class ArrayBuffer implements Buffer {
- private byte[] mArray;
-
- public static final Creator<ArrayBuffer> CREATOR = new Creator<ArrayBuffer>() {
- @Override
- public ArrayBuffer createFromParcel(Parcel in) {
- return new ArrayBuffer(in);
- }
-
- @Override
- public ArrayBuffer[] newArray(int size) {
- return new ArrayBuffer[size];
- }
- };
-
- private ArrayBuffer(Parcel in) {
- mArray = in.createByteArray();
- }
-
- private ArrayBuffer(byte[] bytes) { mArray = bytes; }
-
- @Override
- public int describeContents() { return 0; }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeByteArray(mArray);
- }
-
- @Override
- public int capacity() {
- return mArray != null ? mArray.length : 0;
- }
-
- @Override
- public void readFromByteBuffer(ByteBuffer src, int offset, int size) throws IOException {
- src.position(offset);
- if (mArray == null || mArray.length != size) {
- mArray = new byte[size];
- }
- src.get(mArray, 0, size);
- }
-
- @Override
- public void writeToByteBuffer(ByteBuffer dest, int offset, int size) throws IOException {
- dest.put(mArray, offset, size);
- }
-
- @Override
- public void dispose() {
- mArray = null;
- }
- }
-
- public Buffer buffer;
- @WrapForJNI
- public BufferInfo info;
- public CryptoInfo cryptoInfo;
-
- public static Sample create() { return create(null, new BufferInfo(), null); }
-
- public static Sample create(ByteBuffer src, BufferInfo info, CryptoInfo cryptoInfo) {
- ArrayBuffer buffer = new ArrayBuffer(byteArrayFromBuffer(src, info.offset, info.size));
-
- BufferInfo bufferInfo = new BufferInfo();
- bufferInfo.set(0, info.size, info.presentationTimeUs, info.flags);
-
- return new Sample(buffer, bufferInfo, cryptoInfo);
- }
-
- public static Sample create(SharedMemory sharedMem) {
- return new Sample(new SharedMemBuffer(sharedMem), new BufferInfo(), null);
- }
-
- private Sample(Buffer bytes, BufferInfo info, CryptoInfo cryptoInfo) {
- buffer = bytes;
- this.info = info;
- this.cryptoInfo = cryptoInfo;
- }
-
- private Sample(Parcel in) {
- readInfo(in);
- readCrypto(in);
- buffer = in.readParcelable(Sample.class.getClassLoader());
- }
-
- private void readInfo(Parcel in) {
- int offset = in.readInt();
- int size = in.readInt();
- long pts = in.readLong();
- int flags = in.readInt();
-
- info = new BufferInfo();
- info.set(offset, size, pts, flags);
- }
-
- private void readCrypto(Parcel in) {
- int hasCryptoInfo = in.readInt();
- if (hasCryptoInfo == 0) {
- return;
- }
-
- byte[] iv = in.createByteArray();
- byte[] key = in.createByteArray();
- int mode = in.readInt();
- int[] numBytesOfClearData = in.createIntArray();
- int[] numBytesOfEncryptedData = in.createIntArray();
- int numSubSamples = in.readInt();
-
- cryptoInfo = new CryptoInfo();
- cryptoInfo.set(numSubSamples,
- numBytesOfClearData,
- numBytesOfEncryptedData,
- key,
- iv,
- mode);
- }
-
- public Sample set(ByteBuffer bytes, BufferInfo info, CryptoInfo cryptoInfo) throws IOException {
- if (bytes != null && info.size > 0) {
- buffer.readFromByteBuffer(bytes, info.offset, info.size);
- }
- this.info.set(0, info.size, info.presentationTimeUs, info.flags);
- this.cryptoInfo = cryptoInfo;
-
- return this;
- }
-
- public void dispose() {
- if (isEOS()) {
- return;
- }
-
- if (buffer != null) {
- buffer.dispose();
- buffer = null;
- }
- info = null;
- cryptoInfo = null;
- }
-
- public boolean isEOS() {
- return (this == EOS) ||
- ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0);
- }
-
- public static final Creator<Sample> CREATOR = new Creator<Sample>() {
- @Override
- public Sample createFromParcel(Parcel in) {
- return new Sample(in);
- }
-
- @Override
- public Sample[] newArray(int size) {
- return new Sample[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int parcelableFlags) {
- writeInfo(dest);
- writeCrypto(dest);
- dest.writeParcelable(buffer, parcelableFlags);
- }
-
- private void writeInfo(Parcel dest) {
- dest.writeInt(info.offset);
- dest.writeInt(info.size);
- dest.writeLong(info.presentationTimeUs);
- dest.writeInt(info.flags);
- }
-
- private void writeCrypto(Parcel dest) {
- if (cryptoInfo != null) {
- dest.writeInt(1);
- dest.writeByteArray(cryptoInfo.iv);
- dest.writeByteArray(cryptoInfo.key);
- dest.writeInt(cryptoInfo.mode);
- dest.writeIntArray(cryptoInfo.numBytesOfClearData);
- dest.writeIntArray(cryptoInfo.numBytesOfEncryptedData);
- dest.writeInt(cryptoInfo.numSubSamples);
- } else {
- dest.writeInt(0);
- }
- }
-
- public static byte[] byteArrayFromBuffer(ByteBuffer buffer, int offset, int size) {
- if (buffer == null || buffer.capacity() == 0 || size == 0) {
- return null;
- }
- if (buffer.hasArray() && offset == 0 && buffer.array().length == size) {
- return buffer.array();
- }
- int length = Math.min(offset + size, buffer.capacity()) - offset;
- byte[] bytes = new byte[length];
- buffer.position(offset);
- buffer.get(bytes);
- return bytes;
- }
-
- @WrapForJNI
- public void writeToByteBuffer(ByteBuffer dest) throws IOException {
- if (buffer != null && dest != null && info.size > 0) {
- buffer.writeToByteBuffer(dest, info.offset, info.size);
- }
- }
-
- @Override
- public String toString() {
- if (isEOS()) {
- return "EOS sample";
- }
-
- StringBuilder str = new StringBuilder();
- str.append("{ buffer=").append(buffer).
- append(", info=").
- append("{ offset=").append(info.offset).
- append(", size=").append(info.size).
- append(", pts=").append(info.presentationTimeUs).
- append(", flags=").append(Integer.toHexString(info.flags)).append(" }").
- append(" }");
- return str.toString();
- }
-} \ No newline at end of file
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/SamplePool.java b/mobile/android/base/java/org/mozilla/gecko/media/SamplePool.java
deleted file mode 100644
index 9041e3756..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/SamplePool.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-import android.media.MediaCodec;
-
-import org.mozilla.gecko.mozglue.SharedMemory;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-final class SamplePool {
- private final class Impl {
- private final String mName;
- private int mNextId = 0;
- private int mDefaultBufferSize = 4096;
- private final List<Sample> mRecycledSamples = new ArrayList<>();
-
- private Impl(String name) {
- mName = name;
- }
-
- private void setDefaultBufferSize(int size) {
- mDefaultBufferSize = size;
- }
-
- private synchronized Sample allocate(int size) {
- Sample sample;
- if (!mRecycledSamples.isEmpty()) {
- sample = mRecycledSamples.remove(0);
- sample.info.set(0, 0, 0, 0);
- } else {
- SharedMemory shm = null;
- try {
- shm = new SharedMemory(mNextId++, Math.max(size, mDefaultBufferSize));
- } catch (NoSuchMethodException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- if (shm != null) {
- sample = Sample.create(shm);
- } else {
- sample = Sample.create();
- }
- }
-
- return sample;
- }
-
- private synchronized void recycle(Sample recycled) {
- if (recycled.buffer.capacity() >= mDefaultBufferSize) {
- mRecycledSamples.add(recycled);
- } else {
- recycled.dispose();
- }
- }
-
- private synchronized void clear() {
- for (Sample s : mRecycledSamples) {
- s.dispose();
- }
-
- mRecycledSamples.clear();
- }
-
- @Override
- protected void finalize() {
- clear();
- }
- }
-
- private final Impl mInputs;
- private final Impl mOutputs;
-
- /* package */ SamplePool(String name) {
- mInputs = new Impl(name + " input buffer pool");
- mOutputs = new Impl(name + " output buffer pool");
- }
-
- /* package */ void setInputBufferSize(int size) {
- mInputs.setDefaultBufferSize(size);
- }
-
- /* package */ void setOutputBufferSize(int size) {
- mOutputs.setDefaultBufferSize(size);
- }
-
- /* package */ Sample obtainInput(int size) {
- return mInputs.allocate(size);
- }
-
- /* package */ Sample obtainOutput(MediaCodec.BufferInfo info) {
- Sample output = mOutputs.allocate(info.size);
- output.info.set(0, info.size, info.presentationTimeUs, info.flags);
- return output;
- }
-
- /* package */ void recycleInput(Sample sample) {
- sample.cryptoInfo = null;
- mInputs.recycle(sample);
- }
-
- /* package */ void recycleOutput(Sample sample) {
- mOutputs.recycle(sample);
- }
-
- /* package */ void reset() {
- mInputs.clear();
- mOutputs.clear();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/SessionKeyInfo.java b/mobile/android/base/java/org/mozilla/gecko/media/SessionKeyInfo.java
deleted file mode 100644
index b41ef3625..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/SessionKeyInfo.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import org.mozilla.gecko.annotation.WrapForJNI;
-
-public final class SessionKeyInfo implements Parcelable {
- @WrapForJNI
- public byte[] keyId;
-
- @WrapForJNI
- public int status;
-
- @WrapForJNI
- public SessionKeyInfo(byte[] keyId, int status) {
- this.keyId = keyId;
- this.status = status;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int parcelableFlags) {
- dest.writeByteArray(keyId);
- dest.writeInt(status);
- }
-
- public static final Creator<SessionKeyInfo> CREATOR = new Creator<SessionKeyInfo>() {
- @Override
- public SessionKeyInfo createFromParcel(Parcel in) {
- return new SessionKeyInfo(in);
- }
-
- @Override
- public SessionKeyInfo[] newArray(int size) {
- return new SessionKeyInfo[size];
- }
- };
-
- private SessionKeyInfo(Parcel src) {
- keyId = src.createByteArray();
- status = src.readInt();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/media/VideoPlayer.java b/mobile/android/base/java/org/mozilla/gecko/media/VideoPlayer.java
deleted file mode 100644
index 508b9d015..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/media/VideoPlayer.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.media;
-
-import android.content.Context;
-
-import android.graphics.Color;
-
-import android.net.Uri;
-
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-
-import android.widget.ImageButton;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.MediaController;
-import android.widget.VideoView;
-
-import org.mozilla.gecko.R;
-
-public class VideoPlayer extends FrameLayout {
- private VideoView video;
- private FullScreenMediaController controller;
- private FullScreenListener fullScreenListener;
-
- private boolean isFullScreen;
-
- public VideoPlayer(Context ctx) {
- this(ctx, null);
- }
-
- public VideoPlayer(Context ctx, AttributeSet attrs) {
- this(ctx, attrs, 0);
- }
-
- public VideoPlayer(Context ctx, AttributeSet attrs, int defStyle) {
- super(ctx, attrs, defStyle);
- setFullScreen(false);
- setVisibility(View.GONE);
- }
-
- public void start(Uri uri) {
- stop();
-
- video = new VideoView(getContext());
- controller = new FullScreenMediaController(getContext());
- video.setMediaController(controller);
- controller.setAnchorView(video);
-
- video.setVideoURI(uri);
-
- FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.WRAP_CONTENT,
- Gravity.CENTER);
-
- addView(video, layoutParams);
- setVisibility(View.VISIBLE);
-
- video.setZOrderOnTop(true);
- video.start();
- }
-
- public boolean isPlaying() {
- return video != null;
- }
-
- public void stop() {
- if (video == null) {
- return;
- }
-
- removeAllViews();
- setVisibility(View.GONE);
- video.stopPlayback();
-
- video = null;
- controller = null;
- }
-
- public void setFullScreenListener(FullScreenListener listener) {
- fullScreenListener = listener;
- }
-
- public boolean isFullScreen() {
- return isFullScreen;
- }
-
- public void setFullScreen(boolean fullScreen) {
- isFullScreen = fullScreen;
- if (fullScreen) {
- setBackgroundColor(Color.BLACK);
- } else {
- setBackgroundResource(R.color.dark_transparent_overlay);
- }
-
- if (controller != null) {
- controller.setFullScreen(fullScreen);
- }
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (event.isSystem()) {
- return super.onKeyDown(keyCode, event);
- }
- return true;
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (event.isSystem()) {
- return super.onKeyUp(keyCode, event);
- }
- return true;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- super.onTouchEvent(event);
- return true;
- }
-
- @Override
- public boolean onTrackballEvent(MotionEvent event) {
- super.onTrackballEvent(event);
- return true;
- }
-
- public interface FullScreenListener {
- void onFullScreenChanged(boolean fullScreen);
- }
-
- private class FullScreenMediaController extends MediaController {
- private ImageButton mButton;
-
- public FullScreenMediaController(Context ctx) {
- super(ctx);
-
- mButton = new ImageButton(getContext());
- mButton.setScaleType(ImageView.ScaleType.FIT_CENTER);
- mButton.setBackgroundColor(Color.TRANSPARENT);
- mButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- FullScreenMediaController.this.onFullScreenClicked();
- }
- });
-
- updateFullScreenButton(false);
- }
-
- public void setFullScreen(boolean fullScreen) {
- updateFullScreenButton(fullScreen);
- }
-
- private void updateFullScreenButton(boolean fullScreen) {
- mButton.setImageResource(fullScreen ? R.drawable.exit_fullscreen : R.drawable.fullscreen);
- }
-
- private void onFullScreenClicked() {
- if (VideoPlayer.this.fullScreenListener != null) {
- boolean fullScreen = !VideoPlayer.this.isFullScreen();
- VideoPlayer.this.fullScreenListener.onFullScreenChanged(fullScreen);
- }
- }
-
- @Override
- public void setAnchorView(final View view) {
- super.setAnchorView(view);
-
- // Add the fullscreen button here because this is where the parent class actually creates
- // the media buttons and their layout.
- //
- // http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/widget/MediaController.java#239
- //
- // The media buttons are in a horizontal linear layout which is itself packed into
- // a vertical layout. The vertical layout is the only child of the FrameLayout which
- // MediaController inherits from.
- LinearLayout child = (LinearLayout) getChildAt(0);
- LinearLayout buttons = (LinearLayout) child.getChildAt(0);
-
- LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.FILL_PARENT);
- params.gravity = Gravity.CENTER_VERTICAL;
-
- if (mButton.getParent() != null) {
- ((ViewGroup)mButton.getParent()).removeView(mButton);
- }
-
- buttons.addView(mButton, params);
- }
- }
-} \ No newline at end of file
diff --git a/mobile/android/base/java/org/mozilla/gecko/menu/GeckoMenu.java b/mobile/android/base/java/org/mozilla/gecko/menu/GeckoMenu.java
deleted file mode 100644
index 512f32002..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/menu/GeckoMenu.java
+++ /dev/null
@@ -1,928 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.menu;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.ThreadUtils.AssertBehavior;
-import org.mozilla.gecko.widget.GeckoActionProvider;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.SubMenu;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class GeckoMenu extends ListView
- implements Menu,
- AdapterView.OnItemClickListener,
- GeckoMenuItem.OnShowAsActionChangedListener {
- private static final String LOGTAG = "GeckoMenu";
-
- /**
- * Controls whether off-UI-thread method calls in this class cause an
- * exception or just logging.
- */
- private static final AssertBehavior THREAD_ASSERT_BEHAVIOR = AppConstants.RELEASE_OR_BETA ? AssertBehavior.NONE : AssertBehavior.THROW;
-
- /*
- * A callback for a menu item click/long click event.
- */
- public static interface Callback {
- // Called when a menu item is clicked, with the actual menu item as the argument.
- public boolean onMenuItemClick(MenuItem item);
-
- // Called when a menu item is long-clicked, with the actual menu item as the argument.
- public boolean onMenuItemLongClick(MenuItem item);
- }
-
- /*
- * An interface for a presenter to show the menu.
- * Either an Activity or a View can be a presenter, that can watch for events
- * and show/hide menu.
- */
- public static interface MenuPresenter {
- // Open the menu.
- public void openMenu();
-
- // Show the actual view containing the menu items. This can either be a parent or sub-menu.
- public void showMenu(View menu);
-
- // Close the menu.
- public void closeMenu();
- }
-
- /*
- * An interface for a presenter of action-items.
- * Either an Activity or a View can be a presenter, that can watch for events
- * and add/remove action-items. If not ActionItemBarPresenter, the menu uses a
- * DefaultActionItemBar, that shows the action-items as a header over list-view.
- */
- public static interface ActionItemBarPresenter {
- // Add an action-item.
- public boolean addActionItem(View actionItem);
-
- // Remove an action-item.
- public void removeActionItem(View actionItem);
- }
-
- protected static final int NO_ID = 0;
-
- // List of all menu items.
- private final List<GeckoMenuItem> mItems;
-
- // Quick lookup array used to make a fast path in findItem.
- private final SparseArray<MenuItem> mItemsById;
-
- // Map of "always" action-items in action-bar and their views.
- private final Map<GeckoMenuItem, View> mPrimaryActionItems;
-
- // Map of "ifRoom" action-items in action-bar and their views.
- private final Map<GeckoMenuItem, View> mSecondaryActionItems;
-
- // Map of "collapseActionView" action-items in action-bar and their views.
- private final Map<GeckoMenuItem, View> mQuickShareActionItems;
-
- // Reference to a callback for menu events.
- private Callback mCallback;
-
- // Reference to menu presenter.
- private MenuPresenter mMenuPresenter;
-
- // Reference to "always" action-items bar in action-bar.
- private ActionItemBarPresenter mPrimaryActionItemBar;
-
- // Reference to "ifRoom" action-items bar in action-bar.
- private final ActionItemBarPresenter mSecondaryActionItemBar;
-
- // Reference to "collapseActionView" action-items bar in action-bar.
- private final ActionItemBarPresenter mQuickShareActionItemBar;
-
- // Adapter to hold the list of menu items.
- private final MenuItemsAdapter mAdapter;
-
- // Show/hide icons in the list.
- boolean mShowIcons;
-
- public GeckoMenu(Context context) {
- this(context, null);
- }
-
- public GeckoMenu(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.geckoMenuListViewStyle);
- }
-
- public GeckoMenu(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT));
-
- // Attach an adapter.
- mAdapter = new MenuItemsAdapter();
- setAdapter(mAdapter);
- setOnItemClickListener(this);
-
- mItems = new ArrayList<GeckoMenuItem>();
- mItemsById = new SparseArray<MenuItem>();
- mPrimaryActionItems = new HashMap<GeckoMenuItem, View>();
- mSecondaryActionItems = new HashMap<GeckoMenuItem, View>();
- mQuickShareActionItems = new HashMap<GeckoMenuItem, View>();
-
- mPrimaryActionItemBar = (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_action_bar, null);
- mSecondaryActionItemBar = (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_secondary_action_bar, null);
- mQuickShareActionItemBar = (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_secondary_action_bar, null);
- }
-
- private static void assertOnUiThread() {
- ThreadUtils.assertOnUiThread(THREAD_ASSERT_BEHAVIOR);
- }
-
- @Override
- public MenuItem add(CharSequence title) {
- GeckoMenuItem menuItem = new GeckoMenuItem(this, NO_ID, 0, title);
- addItem(menuItem);
- return menuItem;
- }
-
- @Override
- public MenuItem add(int groupId, int itemId, int order, int titleRes) {
- GeckoMenuItem menuItem = new GeckoMenuItem(this, itemId, order, titleRes);
- addItem(menuItem);
- return menuItem;
- }
-
- @Override
- public MenuItem add(int titleRes) {
- GeckoMenuItem menuItem = new GeckoMenuItem(this, NO_ID, 0, titleRes);
- addItem(menuItem);
- return menuItem;
- }
-
- @Override
- public MenuItem add(int groupId, int itemId, int order, CharSequence title) {
- GeckoMenuItem menuItem = new GeckoMenuItem(this, itemId, order, title);
- addItem(menuItem);
- return menuItem;
- }
-
- private void addItem(GeckoMenuItem menuItem) {
- assertOnUiThread();
- menuItem.setOnShowAsActionChangedListener(this);
- mAdapter.addMenuItem(menuItem);
- mItems.add(menuItem);
- }
-
- private boolean addActionItem(final GeckoMenuItem menuItem) {
- assertOnUiThread();
- menuItem.setOnShowAsActionChangedListener(this);
-
- final View actionView = menuItem.getActionView();
- final int actionEnum = menuItem.getActionEnum();
- boolean added = false;
-
- if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_ALWAYS) {
- if (mPrimaryActionItems.size() == 0 &&
- mPrimaryActionItemBar instanceof DefaultActionItemBar) {
- // Reset the adapter before adding the header view to a list.
- setAdapter(null);
- addHeaderView((DefaultActionItemBar) mPrimaryActionItemBar);
- setAdapter(mAdapter);
- }
-
- if (added = mPrimaryActionItemBar.addActionItem(actionView)) {
- mPrimaryActionItems.put(menuItem, actionView);
- mItems.add(menuItem);
- }
- } else if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_IF_ROOM) {
- if (mSecondaryActionItems.size() == 0) {
- // Reset the adapter before adding the header view to a list.
- setAdapter(null);
- addHeaderView((DefaultActionItemBar) mSecondaryActionItemBar);
- setAdapter(mAdapter);
- }
-
- if (added = mSecondaryActionItemBar.addActionItem(actionView)) {
- mSecondaryActionItems.put(menuItem, actionView);
- mItems.add(menuItem);
- }
- } else if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) {
- if (actionView instanceof MenuItemSwitcherLayout) {
- final MenuItemSwitcherLayout quickShareView = (MenuItemSwitcherLayout) actionView;
-
- // We don't want to add the quick share bar if we don't have any quick share items.
- if (quickShareView.getActionButtonCount() > 0 &&
- (added = mQuickShareActionItemBar.addActionItem(quickShareView))) {
- if (mQuickShareActionItems.size() == 0) {
- // Reset the adapter before adding the header view to a list.
- setAdapter(null);
- addHeaderView((DefaultActionItemBar) mQuickShareActionItemBar);
- setAdapter(mAdapter);
- }
-
- mQuickShareActionItems.put(menuItem, quickShareView);
- mItems.add(menuItem);
- }
- }
- }
-
- // Set the listeners.
- if (actionView instanceof MenuItemActionBar) {
- ((MenuItemActionBar) actionView).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- handleMenuItemClick(menuItem);
- }
- });
- ((MenuItemActionBar) actionView).setOnLongClickListener(new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(View view) {
- if (handleMenuItemLongClick(menuItem)) {
- GeckoAppShell.vibrateOnHapticFeedbackEnabled(getResources().getIntArray(R.array.long_press_vibrate_msec));
- return true;
- }
- return false;
- }
- });
- } else if (actionView instanceof MenuItemSwitcherLayout) {
- ((MenuItemSwitcherLayout) actionView).setMenuItemClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- handleMenuItemClick(menuItem);
- }
- });
- ((MenuItemSwitcherLayout) actionView).setMenuItemLongClickListener(new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(View view) {
- if (handleMenuItemLongClick(menuItem)) {
- GeckoAppShell.vibrateOnHapticFeedbackEnabled(getResources().getIntArray(R.array.long_press_vibrate_msec));
- return true;
- }
- return false;
- }
- });
- }
-
- return added;
- }
-
- @Override
- public int addIntentOptions(int groupId, int itemId, int order, ComponentName caller, Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) {
- return 0;
- }
-
- @Override
- public SubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title) {
- MenuItem menuItem = add(groupId, itemId, order, title);
- return addSubMenu(menuItem);
- }
-
- @Override
- public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) {
- MenuItem menuItem = add(groupId, itemId, order, titleRes);
- return addSubMenu(menuItem);
- }
-
- @Override
- public SubMenu addSubMenu(CharSequence title) {
- MenuItem menuItem = add(title);
- return addSubMenu(menuItem);
- }
-
- @Override
- public SubMenu addSubMenu(int titleRes) {
- MenuItem menuItem = add(titleRes);
- return addSubMenu(menuItem);
- }
-
- private SubMenu addSubMenu(MenuItem menuItem) {
- GeckoSubMenu subMenu = new GeckoSubMenu(getContext());
- subMenu.setMenuItem(menuItem);
- subMenu.setCallback(mCallback);
- subMenu.setMenuPresenter(mMenuPresenter);
- ((GeckoMenuItem) menuItem).setSubMenu(subMenu);
- return subMenu;
- }
-
- private void removePrimaryActionBarView() {
- // Reset the adapter before removing the header view from a list.
- setAdapter(null);
- removeHeaderView((DefaultActionItemBar) mPrimaryActionItemBar);
- setAdapter(mAdapter);
- }
-
- private void removeSecondaryActionBarView() {
- // Reset the adapter before removing the header view from a list.
- setAdapter(null);
- removeHeaderView((DefaultActionItemBar) mSecondaryActionItemBar);
- setAdapter(mAdapter);
- }
-
- private void removeQuickShareActionBarView() {
- // Reset the adapter before removing the header view from a list.
- setAdapter(null);
- removeHeaderView((DefaultActionItemBar) mQuickShareActionItemBar);
- setAdapter(mAdapter);
- }
-
- @Override
- public void clear() {
- assertOnUiThread();
- for (GeckoMenuItem menuItem : mItems) {
- if (menuItem.hasSubMenu()) {
- SubMenu sub = menuItem.getSubMenu();
- if (sub == null) {
- continue;
- }
- try {
- sub.clear();
- } catch (Exception ex) {
- Log.e(LOGTAG, "Couldn't clear submenu.", ex);
- }
- }
- }
-
- mAdapter.clear();
- mItems.clear();
-
- /*
- * Reinflating the menu will re-add any action items to the toolbar, so
- * remove the old ones. This also ensures that any text associated with
- * these is switched to the correct locale.
- */
- if (mPrimaryActionItemBar != null) {
- for (View item : mPrimaryActionItems.values()) {
- mPrimaryActionItemBar.removeActionItem(item);
- }
- }
- mPrimaryActionItems.clear();
-
- if (mSecondaryActionItemBar != null) {
- for (View item : mSecondaryActionItems.values()) {
- mSecondaryActionItemBar.removeActionItem(item);
- }
- }
- mSecondaryActionItems.clear();
-
- if (mQuickShareActionItemBar != null) {
- for (View item : mQuickShareActionItems.values()) {
- mQuickShareActionItemBar.removeActionItem(item);
- }
- }
- mQuickShareActionItems.clear();
-
- // Remove the view, too -- the first addActionItem will re-add it,
- // and this is simpler than changing that logic.
- if (mPrimaryActionItemBar instanceof DefaultActionItemBar) {
- removePrimaryActionBarView();
- }
-
- removeSecondaryActionBarView();
- removeQuickShareActionBarView();
- }
-
- @Override
- public void close() {
- if (mMenuPresenter != null)
- mMenuPresenter.closeMenu();
- }
-
- private void showMenu(View viewForMenu) {
- if (mMenuPresenter != null)
- mMenuPresenter.showMenu(viewForMenu);
- }
-
- @Override
- public MenuItem findItem(int id) {
- assertOnUiThread();
- MenuItem quickItem = mItemsById.get(id);
- if (quickItem != null) {
- return quickItem;
- }
-
- for (GeckoMenuItem menuItem : mItems) {
- if (menuItem.getItemId() == id) {
- mItemsById.put(id, menuItem);
- return menuItem;
- } else if (menuItem.hasSubMenu()) {
- if (!menuItem.hasActionProvider()) {
- SubMenu subMenu = menuItem.getSubMenu();
- MenuItem item = subMenu.findItem(id);
- if (item != null) {
- mItemsById.put(id, item);
- return item;
- }
- }
- }
- }
- return null;
- }
-
- @Override
- public MenuItem getItem(int index) {
- if (index < mItems.size())
- return mItems.get(index);
-
- return null;
- }
-
- @Override
- public boolean hasVisibleItems() {
- assertOnUiThread();
- for (GeckoMenuItem menuItem : mItems) {
- if (menuItem.isVisible() &&
- !mPrimaryActionItems.containsKey(menuItem) &&
- !mSecondaryActionItems.containsKey(menuItem) &&
- !mQuickShareActionItems.containsKey(menuItem))
- return true;
- }
-
- return false;
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- // Close the menu if it is open and the hardware menu key is pressed.
- if (keyCode == KeyEvent.KEYCODE_MENU && isShown()) {
- close();
- return true;
- }
-
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- public boolean isShortcutKey(int keyCode, KeyEvent event) {
- return true;
- }
-
- @Override
- public boolean performIdentifierAction(int id, int flags) {
- return false;
- }
-
- @Override
- public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
- return false;
- }
-
- @Override
- public void removeGroup(int groupId) {
- }
-
- @Override
- public void removeItem(int id) {
- assertOnUiThread();
- GeckoMenuItem item = (GeckoMenuItem) findItem(id);
- if (item == null)
- return;
-
- // Remove it from the cache.
- mItemsById.remove(id);
-
- // Remove it from any sub-menu.
- for (GeckoMenuItem menuItem : mItems) {
- if (menuItem.hasSubMenu()) {
- SubMenu subMenu = menuItem.getSubMenu();
- if (subMenu != null && subMenu.findItem(id) != null) {
- subMenu.removeItem(id);
- return;
- }
- }
- }
-
- // Remove it from own menu.
- if (mPrimaryActionItems.containsKey(item)) {
- if (mPrimaryActionItemBar != null)
- mPrimaryActionItemBar.removeActionItem(mPrimaryActionItems.get(item));
-
- mPrimaryActionItems.remove(item);
- mItems.remove(item);
-
- if (mPrimaryActionItems.size() == 0 &&
- mPrimaryActionItemBar instanceof DefaultActionItemBar) {
- removePrimaryActionBarView();
- }
-
- return;
- }
-
- if (mSecondaryActionItems.containsKey(item)) {
- if (mSecondaryActionItemBar != null)
- mSecondaryActionItemBar.removeActionItem(mSecondaryActionItems.get(item));
-
- mSecondaryActionItems.remove(item);
- mItems.remove(item);
-
- if (mSecondaryActionItems.size() == 0) {
- removeSecondaryActionBarView();
- }
-
- return;
- }
-
- if (mQuickShareActionItems.containsKey(item)) {
- if (mQuickShareActionItemBar != null)
- mQuickShareActionItemBar.removeActionItem(mQuickShareActionItems.get(item));
-
- mQuickShareActionItems.remove(item);
- mItems.remove(item);
-
- if (mQuickShareActionItems.size() == 0) {
- removeQuickShareActionBarView();
- }
-
- return;
- }
-
- mAdapter.removeMenuItem(item);
- mItems.remove(item);
- }
-
- @Override
- public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
- }
-
- @Override
- public void setGroupEnabled(int group, boolean enabled) {
- }
-
- @Override
- public void setGroupVisible(int group, boolean visible) {
- }
-
- @Override
- public void setQwertyMode(boolean isQwerty) {
- }
-
- @Override
- public int size() {
- return mItems.size();
- }
-
- @Override
- public boolean hasActionItemBar() {
- return (mPrimaryActionItemBar != null) &&
- (mSecondaryActionItemBar != null) &&
- (mQuickShareActionItemBar != null);
- }
-
- @Override
- public void onShowAsActionChanged(GeckoMenuItem item) {
- removeItem(item.getItemId());
-
- if (item.isActionItem() && addActionItem(item)) {
- return;
- }
-
- addItem(item);
- }
-
- public void onItemChanged(GeckoMenuItem item) {
- assertOnUiThread();
- if (item.isActionItem()) {
- final View actionView;
- final int actionEnum = item.getActionEnum();
- if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_ALWAYS) {
- actionView = mPrimaryActionItems.get(item);
- } else if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_IF_ROOM) {
- actionView = mSecondaryActionItems.get(item);
- } else {
- actionView = mQuickShareActionItems.get(item);
- }
-
- if (actionView != null) {
- if (item.isVisible()) {
- actionView.setVisibility(View.VISIBLE);
- if (actionView instanceof MenuItemActionBar) {
- ((MenuItemActionBar) actionView).initialize(item);
- } else {
- ((MenuItemSwitcherLayout) actionView).initialize(item);
- }
- } else {
- actionView.setVisibility(View.GONE);
- }
- }
- } else {
- mAdapter.notifyDataSetChanged();
- }
- }
-
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- // We might be showing headers. Account them while using the position.
- position -= getHeaderViewsCount();
-
- GeckoMenuItem item = mAdapter.getItem(position);
- handleMenuItemClick(item);
- }
-
- void handleMenuItemClick(GeckoMenuItem item) {
- if (!item.isEnabled())
- return;
-
- if (item.invoke()) {
- close();
- } else if (item.hasSubMenu()) {
- // Refresh the submenu for the provider.
- GeckoActionProvider provider = item.getGeckoActionProvider();
- if (provider != null) {
- GeckoSubMenu subMenu = new GeckoSubMenu(getContext());
- subMenu.setShowIcons(true);
- provider.onPrepareSubMenu(subMenu);
- item.setSubMenu(subMenu);
- }
-
- // Show the submenu.
- GeckoSubMenu subMenu = (GeckoSubMenu) item.getSubMenu();
- showMenu(subMenu);
- } else {
- close();
- mCallback.onMenuItemClick(item);
- }
- }
-
- boolean handleMenuItemLongClick(GeckoMenuItem item) {
- if (!item.isEnabled()) {
- return false;
- }
-
- if (mCallback != null) {
- if (mCallback.onMenuItemLongClick(item)) {
- close();
- return true;
- }
- }
- return false;
- }
-
- public Callback getCallback() {
- return mCallback;
- }
-
- public MenuPresenter getMenuPresenter() {
- return mMenuPresenter;
- }
-
- public void setCallback(Callback callback) {
- mCallback = callback;
-
- // Update the submenus just in case this changes on the fly.
- for (GeckoMenuItem menuItem : mItems) {
- if (menuItem.hasSubMenu()) {
- GeckoSubMenu subMenu = (GeckoSubMenu) menuItem.getSubMenu();
- subMenu.setCallback(mCallback);
- }
- }
- }
-
- public void setMenuPresenter(MenuPresenter presenter) {
- mMenuPresenter = presenter;
-
- // Update the submenus just in case this changes on the fly.
- for (GeckoMenuItem menuItem : mItems) {
- if (menuItem.hasSubMenu()) {
- GeckoSubMenu subMenu = (GeckoSubMenu) menuItem.getSubMenu();
- subMenu.setMenuPresenter(mMenuPresenter);
- }
- }
- }
-
- public void setActionItemBarPresenter(ActionItemBarPresenter presenter) {
- mPrimaryActionItemBar = presenter;
- }
-
- public void setShowIcons(boolean show) {
- if (mShowIcons != show) {
- mShowIcons = show;
- mAdapter.notifyDataSetChanged();
- }
- }
-
- // Action Items are added to the header view by default.
- // URL bar can register itself as a presenter, in case it has a different place to show them.
- public static class DefaultActionItemBar extends LinearLayout
- implements ActionItemBarPresenter {
- private final int mRowHeight;
- private float mWeightSum;
-
- public DefaultActionItemBar(Context context) {
- this(context, null);
- }
-
- public DefaultActionItemBar(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- mRowHeight = getResources().getDimensionPixelSize(R.dimen.menu_item_row_height);
- }
-
- @Override
- public boolean addActionItem(View actionItem) {
- ViewGroup.LayoutParams actualParams = actionItem.getLayoutParams();
- LinearLayout.LayoutParams params;
-
- if (actualParams != null) {
- params = new LinearLayout.LayoutParams(actionItem.getLayoutParams());
- params.width = 0;
- } else {
- params = new LinearLayout.LayoutParams(0, mRowHeight);
- }
-
- if (actionItem instanceof MenuItemSwitcherLayout) {
- params.weight = ((MenuItemSwitcherLayout) actionItem).getChildCount();
- } else {
- params.weight = 1.0f;
- }
-
- mWeightSum += params.weight;
-
- actionItem.setLayoutParams(params);
- addView(actionItem);
- setWeightSum(mWeightSum);
- return true;
- }
-
- @Override
- public void removeActionItem(View actionItem) {
- if (indexOfChild(actionItem) != -1) {
- LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) actionItem.getLayoutParams();
- mWeightSum -= params.weight;
- removeView(actionItem);
- }
- }
- }
-
- // Adapter to bind menu items to the list.
- private class MenuItemsAdapter extends BaseAdapter {
- private static final int VIEW_TYPE_DEFAULT = 0;
- private static final int VIEW_TYPE_ACTION_MODE = 1;
-
- private final List<GeckoMenuItem> mItems;
-
- public MenuItemsAdapter() {
- mItems = new ArrayList<GeckoMenuItem>();
- }
-
- @Override
- public int getCount() {
- if (mItems == null)
- return 0;
-
- int visibleCount = 0;
- for (GeckoMenuItem item : mItems) {
- if (item.isVisible())
- visibleCount++;
- }
-
- return visibleCount;
- }
-
- @Override
- public GeckoMenuItem getItem(int position) {
- for (GeckoMenuItem item : mItems) {
- if (item.isVisible()) {
- position--;
-
- if (position < 0)
- return item;
- }
- }
-
- return null;
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- GeckoMenuItem item = getItem(position);
- GeckoMenuItem.Layout view = null;
-
- // Try to re-use the view.
- if (convertView == null && getItemViewType(position) == VIEW_TYPE_DEFAULT) {
- view = new MenuItemDefault(parent.getContext(), null);
- } else {
- view = (GeckoMenuItem.Layout) convertView;
- }
-
- if (view == null || view instanceof MenuItemSwitcherLayout) {
- // Always get from the menu item.
- // This will ensure that the default activity is refreshed.
- view = (MenuItemSwitcherLayout) item.getActionView();
-
- // ListView will not perform an item click if the row has a focusable view in it.
- // Hence, forward the click event on the menu item in the action-view to the ListView.
- final View actionView = (View) view;
- final int pos = position;
- final long id = getItemId(position);
- ((MenuItemSwitcherLayout) view).setMenuItemClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- GeckoMenu listView = GeckoMenu.this;
- listView.performItemClick(actionView, pos + listView.getHeaderViewsCount(), id);
- }
- });
- }
-
- // Initialize the view.
- view.setShowIcon(mShowIcons);
- view.initialize(item);
- return (View) view;
- }
-
- @Override
- public int getItemViewType(int position) {
- return getItem(position).getGeckoActionProvider() == null ? VIEW_TYPE_DEFAULT : VIEW_TYPE_ACTION_MODE;
- }
-
- @Override
- public int getViewTypeCount() {
- return 2;
- }
-
- @Override
- public boolean hasStableIds() {
- return false;
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- // Setting this to true is a workaround to fix disappearing
- // dividers in the menu (bug 963249).
- return true;
- }
-
- @Override
- public boolean isEnabled(int position) {
- // Setting this to true is a workaround to fix disappearing
- // dividers in the menu in L (bug 1050780).
- return true;
- }
-
- public void addMenuItem(GeckoMenuItem menuItem) {
- if (mItems.contains(menuItem))
- return;
-
- // Insert it in proper order.
- int index = 0;
- for (GeckoMenuItem item : mItems) {
- if (item.getOrder() > menuItem.getOrder()) {
- mItems.add(index, menuItem);
- notifyDataSetChanged();
- return;
- } else {
- index++;
- }
- }
-
- // Add the menuItem at the end.
- mItems.add(menuItem);
- notifyDataSetChanged();
- }
-
- public void removeMenuItem(GeckoMenuItem menuItem) {
- // Remove it from the list.
- mItems.remove(menuItem);
- notifyDataSetChanged();
- }
-
- public void clear() {
- mItemsById.clear();
- mItems.clear();
- notifyDataSetChanged();
- }
-
- public GeckoMenuItem getMenuItem(int id) {
- for (GeckoMenuItem item : mItems) {
- if (item.getItemId() == id)
- return item;
- }
-
- return null;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/menu/GeckoMenuInflater.java b/mobile/android/base/java/org/mozilla/gecko/menu/GeckoMenuInflater.java
deleted file mode 100644
index dfcb31c5f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/menu/GeckoMenuInflater.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.menu;
-
-import java.io.IOException;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.R;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.util.AttributeSet;
-import android.util.Xml;
-import android.view.InflateException;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.SubMenu;
-
-public class GeckoMenuInflater extends MenuInflater {
- private static final String TAG_MENU = "menu";
- private static final String TAG_ITEM = "item";
- private static final int NO_ID = 0;
-
- private final Context mContext;
-
- // Private class to hold the parsed menu item.
- private class ParsedItem {
- public int id;
- public int order;
- public CharSequence title;
- public int iconRes;
- public boolean checkable;
- public boolean checked;
- public boolean visible;
- public boolean enabled;
- public int showAsAction;
- public boolean hasSubMenu;
- }
-
- public GeckoMenuInflater(Context context) {
- super(context);
- mContext = context;
- }
-
- @Override
- public void inflate(int menuRes, Menu menu) {
-
- // This does not check for a well-formed XML.
-
- XmlResourceParser parser = null;
- try {
- parser = mContext.getResources().getXml(menuRes);
- AttributeSet attrs = Xml.asAttributeSet(parser);
-
- parseMenu(parser, attrs, menu);
-
- } catch (XmlPullParserException | IOException e) {
- throw new InflateException("Error inflating menu XML", e);
- } finally {
- if (parser != null)
- parser.close();
- }
- }
-
- private void parseMenu(XmlResourceParser parser, AttributeSet attrs, Menu menu)
- throws XmlPullParserException, IOException {
- ParsedItem item = null;
-
- String tag;
- int eventType = parser.getEventType();
-
- do {
- tag = parser.getName();
-
- switch (eventType) {
- case XmlPullParser.START_TAG:
- if (tag.equals(TAG_ITEM)) {
- // Parse the menu item.
- item = new ParsedItem();
- parseItem(item, attrs);
- } else if (tag.equals(TAG_MENU)) {
- if (item != null) {
- // Add the submenu item.
- SubMenu subMenu = menu.addSubMenu(NO_ID, item.id, item.order, item.title);
- item.hasSubMenu = true;
-
- // Set the menu item in main menu.
- MenuItem menuItem = subMenu.getItem();
- setValues(item, menuItem);
-
- // Start parsing the sub menu.
- parseMenu(parser, attrs, subMenu);
- }
- }
- break;
-
- case XmlPullParser.END_TAG:
- if (parser.getName().equals(TAG_ITEM)) {
- if (!item.hasSubMenu) {
- // Add the item.
- MenuItem menuItem = menu.add(NO_ID, item.id, item.order, item.title);
- setValues(item, menuItem);
- }
- } else if (tag.equals(TAG_MENU)) {
- return;
- }
- break;
- }
-
- eventType = parser.next();
-
- } while (eventType != XmlPullParser.END_DOCUMENT);
- }
-
- public void parseItem(ParsedItem item, AttributeSet attrs) {
- TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.MenuItem);
-
- item.id = a.getResourceId(R.styleable.MenuItem_android_id, NO_ID);
- item.order = a.getInt(R.styleable.MenuItem_android_orderInCategory, 0);
- item.title = a.getText(R.styleable.MenuItem_android_title);
- item.checkable = a.getBoolean(R.styleable.MenuItem_android_checkable, false);
- item.checked = a.getBoolean(R.styleable.MenuItem_android_checked, false);
- item.visible = a.getBoolean(R.styleable.MenuItem_android_visible, true);
- item.enabled = a.getBoolean(R.styleable.MenuItem_android_enabled, true);
- item.hasSubMenu = false;
- item.iconRes = a.getResourceId(R.styleable.MenuItem_android_icon, 0);
- item.showAsAction = a.getInt(R.styleable.MenuItem_android_showAsAction, 0);
-
- a.recycle();
- }
-
- public void setValues(ParsedItem item, MenuItem menuItem) {
- // We are blocking any presenter updates during inflation.
- GeckoMenuItem geckoItem = null;
- if (menuItem instanceof GeckoMenuItem) {
- geckoItem = (GeckoMenuItem) menuItem;
- }
-
- if (geckoItem != null) {
- geckoItem.stopDispatchingChanges();
- }
-
- menuItem.setChecked(item.checked)
- .setVisible(item.visible)
- .setEnabled(item.enabled)
- .setCheckable(item.checkable)
- .setIcon(item.iconRes);
-
- menuItem.setShowAsAction(item.showAsAction);
-
- if (geckoItem != null) {
- // We don't need to allow presenter updates during inflation,
- // so we use the weak form of re-enabling changes.
- geckoItem.resumeDispatchingChanges();
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/menu/GeckoMenuItem.java b/mobile/android/base/java/org/mozilla/gecko/menu/GeckoMenuItem.java
deleted file mode 100644
index 21df4208d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/menu/GeckoMenuItem.java
+++ /dev/null
@@ -1,472 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.menu;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.ResourceDrawableUtils;
-import org.mozilla.gecko.widget.GeckoActionProvider;
-
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
-import android.view.ActionProvider;
-import android.view.ContextMenu;
-import android.view.MenuItem;
-import android.view.SubMenu;
-import android.view.View;
-
-public class GeckoMenuItem implements MenuItem {
- private static final int SHARE_BAR_HISTORY_SIZE = 2;
-
- // These values mirror MenuItem values that are only available on API >= 11.
- public static final int SHOW_AS_ACTION_NEVER = 0;
- public static final int SHOW_AS_ACTION_IF_ROOM = 1;
- public static final int SHOW_AS_ACTION_ALWAYS = 2;
- public static final int SHOW_AS_ACTION_WITH_TEXT = 4;
- public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8;
-
- // A View that can show a MenuItem should be able to initialize from
- // the properties of the MenuItem.
- public static interface Layout {
- public void initialize(GeckoMenuItem item);
- public void setShowIcon(boolean show);
- }
-
- public static interface OnShowAsActionChangedListener {
- public boolean hasActionItemBar();
- public void onShowAsActionChanged(GeckoMenuItem item);
- }
-
- private final int mId;
- private final int mOrder;
- private View mActionView;
- private int mActionEnum;
- private CharSequence mTitle;
- private CharSequence mTitleCondensed;
- private boolean mCheckable;
- private boolean mChecked;
- private boolean mVisible = true;
- private boolean mEnabled = true;
- private Drawable mIcon;
- private int mIconRes;
- private GeckoActionProvider mActionProvider;
- private GeckoSubMenu mSubMenu;
- private MenuItem.OnMenuItemClickListener mMenuItemClickListener;
- final GeckoMenu mMenu;
- OnShowAsActionChangedListener mShowAsActionChangedListener;
-
- private volatile boolean mShouldDispatchChanges = true;
- private volatile boolean mDidChange;
-
- public GeckoMenuItem(GeckoMenu menu, int id, int order, int titleRes) {
- mMenu = menu;
- mId = id;
- mOrder = order;
- mTitle = mMenu.getResources().getString(titleRes);
- }
-
- public GeckoMenuItem(GeckoMenu menu, int id, int order, CharSequence title) {
- mMenu = menu;
- mId = id;
- mOrder = order;
- mTitle = title;
- }
-
- /**
- * Stop dispatching item changed events to presenters until
- * [start|resume]DispatchingItemsChanged() is called. Useful when
- * many menu operations are going to be performed as a batch.
- */
- public void stopDispatchingChanges() {
- mDidChange = false;
- mShouldDispatchChanges = false;
- }
-
- /**
- * Resume dispatching item changed events to presenters. This method
- * will NOT call onItemChanged if any menu operations were queued.
- * Only future menu operations will call onItemChanged. Useful for
- * sequelching presenter updates.
- */
- public void resumeDispatchingChanges() {
- mShouldDispatchChanges = true;
- }
-
- /**
- * Start dispatching item changed events to presenters. This method
- * will call onItemChanged if any menu operations were queued.
- */
- public void startDispatchingChanges() {
- if (mDidChange) {
- mMenu.onItemChanged(this);
- }
- mShouldDispatchChanges = true;
- }
-
- @Override
- public boolean collapseActionView() {
- return false;
- }
-
- @Override
- public boolean expandActionView() {
- return false;
- }
-
- public boolean hasActionProvider() {
- return (mActionProvider != null);
- }
-
- public int getActionEnum() {
- return mActionEnum;
- }
-
- public GeckoActionProvider getGeckoActionProvider() {
- return mActionProvider;
- }
-
- @Override
- public ActionProvider getActionProvider() {
- return null;
- }
-
- @Override
- public View getActionView() {
- if (mActionProvider != null) {
- return mActionProvider.onCreateActionView(SHARE_BAR_HISTORY_SIZE,
- GeckoActionProvider.ActionViewType.DEFAULT);
- }
-
- return mActionView;
- }
-
- @Override
- public char getAlphabeticShortcut() {
- return 0;
- }
-
- @Override
- public int getGroupId() {
- return 0;
- }
-
- @Override
- public Drawable getIcon() {
- if (mIcon == null) {
- if (mIconRes != 0)
- return ResourceDrawableUtils.getDrawable(mMenu.getContext(), mIconRes);
- else
- return null;
- } else {
- return mIcon;
- }
- }
-
- @Override
- public Intent getIntent() {
- return null;
- }
-
- @Override
- public int getItemId() {
- return mId;
- }
-
- @Override
- public ContextMenu.ContextMenuInfo getMenuInfo() {
- return null;
- }
-
- @Override
- public char getNumericShortcut() {
- return 0;
- }
-
- @Override
- public int getOrder() {
- return mOrder;
- }
-
- @Override
- public SubMenu getSubMenu() {
- return mSubMenu;
- }
-
- @Override
- public CharSequence getTitle() {
- return mTitle;
- }
-
- @Override
- public CharSequence getTitleCondensed() {
- return mTitleCondensed;
- }
-
- @Override
- public boolean hasSubMenu() {
- if (mActionProvider != null)
- return mActionProvider.hasSubMenu();
-
- return (mSubMenu != null);
- }
-
- public boolean isActionItem() {
- return (mActionEnum > 0);
- }
-
- @Override
- public boolean isActionViewExpanded() {
- return false;
- }
-
- @Override
- public boolean isCheckable() {
- return mCheckable;
- }
-
- @Override
- public boolean isChecked() {
- return mChecked;
- }
-
- @Override
- public boolean isEnabled() {
- return mEnabled;
- }
-
- @Override
- public boolean isVisible() {
- return mVisible;
- }
-
- @Override
- public MenuItem setActionProvider(ActionProvider actionProvider) {
- return this;
- }
-
- public MenuItem setActionProvider(GeckoActionProvider actionProvider) {
- mActionProvider = actionProvider;
- if (mActionProvider != null) {
- actionProvider.setOnTargetSelectedListener(new GeckoActionProvider.OnTargetSelectedListener() {
- @Override
- public void onTargetSelected() {
- mMenu.close();
-
- // Refresh the menu item to show the high frequency apps.
- mShowAsActionChangedListener.onShowAsActionChanged(GeckoMenuItem.this);
- }
- });
- }
-
- mShowAsActionChangedListener.onShowAsActionChanged(this);
- return this;
- }
-
- @Override
- public MenuItem setActionView(int resId) {
- return this;
- }
-
- @Override
- public MenuItem setActionView(View view) {
- return this;
- }
-
- @Override
- public MenuItem setAlphabeticShortcut(char alphaChar) {
- return this;
- }
-
- @Override
- public MenuItem setCheckable(boolean checkable) {
- if (mCheckable != checkable) {
- mCheckable = checkable;
- if (mShouldDispatchChanges) {
- mMenu.onItemChanged(this);
- } else {
- mDidChange = true;
- }
- }
- return this;
- }
-
- @Override
- public MenuItem setChecked(boolean checked) {
- if (mChecked != checked) {
- mChecked = checked;
- if (mShouldDispatchChanges) {
- mMenu.onItemChanged(this);
- } else {
- mDidChange = true;
- }
- }
- return this;
- }
-
- @Override
- public MenuItem setEnabled(boolean enabled) {
- if (mEnabled != enabled) {
- mEnabled = enabled;
- if (mShouldDispatchChanges) {
- mMenu.onItemChanged(this);
- } else {
- mDidChange = true;
- }
- }
- return this;
- }
-
- @Override
- public MenuItem setIcon(Drawable icon) {
- if (mIcon != icon) {
- mIcon = icon;
- if (mShouldDispatchChanges) {
- mMenu.onItemChanged(this);
- } else {
- mDidChange = true;
- }
- }
- return this;
- }
-
- @Override
- public MenuItem setIcon(int iconRes) {
- if (mIconRes != iconRes) {
- mIconRes = iconRes;
- if (mShouldDispatchChanges) {
- mMenu.onItemChanged(this);
- } else {
- mDidChange = true;
- }
- }
- return this;
- }
-
- @Override
- public MenuItem setIntent(Intent intent) {
- return this;
- }
-
- @Override
- public MenuItem setNumericShortcut(char numericChar) {
- return this;
- }
-
- @Override
- public MenuItem setOnActionExpandListener(MenuItem.OnActionExpandListener listener) {
- return this;
- }
-
- @Override
- public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener menuItemClickListener) {
- mMenuItemClickListener = menuItemClickListener;
- return this;
- }
-
- @Override
- public MenuItem setShortcut(char numericChar, char alphaChar) {
- return this;
- }
-
- @Override
- public void setShowAsAction(int actionEnum) {
- setShowAsAction(actionEnum, 0);
- }
-
- public void setShowAsAction(int actionEnum, int style) {
- if (mShowAsActionChangedListener == null)
- return;
-
- if (mActionEnum == actionEnum)
- return;
-
- if (actionEnum > 0) {
- if (!mShowAsActionChangedListener.hasActionItemBar())
- return;
-
- if (!hasActionProvider()) {
- // Change the type to just an icon
- MenuItemActionBar actionView;
- if (style != 0) {
- actionView = new MenuItemActionBar(mMenu.getContext(), null, style);
- } else {
- if (actionEnum == SHOW_AS_ACTION_ALWAYS) {
- actionView = new MenuItemActionBar(mMenu.getContext());
- } else {
- actionView = new MenuItemActionBar(mMenu.getContext(), null, R.attr.menuItemSecondaryActionBarStyle);
- }
- }
-
- actionView.initialize(this);
- mActionView = actionView;
- }
-
- mActionEnum = actionEnum;
- }
-
- mShowAsActionChangedListener.onShowAsActionChanged(this);
- }
-
- @Override
- public MenuItem setShowAsActionFlags(int actionEnum) {
- return this;
- }
-
- public MenuItem setSubMenu(GeckoSubMenu subMenu) {
- mSubMenu = subMenu;
- return this;
- }
-
- @Override
- public MenuItem setTitle(CharSequence title) {
- if (!TextUtils.equals(mTitle, title)) {
- mTitle = title;
- if (mShouldDispatchChanges) {
- mMenu.onItemChanged(this);
- } else {
- mDidChange = true;
- }
- }
- return this;
- }
-
- @Override
- public MenuItem setTitle(int title) {
- CharSequence newTitle = mMenu.getResources().getString(title);
- return setTitle(newTitle);
- }
-
- @Override
- public MenuItem setTitleCondensed(CharSequence title) {
- mTitleCondensed = title;
- return this;
- }
-
- @Override
- public MenuItem setVisible(boolean visible) {
- // Action views are not normal menu items and visibility can get out
- // of sync unless we dispatch whenever required.
- if (isActionItem() || mVisible != visible) {
- mVisible = visible;
- if (mShouldDispatchChanges) {
- mMenu.onItemChanged(this);
- } else {
- mDidChange = true;
- }
- }
- return this;
- }
-
- public boolean invoke() {
- if (mMenuItemClickListener != null)
- return mMenuItemClickListener.onMenuItemClick(this);
- else
- return false;
- }
-
- public void setOnShowAsActionChangedListener(OnShowAsActionChangedListener listener) {
- mShowAsActionChangedListener = listener;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/menu/GeckoSubMenu.java b/mobile/android/base/java/org/mozilla/gecko/menu/GeckoSubMenu.java
deleted file mode 100644
index d774bdd37..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/menu/GeckoSubMenu.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.menu;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.MenuItem;
-import android.view.SubMenu;
-import android.view.View;
-
-public class GeckoSubMenu extends GeckoMenu
- implements SubMenu {
- private static final String LOGTAG = "GeckoSubMenu";
-
- // MenuItem associated with this submenu.
- private MenuItem mMenuItem;
-
- public GeckoSubMenu(Context context) {
- super(context);
- }
-
- public GeckoSubMenu(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public GeckoSubMenu(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- public void clearHeader() {
- }
-
- public SubMenu setMenuItem(MenuItem item) {
- mMenuItem = item;
- return this;
- }
-
- @Override
- public MenuItem getItem() {
- return mMenuItem;
- }
-
- @Override
- public SubMenu setHeaderIcon(Drawable icon) {
- return this;
- }
-
- @Override
- public SubMenu setHeaderIcon(int iconRes) {
- return this;
- }
-
- @Override
- public SubMenu setHeaderTitle(CharSequence title) {
- return this;
- }
-
- @Override
- public SubMenu setHeaderTitle(int titleRes) {
- return this;
- }
-
- @Override
- public SubMenu setHeaderView(View view) {
- return this;
- }
-
- @Override
- public SubMenu setIcon(Drawable icon) {
- return this;
- }
-
- @Override
- public SubMenu setIcon(int iconRes) {
- return this;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/menu/MenuItemActionBar.java b/mobile/android/base/java/org/mozilla/gecko/menu/MenuItemActionBar.java
deleted file mode 100644
index 882187dd6..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/menu/MenuItemActionBar.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.menu;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.ResourceDrawableUtils;
-import org.mozilla.gecko.widget.themed.ThemedImageButton;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-
-public class MenuItemActionBar extends ThemedImageButton
- implements GeckoMenuItem.Layout {
- private static final String LOGTAG = "GeckoMenuItemActionBar";
-
- public MenuItemActionBar(Context context) {
- this(context, null);
- }
-
- public MenuItemActionBar(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.menuItemActionBarStyle);
- }
-
- public MenuItemActionBar(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- public void initialize(GeckoMenuItem item) {
- if (item == null)
- return;
-
- setIcon(item.getIcon());
- setTitle(item.getTitle());
- setEnabled(item.isEnabled());
- setId(item.getItemId());
- }
-
- void setIcon(Drawable icon) {
- if (icon == null) {
- setVisibility(GONE);
- } else {
- setVisibility(VISIBLE);
- setImageDrawable(icon);
- }
- }
-
- void setIcon(int icon) {
- setIcon((icon == 0) ? null : ResourceDrawableUtils.getDrawable(getContext(), icon));
- }
-
- void setTitle(CharSequence title) {
- // set accessibility contentDescription here
- setContentDescription(title);
- }
-
- @Override
- public void setShowIcon(boolean show) {
- // Do nothing.
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/menu/MenuItemDefault.java b/mobile/android/base/java/org/mozilla/gecko/menu/MenuItemDefault.java
deleted file mode 100644
index 5b5069334..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/menu/MenuItemDefault.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.menu;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.ResourceDrawableUtils;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.widget.TextView;
-
-public class MenuItemDefault extends TextView
- implements GeckoMenuItem.Layout {
- private static final int[] STATE_MORE = new int[] { R.attr.state_more };
- private static final int[] STATE_CHECKED = new int[] { android.R.attr.state_checkable, android.R.attr.state_checked };
- private static final int[] STATE_UNCHECKED = new int[] { android.R.attr.state_checkable };
-
- private Drawable mIcon;
- private final Drawable mState;
- private static Rect sIconBounds;
-
- private boolean mCheckable;
- private boolean mChecked;
- private boolean mHasSubMenu;
- private boolean mShowIcon;
-
- public MenuItemDefault(Context context) {
- this(context, null);
- }
-
- public MenuItemDefault(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.menuItemDefaultStyle);
- }
-
- public MenuItemDefault(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- Resources res = getResources();
- int width = res.getDimensionPixelSize(R.dimen.menu_item_row_width);
- int height = res.getDimensionPixelSize(R.dimen.menu_item_row_height);
- setMinimumWidth(width);
- setMinimumHeight(height);
-
- int stateIconSize = res.getDimensionPixelSize(R.dimen.menu_item_state_icon);
- Rect stateIconBounds = new Rect(0, 0, stateIconSize, stateIconSize);
-
- mState = res.getDrawable(R.drawable.menu_item_state).mutate();
- mState.setBounds(stateIconBounds);
-
- if (sIconBounds == null) {
- int iconSize = res.getDimensionPixelSize(R.dimen.menu_item_icon);
- sIconBounds = new Rect(0, 0, iconSize, iconSize);
- }
-
- setCompoundDrawables(mIcon, null, mState, null);
- }
-
- @Override
- public int[] onCreateDrawableState(int extraSpace) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 2);
-
- if (mHasSubMenu)
- mergeDrawableStates(drawableState, STATE_MORE);
- else if (mCheckable && mChecked)
- mergeDrawableStates(drawableState, STATE_CHECKED);
- else if (mCheckable && !mChecked)
- mergeDrawableStates(drawableState, STATE_UNCHECKED);
-
- return drawableState;
- }
-
- @Override
- public void initialize(GeckoMenuItem item) {
- if (item == null)
- return;
-
- setTitle(item.getTitle());
- setIcon(item.getIcon());
- setEnabled(item.isEnabled());
- setCheckable(item.isCheckable());
- setChecked(item.isChecked());
- setSubMenuIndicator(item.hasSubMenu());
- }
-
- private void refreshIcon() {
- setCompoundDrawables(mShowIcon ? mIcon : null, null, mState, null);
- }
-
- void setIcon(Drawable icon) {
- mIcon = icon;
-
- if (mIcon != null) {
- mIcon.setBounds(sIconBounds);
- mIcon.setAlpha(isEnabled() ? 255 : 99);
- }
-
- refreshIcon();
- }
-
- void setIcon(int icon) {
- setIcon((icon == 0) ? null : ResourceDrawableUtils.getDrawable(getContext(), icon));
- }
-
- void setTitle(CharSequence title) {
- setText(title);
- }
-
- @Override
- public void setEnabled(boolean enabled) {
- super.setEnabled(enabled);
-
- if (mIcon != null)
- mIcon.setAlpha(enabled ? 255 : 99);
-
- if (mState != null)
- mState.setAlpha(enabled ? 255 : 99);
- }
-
- private void setCheckable(boolean checkable) {
- if (mCheckable != checkable) {
- mCheckable = checkable;
- refreshDrawableState();
- }
- }
-
- private void setChecked(boolean checked) {
- if (mChecked != checked) {
- mChecked = checked;
- refreshDrawableState();
- }
- }
-
- @Override
- public void setShowIcon(boolean show) {
- if (mShowIcon != show) {
- mShowIcon = show;
- refreshIcon();
- }
- }
-
- void setSubMenuIndicator(boolean hasSubMenu) {
- if (mHasSubMenu != hasSubMenu) {
- mHasSubMenu = hasSubMenu;
- refreshDrawableState();
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/menu/MenuItemSwitcherLayout.java b/mobile/android/base/java/org/mozilla/gecko/menu/MenuItemSwitcherLayout.java
deleted file mode 100644
index d01f52687..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/menu/MenuItemSwitcherLayout.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.menu;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.mozilla.gecko.R;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-
-/**
- * This class is a container view for menu items that:
- * * Shows text if there is enough space and there are
- * no action buttons ({@link #mActionButtons}).
- * * Shows an icon if there is not enough space for text,
- * or there are action buttons.
- */
-public class MenuItemSwitcherLayout extends LinearLayout
- implements GeckoMenuItem.Layout,
- View.OnClickListener {
- private final MenuItemDefault mMenuItem;
- private final MenuItemActionBar mMenuButton;
- private final List<ImageButton> mActionButtons;
- private final List<View.OnClickListener> mActionButtonListeners = new ArrayList<View.OnClickListener>();
-
- public MenuItemSwitcherLayout(Context context) {
- this(context, null);
- }
-
- public MenuItemSwitcherLayout(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.menuItemSwitcherLayoutStyle);
- }
-
- @TargetApi(14)
- public MenuItemSwitcherLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs);
-
- LayoutInflater.from(context).inflate(R.layout.menu_item_switcher_layout, this);
- mMenuItem = (MenuItemDefault) findViewById(R.id.menu_item);
- mMenuButton = (MenuItemActionBar) findViewById(R.id.menu_item_button);
- mActionButtons = new ArrayList<ImageButton>();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- final int width = right - left;
-
- final View parent = (View) getParent();
- final int parentPadding = parent.getPaddingLeft() + parent.getPaddingRight();
- final int horizontalSpaceAvailableInParent = parent.getMeasuredWidth() - parentPadding;
-
- // Check if there is another View sharing horizontal
- // space with this View in the parent.
- if (width < horizontalSpaceAvailableInParent || mActionButtons.size() != 0) {
- // Use the icon.
- mMenuItem.setVisibility(View.GONE);
- mMenuButton.setVisibility(View.VISIBLE);
- } else {
- // Use the button.
- mMenuItem.setVisibility(View.VISIBLE);
- mMenuButton.setVisibility(View.GONE);
- }
-
- super.onLayout(changed, left, top, right, bottom);
- }
-
- @Override
- public void initialize(GeckoMenuItem item) {
- if (item == null) {
- return;
- }
-
- mMenuItem.initialize(item);
- mMenuButton.initialize(item);
- setEnabled(item.isEnabled());
- }
-
- @Override
- public void setEnabled(boolean enabled) {
- super.setEnabled(enabled);
- mMenuItem.setEnabled(enabled);
- mMenuButton.setEnabled(enabled);
-
- for (ImageButton button : mActionButtons) {
- button.setEnabled(enabled);
- button.setAlpha(enabled ? 255 : 99);
- }
- }
-
- public void setMenuItemClickListener(View.OnClickListener listener) {
- mMenuItem.setOnClickListener(listener);
- mMenuButton.setOnClickListener(listener);
- }
-
- public void setMenuItemLongClickListener(View.OnLongClickListener listener) {
- mMenuItem.setOnLongClickListener(listener);
- mMenuButton.setOnLongClickListener(listener);
- }
-
- public void addActionButtonClickListener(View.OnClickListener listener) {
- mActionButtonListeners.add(listener);
- }
-
- @Override
- public void setShowIcon(boolean show) {
- mMenuItem.setShowIcon(show);
- }
-
- public void setIcon(Drawable icon) {
- mMenuItem.setIcon(icon);
- mMenuButton.setIcon(icon);
- }
-
- public void setIcon(int icon) {
- mMenuItem.setIcon(icon);
- mMenuButton.setIcon(icon);
- }
-
- public void setTitle(CharSequence title) {
- mMenuItem.setTitle(title);
- mMenuButton.setContentDescription(title);
- }
-
- public void setSubMenuIndicator(boolean hasSubMenu) {
- mMenuItem.setSubMenuIndicator(hasSubMenu);
- }
-
- public void addActionButton(Drawable drawable, CharSequence label) {
- // If this is the first icon, retain the text.
- // If not, make the menu item an icon.
- final int count = mActionButtons.size();
- mMenuItem.setVisibility(View.GONE);
- mMenuButton.setVisibility(View.VISIBLE);
-
- if (drawable != null) {
- ImageButton button = new ImageButton(getContext(), null, R.attr.menuItemSecondaryActionBarStyle);
- button.setImageDrawable(drawable);
- button.setContentDescription(label);
- button.setOnClickListener(this);
- button.setTag(count);
-
- final int height = (int) (getResources().getDimension(R.dimen.menu_item_row_height));
- LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, height);
- params.weight = 1.0f;
- button.setLayoutParams(params);
-
- // Place action buttons to the right of the actual menu item
- mActionButtons.add(button);
- addView(button, count + 1);
- }
- }
-
- protected int getActionButtonCount() {
- return mActionButtons.size();
- }
-
- @Override
- public void onClick(View view) {
- for (View.OnClickListener listener : mActionButtonListeners) {
- listener.onClick(view);
- }
- }
-
- /**
- * Update the styles if this view is being used in the context menus.
- *
- * Ideally, we just use different layout files and styles to set this, but
- * MenuItemSwitcherLayout is too integrated into GeckoActionProvider to provide
- * an easy separation so instead I provide this hack. I'm sorry.
- */
- public void initContextMenuStyles() {
- final int defaultContextMenuPadding = getContext().getResources().getDimensionPixelOffset(
- R.dimen.context_menu_item_horizontal_padding);
- mMenuItem.setPadding(defaultContextMenuPadding, getPaddingTop(),
- defaultContextMenuPadding, getPaddingBottom());
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/menu/MenuPanel.java b/mobile/android/base/java/org/mozilla/gecko/menu/MenuPanel.java
deleted file mode 100644
index ce4da8b7f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/menu/MenuPanel.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.menu;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.LinearLayout;
-
-/**
- * The outer container for the custom menu. On phones with h/w menu button,
- * this is given to Android which inflates it to the right panel. On phones
- * with s/w menu button, this is added to a MenuPopup.
- */
-public class MenuPanel extends LinearLayout {
- public MenuPanel(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- int width = (int) context.getResources().getDimension(R.dimen.menu_item_row_width);
- setLayoutParams(new ViewGroup.LayoutParams(width, ViewGroup.LayoutParams.WRAP_CONTENT));
- }
-
- @Override
- public boolean dispatchPopulateAccessibilityEvent (AccessibilityEvent event) {
- onPopulateAccessibilityEvent(event);
-
- return true;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/menu/MenuPopup.java b/mobile/android/base/java/org/mozilla/gecko/menu/MenuPopup.java
deleted file mode 100644
index 227cc7630..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/menu/MenuPopup.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.menu;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.support.v7.widget.CardView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.PopupWindow;
-
-/**
- * A popup to show the inflated MenuPanel.
- */
-public class MenuPopup extends PopupWindow {
- private final CardView mPanel;
-
- private final int mPopupWidth;
-
- public MenuPopup(Context context) {
- super(context);
-
- setFocusable(true);
-
- mPopupWidth = context.getResources().getDimensionPixelSize(R.dimen.menu_popup_width);
-
- // Setting a null background makes the popup to not close on touching outside.
- setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
- setWindowLayoutMode(ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
-
- LayoutInflater inflater = LayoutInflater.from(context);
- mPanel = (CardView) inflater.inflate(R.layout.menu_popup, null);
- setContentView(mPanel);
-
- setAnimationStyle(R.style.PopupAnimation);
- }
-
- /**
- * Adds the panel with the menu to its content.
- *
- * @param view The panel view with the menu to be shown.
- */
- public void setPanelView(View view) {
- view.setLayoutParams(new FrameLayout.LayoutParams(mPopupWidth,
- FrameLayout.LayoutParams.WRAP_CONTENT));
-
- mPanel.removeAllViews();
- mPanel.addView(view);
- }
-
- /**
- * A small little offset.
- */
- @Override
- public void showAsDropDown(View anchor) {
- // Set a height, so that the popup will not be displayed below the bottom of the screen.
- // We use the exact height of the internal content, which is the technique described in
- // http://stackoverflow.com/a/7698709
- setHeight(mPanel.getHeight());
-
- // Attempt to align the center of the popup with the center of the anchor. If the anchor is
- // near the edge of the screen, the popup will just align with the edge of the screen.
- final int xOffset = anchor.getWidth() / 2 - mPopupWidth / 2;
- showAsDropDown(anchor, xOffset, -anchor.getHeight());
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/mozglue/SharedMemBuffer.java b/mobile/android/base/java/org/mozilla/gecko/mozglue/SharedMemBuffer.java
deleted file mode 100644
index cf22685c2..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/mozglue/SharedMemBuffer.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.mozglue;
-
-import android.os.Parcel;
-
-import org.mozilla.gecko.media.Sample;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-public final class SharedMemBuffer implements Sample.Buffer {
- private SharedMemory mSharedMem;
-
- /* package */
- public SharedMemBuffer(SharedMemory sharedMem) {
- mSharedMem = sharedMem;
- }
-
- protected SharedMemBuffer(Parcel in) {
- mSharedMem = in.readParcelable(Sample.class.getClassLoader());
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelable(mSharedMem, flags);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- public static final Creator<SharedMemBuffer> CREATOR = new Creator<SharedMemBuffer>() {
- @Override
- public SharedMemBuffer createFromParcel(Parcel in) {
- return new SharedMemBuffer(in);
- }
-
- @Override
- public SharedMemBuffer[] newArray(int size) {
- return new SharedMemBuffer[size];
- }
- };
-
- @Override
- public int capacity() {
- return mSharedMem != null ? mSharedMem.getSize() : 0;
- }
-
- @Override
- public void readFromByteBuffer(ByteBuffer src, int offset, int size) throws IOException {
- if (!src.isDirect()) {
- throw new IOException("SharedMemBuffer only support reading from direct byte buffer.");
- }
- nativeReadFromDirectBuffer(src, mSharedMem.getPointer(), offset, size);
- }
-
- private native static void nativeReadFromDirectBuffer(ByteBuffer src, long dest, int offset, int size);
-
- @Override
- public void writeToByteBuffer(ByteBuffer dest, int offset, int size) throws IOException {
- if (!dest.isDirect()) {
- throw new IOException("SharedMemBuffer only support writing to direct byte buffer.");
- }
- nativeWriteToDirectBuffer(mSharedMem.getPointer(), dest, offset, size);
- }
-
- private native static void nativeWriteToDirectBuffer(long src, ByteBuffer dest, int offset, int size);
-
- @Override
- public void dispose() {
- if (mSharedMem != null) {
- mSharedMem.dispose();
- mSharedMem = null;
- }
- }
-
- @Override public String toString() { return "Buffer: " + mSharedMem; }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/mozglue/SharedMemory.java b/mobile/android/base/java/org/mozilla/gecko/mozglue/SharedMemory.java
deleted file mode 100644
index bc43a2755..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/mozglue/SharedMemory.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.mozglue;
-
-import android.os.MemoryFile;
-import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
-import android.os.Parcelable;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.lang.reflect.Method;
-
-public class SharedMemory implements Parcelable {
- private static final String LOGTAG = "GeckoShmem";
- private static Method sGetFDMethod = null; // MemoryFile.getFileDescriptor() is hidden. :(
- private ParcelFileDescriptor mDescriptor;
- private int mSize;
- private int mId;
- private long mHandle; // The native pointer.
- private boolean mIsMapped;
- private MemoryFile mBackedFile;
-
- static {
- try {
- sGetFDMethod = MemoryFile.class.getDeclaredMethod("getFileDescriptor");
- } catch (NoSuchMethodException e) {
- e.printStackTrace();
- }
- }
-
- private SharedMemory(Parcel in) {
- mDescriptor = in.readFileDescriptor();
- mSize = in.readInt();
- mId = in.readInt();
- }
-
- public static final Creator<SharedMemory> CREATOR = new Creator<SharedMemory>() {
- @Override
- public SharedMemory createFromParcel(Parcel in) {
- return new SharedMemory(in);
- }
-
- @Override
- public SharedMemory[] newArray(int size) {
- return new SharedMemory[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- // We don't want ParcelFileDescriptor.writeToParcel() to close the fd.
- dest.writeFileDescriptor(mDescriptor.getFileDescriptor());
- dest.writeInt(mSize);
- dest.writeInt(mId);
- }
-
- public SharedMemory(int id, int size) throws NoSuchMethodException, IOException {
- if (sGetFDMethod == null) {
- throw new NoSuchMethodException("MemoryFile.getFileDescriptor() doesn't exist.");
- }
- mBackedFile = new MemoryFile(null, size);
- try {
- FileDescriptor fd = (FileDescriptor)sGetFDMethod.invoke(mBackedFile);
- mDescriptor = ParcelFileDescriptor.dup(fd);
- mSize = size;
- mId = id;
- mBackedFile.allowPurging(false);
- } catch (Exception e) {
- e.printStackTrace();
- close();
- throw new IOException(e.getMessage());
- }
- }
-
- public void flush() {
- if (mBackedFile == null) {
- close();
- }
- }
-
- public void close() {
- if (mIsMapped) {
- unmap(mHandle, mSize);
- mHandle = 0;
- }
-
- if (mDescriptor != null) {
- try {
- mDescriptor.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- mDescriptor = null;
- }
- }
-
- // Should only be called by process that allocates shared memory.
- public void dispose() {
- if (!isValid()) {
- return;
- }
-
- close();
-
- if (mBackedFile != null) {
- mBackedFile.close();
- mBackedFile = null;
- }
- }
-
- private native void unmap(long address, int size);
-
- public boolean isValid() { return mDescriptor != null; }
-
- public int getSize() { return mSize; }
-
- private int getFD() {
- return isValid() ? mDescriptor.getFd() : -1;
- }
-
- public long getPointer() {
- if (!isValid()) {
- return 0;
- }
-
- if (!mIsMapped) {
- mHandle = map(getFD(), mSize);
- if (mHandle != 0) {
- mIsMapped = true;
- }
- }
- return mHandle;
- }
-
- private native long map(int fd, int size);
-
- @Override
- protected void finalize() throws Throwable {
- if (mBackedFile != null) {
- Log.w(LOGTAG, "dispose() not called before finalizing");
- }
- dispose();
-
- super.finalize();
- }
-
- @Override
- public String toString() {
- return "SHM(" + getSize() + " bytes): id=" + mId + ", backing=" + mBackedFile + ",fd=" + mDescriptor;
- }
-
- @Override
- public boolean equals(Object that) {
- return (this == that) ||
- ((that instanceof SharedMemory) && (hashCode() == that.hashCode()));
- }
-
- @Override
- public int hashCode() {
- return mId;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/notifications/NotificationClient.java b/mobile/android/base/java/org/mozilla/gecko/notifications/NotificationClient.java
deleted file mode 100644
index c46c01050..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/notifications/NotificationClient.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.notifications;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationManagerCompat;
-import android.util.Log;
-
-import java.util.HashMap;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoService;
-import org.mozilla.gecko.NotificationListener;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.gfx.BitmapUtils;
-
-/**
- * Client for posting notifications.
- */
-public final class NotificationClient implements NotificationListener {
- private static final String LOGTAG = "GeckoNotificationClient";
- /* package */ static final String CLICK_ACTION = AppConstants.ANDROID_PACKAGE_NAME + ".NOTIFICATION_CLICK";
- /* package */ static final String CLOSE_ACTION = AppConstants.ANDROID_PACKAGE_NAME + ".NOTIFICATION_CLOSE";
- /* package */ static final String PERSISTENT_INTENT_EXTRA = "persistentIntent";
-
- private final Context mContext;
- private final NotificationManagerCompat mNotificationManager;
-
- private final HashMap<String, Notification> mNotifications = new HashMap<>();
-
- /**
- * Notification associated with this service's foreground state.
- *
- * {@link android.app.Service#startForeground(int, android.app.Notification)}
- * associates the foreground with exactly one notification from the service.
- * To keep Fennec alive during downloads (and to make sure it can be killed
- * once downloads are complete), we make sure that the foreground is always
- * associated with an active progress notification if and only if at least
- * one download is in progress.
- */
- private String mForegroundNotification;
-
- public NotificationClient(Context context) {
- mContext = context.getApplicationContext();
- mNotificationManager = NotificationManagerCompat.from(mContext);
- }
-
- @Override // NotificationListener
- public void showNotification(String name, String cookie, String title,
- String text, String host, String imageUrl) {
- showNotification(name, cookie, title, text, host, imageUrl, /* data */ null);
- }
-
- @Override // NotificationListener
- public void showPersistentNotification(String name, String cookie, String title,
- String text, String host, String imageUrl,
- String data) {
- showNotification(name, cookie, title, text, host, imageUrl, data != null ? data : "");
- }
-
- private void showNotification(String name, String cookie, String title,
- String text, String host, String imageUrl,
- String persistentData) {
- // Put the strings into the intent as an URI
- // "alert:?name=<name>&cookie=<cookie>"
- String packageName = AppConstants.ANDROID_PACKAGE_NAME;
- String className = AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS;
- if (GeckoAppShell.getGeckoInterface() != null) {
- final ComponentName comp = GeckoAppShell.getGeckoInterface()
- .getActivity().getComponentName();
- packageName = comp.getPackageName();
- className = comp.getClassName();
- }
- final Uri dataUri = (new Uri.Builder())
- .scheme("moz-notification")
- .authority(packageName)
- .path(className)
- .appendQueryParameter("name", name)
- .appendQueryParameter("cookie", cookie)
- .build();
-
- final Intent clickIntent = new Intent(CLICK_ACTION);
- clickIntent.setClass(mContext, NotificationReceiver.class);
- clickIntent.setData(dataUri);
-
- if (persistentData != null) {
- final Intent persistentIntent = GeckoService.getIntentToCreateServices(
- mContext, "persistent-notification-click", persistentData);
- clickIntent.putExtra(PERSISTENT_INTENT_EXTRA, persistentIntent);
- }
-
- final PendingIntent clickPendingIntent = PendingIntent.getBroadcast(
- mContext, 0, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT);
-
- final Intent closeIntent = new Intent(CLOSE_ACTION);
- closeIntent.setClass(mContext, NotificationReceiver.class);
- closeIntent.setData(dataUri);
-
- if (persistentData != null) {
- final Intent persistentIntent = GeckoService.getIntentToCreateServices(
- mContext, "persistent-notification-close", persistentData);
- closeIntent.putExtra(PERSISTENT_INTENT_EXTRA, persistentIntent);
- }
-
- final PendingIntent closePendingIntent = PendingIntent.getBroadcast(
- mContext, 0, closeIntent, PendingIntent.FLAG_UPDATE_CURRENT);
-
- add(name, imageUrl, host, title, text, clickPendingIntent, closePendingIntent);
- GeckoAppShell.onNotificationShow(name, cookie);
- }
-
- @Override // NotificationListener
- public void closeNotification(String name)
- {
- remove(name);
- }
-
- /**
- * Adds a notification; used for web notifications.
- *
- * @param name the unique name of the notification
- * @param imageUrl URL of the image to use
- * @param alertTitle title of the notification
- * @param alertText text of the notification
- * @param contentIntent Intent used when the notification is clicked
- * @param deleteIntent Intent used when the notification is closed
- */
- private void add(final String name, final String imageUrl, final String host,
- final String alertTitle, final String alertText,
- final PendingIntent contentIntent, final PendingIntent deleteIntent) {
- final NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext)
- .setContentTitle(alertTitle)
- .setContentText(alertText)
- .setSmallIcon(R.drawable.ic_status_logo)
- .setContentIntent(contentIntent)
- .setDeleteIntent(deleteIntent)
- .setAutoCancel(true)
- .setStyle(new NotificationCompat.BigTextStyle()
- .bigText(alertText)
- .setSummaryText(host));
-
- // Fetch icon.
- if (!imageUrl.isEmpty()) {
- final Bitmap image = BitmapUtils.decodeUrl(imageUrl);
- builder.setLargeIcon(image);
- }
-
- builder.setWhen(System.currentTimeMillis());
- final Notification notification = builder.build();
-
- synchronized (this) {
- mNotifications.put(name, notification);
- }
-
- mNotificationManager.notify(name, 0, notification);
- }
-
- /**
- * Adds a notification; used for Fennec app notifications.
- *
- * @param name the unique name of the notification
- * @param notification the Notification to add
- */
- public synchronized void add(final String name, final Notification notification) {
- final boolean ongoing = isOngoing(notification);
-
- if (ongoing != isOngoing(mNotifications.get(name))) {
- // In order to change notification from ongoing to non-ongoing, or vice versa,
- // we have to remove the previous notification, because ongoing notifications
- // use a different id value than non-ongoing notifications.
- onNotificationClose(name);
- }
-
- mNotifications.put(name, notification);
-
- if (!ongoing) {
- mNotificationManager.notify(name, 0, notification);
- return;
- }
-
- // Ongoing
- if (mForegroundNotification == null) {
- setForegroundNotificationLocked(name, notification);
- } else if (mForegroundNotification.equals(name)) {
- // Shortcut to update the current foreground notification, instead of
- // going through the service.
- mNotificationManager.notify(R.id.foregroundNotification, notification);
- }
- }
-
- /**
- * Updates a notification.
- *
- * @param name Name of existing notification
- * @param progress progress of item being updated
- * @param progressMax max progress of item being updated
- * @param alertText text of the notification
- */
- public void update(final String name, final long progress,
- final long progressMax, final String alertText) {
- Notification notification;
- synchronized (this) {
- notification = mNotifications.get(name);
- }
- if (notification == null) {
- return;
- }
-
- notification = new NotificationCompat.Builder(mContext)
- .setContentText(alertText)
- .setSmallIcon(notification.icon)
- .setWhen(notification.when)
- .setContentIntent(notification.contentIntent)
- .setProgress((int) progressMax, (int) progress, false)
- .build();
-
- add(name, notification);
- }
-
- /* package */ synchronized Notification onNotificationClose(final String name) {
- mNotificationManager.cancel(name, 0);
-
- final Notification notification = mNotifications.remove(name);
- if (notification != null) {
- updateForegroundNotificationLocked(name);
- }
- return notification;
- }
-
- /**
- * Removes a notification.
- *
- * @param name Name of existing notification
- */
- public synchronized void remove(final String name) {
- final Notification notification = onNotificationClose(name);
- if (notification == null || notification.deleteIntent == null) {
- return;
- }
-
- // Canceling the notification doesn't trigger the delete intent, so we
- // have to trigger it manually.
- try {
- notification.deleteIntent.send();
- } catch (final PendingIntent.CanceledException e) {
- // Ignore.
- }
- }
-
- /**
- * Determines whether the service is done.
- *
- * The service is considered finished when all notifications have been
- * removed.
- *
- * @return whether all notifications have been removed
- */
- public synchronized boolean isDone() {
- return mNotifications.isEmpty();
- }
-
- /**
- * Determines whether a notification should hold a foreground service to keep Gecko alive
- *
- * @param name the name of the notification to check
- * @return whether the notification is ongoing
- */
- public synchronized boolean isOngoing(final String name) {
- return isOngoing(mNotifications.get(name));
- }
-
- /**
- * Determines whether a notification should hold a foreground service to keep Gecko alive
- *
- * @param notification the notification to check
- * @return whether the notification is ongoing
- */
- public boolean isOngoing(final Notification notification) {
- if (notification != null && (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0) {
- return true;
- }
- return false;
- }
-
- private void setForegroundNotificationLocked(final String name,
- final Notification notification) {
- mForegroundNotification = name;
-
- final Intent intent = new Intent(mContext, NotificationService.class);
- intent.putExtra(NotificationService.EXTRA_NOTIFICATION, notification);
- mContext.startService(intent);
- }
-
- private void updateForegroundNotificationLocked(final String oldName) {
- if (mForegroundNotification == null || !mForegroundNotification.equals(oldName)) {
- return;
- }
-
- // If we're removing the notification associated with the
- // foreground, we need to pick another active notification to act
- // as the foreground notification.
- for (final String name : mNotifications.keySet()) {
- final Notification notification = mNotifications.get(name);
- if (isOngoing(notification)) {
- setForegroundNotificationLocked(name, notification);
- return;
- }
- }
-
- setForegroundNotificationLocked(null, null);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/notifications/NotificationHelper.java b/mobile/android/base/java/org/mozilla/gecko/notifications/NotificationHelper.java
deleted file mode 100644
index 1e33031b5..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/notifications/NotificationHelper.java
+++ /dev/null
@@ -1,366 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.notifications;
-
-import java.util.HashMap;
-import java.util.Iterator;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.gfx.BitmapUtils;
-import org.mozilla.gecko.mozglue.SafeIntent;
-import org.mozilla.gecko.util.GeckoEventListener;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.support.v4.app.NotificationCompat;
-import android.util.Log;
-
-public final class NotificationHelper implements GeckoEventListener {
- public static final String HELPER_BROADCAST_ACTION = AppConstants.ANDROID_PACKAGE_NAME + ".helperBroadcastAction";
-
- public static final String NOTIFICATION_ID = "NotificationHelper_ID";
- private static final String LOGTAG = "GeckoNotificationHelper";
- private static final String HELPER_NOTIFICATION = "helperNotif";
-
- // Attributes mandatory to be used while sending a notification from js.
- private static final String TITLE_ATTR = "title";
- private static final String TEXT_ATTR = "text";
- /* package */ static final String ID_ATTR = "id";
- private static final String SMALLICON_ATTR = "smallIcon";
-
- // Attributes that can be used while sending a notification from js.
- private static final String PROGRESS_VALUE_ATTR = "progress_value";
- private static final String PROGRESS_MAX_ATTR = "progress_max";
- private static final String PROGRESS_INDETERMINATE_ATTR = "progress_indeterminate";
- private static final String LIGHT_ATTR = "light";
- private static final String ONGOING_ATTR = "ongoing";
- private static final String WHEN_ATTR = "when";
- private static final String PRIORITY_ATTR = "priority";
- private static final String LARGE_ICON_ATTR = "largeIcon";
- private static final String ACTIONS_ATTR = "actions";
- private static final String ACTION_ID_ATTR = "buttonId";
- private static final String ACTION_TITLE_ATTR = "title";
- private static final String ACTION_ICON_ATTR = "icon";
- private static final String PERSISTENT_ATTR = "persistent";
- private static final String HANDLER_ATTR = "handlerKey";
- private static final String COOKIE_ATTR = "cookie";
- static final String EVENT_TYPE_ATTR = "eventType";
-
- private static final String NOTIFICATION_SCHEME = "moz-notification";
-
- private static final String BUTTON_EVENT = "notification-button-clicked";
- private static final String CLICK_EVENT = "notification-clicked";
- static final String CLEARED_EVENT = "notification-cleared";
-
- static final String ORIGINAL_EXTRA_COMPONENT = "originalComponent";
-
- private final Context mContext;
-
- // Holds a list of notifications that should be cleared if the Fennec Activity is shut down.
- // Will not include ongoing or persistent notifications that are tied to Gecko's lifecycle.
- private HashMap<String, String> mClearableNotifications;
-
- private boolean mInitialized;
- private static NotificationHelper sInstance;
-
- private NotificationHelper(Context context) {
- mContext = context;
- }
-
- public void init() {
- if (mInitialized) {
- return;
- }
-
- mClearableNotifications = new HashMap<String, String>();
- EventDispatcher.getInstance().registerGeckoThreadListener(this,
- "Notification:Show",
- "Notification:Hide");
- mInitialized = true;
- }
-
- public static NotificationHelper getInstance(Context context) {
- // If someone else created this singleton, but didn't initialize it, something has gone wrong.
- if (sInstance != null && !sInstance.mInitialized) {
- throw new IllegalStateException("NotificationHelper was created by someone else but not initialized");
- }
-
- if (sInstance == null) {
- sInstance = new NotificationHelper(context.getApplicationContext());
- }
- return sInstance;
- }
-
- @Override
- public void handleMessage(String event, JSONObject message) {
- if (event.equals("Notification:Show")) {
- showNotification(message);
- } else if (event.equals("Notification:Hide")) {
- hideNotification(message);
- }
- }
-
- public boolean isHelperIntent(Intent i) {
- return i.getBooleanExtra(HELPER_NOTIFICATION, false);
- }
-
- public static void getArgsAndSendNotificationIntent(SafeIntent intent) {
- final JSONObject args = new JSONObject();
- final Uri data = intent.getData();
-
- final String notificationType = data.getQueryParameter(EVENT_TYPE_ATTR);
-
- try {
- args.put(ID_ATTR, data.getQueryParameter(ID_ATTR));
- args.put(EVENT_TYPE_ATTR, notificationType);
- args.put(HANDLER_ATTR, data.getQueryParameter(HANDLER_ATTR));
- args.put(COOKIE_ATTR, intent.getStringExtra(COOKIE_ATTR));
-
- if (BUTTON_EVENT.equals(notificationType)) {
- final String actionName = data.getQueryParameter(ACTION_ID_ATTR);
- args.put(ACTION_ID_ATTR, actionName);
- }
-
- Log.i(LOGTAG, "Send " + args.toString());
- GeckoAppShell.notifyObservers("Notification:Event", args.toString());
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error building JSON notification arguments.", e);
- }
- }
-
- public void handleNotificationIntent(SafeIntent i) {
- final Uri data = i.getData();
- final String notificationType = data.getQueryParameter(EVENT_TYPE_ATTR);
- final String id = data.getQueryParameter(ID_ATTR);
- if (id == null || notificationType == null) {
- Log.e(LOGTAG, "handleNotificationEvent: invalid intent parameters");
- return;
- }
-
- getArgsAndSendNotificationIntent(i);
-
- // If the notification was clicked, we are closing it. This must be executed after
- // sending the event to js side because when the notification is canceled no event can be
- // handled.
- if (CLICK_EVENT.equals(notificationType) && !i.getBooleanExtra(ONGOING_ATTR, false)) {
- // The handler and cookie parameters are optional.
- final String handler = data.getQueryParameter(HANDLER_ATTR);
- final String cookie = i.getStringExtra(COOKIE_ATTR);
- hideNotification(id, handler, cookie);
- }
- }
-
- private Uri.Builder getNotificationBuilder(JSONObject message, String type) {
- Uri.Builder b = new Uri.Builder();
- b.scheme(NOTIFICATION_SCHEME).appendQueryParameter(EVENT_TYPE_ATTR, type);
-
- try {
- final String id = message.getString(ID_ATTR);
- b.appendQueryParameter(ID_ATTR, id);
- } catch (JSONException ex) {
- Log.i(LOGTAG, "buildNotificationPendingIntent, error parsing", ex);
- }
-
- try {
- final String id = message.getString(HANDLER_ATTR);
- b.appendQueryParameter(HANDLER_ATTR, id);
- } catch (JSONException ex) {
- Log.i(LOGTAG, "Notification doesn't have a handler");
- }
-
- return b;
- }
-
- private Intent buildNotificationIntent(JSONObject message, Uri.Builder builder) {
- Intent notificationIntent = new Intent(HELPER_BROADCAST_ACTION);
- final boolean ongoing = message.optBoolean(ONGOING_ATTR);
- notificationIntent.putExtra(ONGOING_ATTR, ongoing);
-
- final Uri dataUri = builder.build();
- notificationIntent.setData(dataUri);
- notificationIntent.putExtra(HELPER_NOTIFICATION, true);
- notificationIntent.putExtra(COOKIE_ATTR, message.optString(COOKIE_ATTR));
-
- // All intents get routed through the notificationReceiver. That lets us bail if we don't want to start Gecko
- final ComponentName name = new ComponentName(mContext, GeckoAppShell.getGeckoInterface().getActivity().getClass());
- notificationIntent.putExtra(ORIGINAL_EXTRA_COMPONENT, name);
-
- return notificationIntent;
- }
-
- private PendingIntent buildNotificationPendingIntent(JSONObject message, String type) {
- Uri.Builder builder = getNotificationBuilder(message, type);
- final Intent notificationIntent = buildNotificationIntent(message, builder);
- return PendingIntent.getBroadcast(mContext, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- private PendingIntent buildButtonClickPendingIntent(JSONObject message, JSONObject action) {
- Uri.Builder builder = getNotificationBuilder(message, BUTTON_EVENT);
- try {
- // Action name must be in query uri, otherwise buttons pending intents
- // would be collapsed.
- if (action.has(ACTION_ID_ATTR)) {
- builder.appendQueryParameter(ACTION_ID_ATTR, action.getString(ACTION_ID_ATTR));
- } else {
- Log.i(LOGTAG, "button event with no name");
- }
- } catch (JSONException ex) {
- Log.i(LOGTAG, "buildNotificationPendingIntent, error parsing", ex);
- }
- final Intent notificationIntent = buildNotificationIntent(message, builder);
- PendingIntent res = PendingIntent.getBroadcast(mContext, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- return res;
- }
-
- private void showNotification(JSONObject message) {
- NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext);
-
- // These attributes are required
- final String id;
- try {
- builder.setContentTitle(message.getString(TITLE_ATTR));
- builder.setContentText(message.getString(TEXT_ATTR));
- id = message.getString(ID_ATTR);
- } catch (JSONException ex) {
- Log.i(LOGTAG, "Error parsing", ex);
- return;
- }
-
- Uri imageUri = Uri.parse(message.optString(SMALLICON_ATTR));
- builder.setSmallIcon(BitmapUtils.getResource(mContext, imageUri));
-
- JSONArray light = message.optJSONArray(LIGHT_ATTR);
- if (light != null && light.length() == 3) {
- try {
- builder.setLights(light.getInt(0),
- light.getInt(1),
- light.getInt(2));
- } catch (JSONException ex) {
- Log.i(LOGTAG, "Error parsing", ex);
- }
- }
-
- boolean ongoing = message.optBoolean(ONGOING_ATTR);
- builder.setOngoing(ongoing);
-
- if (message.has(WHEN_ATTR)) {
- long when = message.optLong(WHEN_ATTR);
- builder.setWhen(when);
- }
-
- if (message.has(PRIORITY_ATTR)) {
- int priority = message.optInt(PRIORITY_ATTR);
- builder.setPriority(priority);
- }
-
- if (message.has(LARGE_ICON_ATTR)) {
- Bitmap b = BitmapUtils.getBitmapFromDataURI(message.optString(LARGE_ICON_ATTR));
- builder.setLargeIcon(b);
- }
-
- if (message.has(PROGRESS_VALUE_ATTR) &&
- message.has(PROGRESS_MAX_ATTR) &&
- message.has(PROGRESS_INDETERMINATE_ATTR)) {
- try {
- final int progress = message.getInt(PROGRESS_VALUE_ATTR);
- final int progressMax = message.getInt(PROGRESS_MAX_ATTR);
- final boolean progressIndeterminate = message.getBoolean(PROGRESS_INDETERMINATE_ATTR);
- builder.setProgress(progressMax, progress, progressIndeterminate);
- } catch (JSONException ex) {
- Log.i(LOGTAG, "Error parsing", ex);
- }
- }
-
- JSONArray actions = message.optJSONArray(ACTIONS_ATTR);
- if (actions != null) {
- try {
- for (int i = 0; i < actions.length(); i++) {
- JSONObject action = actions.getJSONObject(i);
- final PendingIntent pending = buildButtonClickPendingIntent(message, action);
- final String actionTitle = action.getString(ACTION_TITLE_ATTR);
- final Uri actionImage = Uri.parse(action.optString(ACTION_ICON_ATTR));
- builder.addAction(BitmapUtils.getResource(mContext, actionImage),
- actionTitle,
- pending);
- }
- } catch (JSONException ex) {
- Log.i(LOGTAG, "Error parsing", ex);
- }
- }
-
- PendingIntent pi = buildNotificationPendingIntent(message, CLICK_EVENT);
- builder.setContentIntent(pi);
- PendingIntent deletePendingIntent = buildNotificationPendingIntent(message, CLEARED_EVENT);
- builder.setDeleteIntent(deletePendingIntent);
-
- ((NotificationClient) GeckoAppShell.getNotificationListener()).add(id, builder.build());
-
- boolean persistent = message.optBoolean(PERSISTENT_ATTR);
- // We add only not persistent notifications to the list since we want to purge only
- // them when geckoapp is destroyed.
- if (!persistent && !mClearableNotifications.containsKey(id)) {
- mClearableNotifications.put(id, message.toString());
- }
- }
-
- private void hideNotification(JSONObject message) {
- final String id;
- final String handler;
- final String cookie;
- try {
- id = message.getString("id");
- handler = message.optString("handlerKey");
- cookie = message.optString("cookie");
- } catch (JSONException ex) {
- Log.i(LOGTAG, "Error parsing", ex);
- return;
- }
-
- hideNotification(id, handler, cookie);
- }
-
- private void closeNotification(String id, String handlerKey, String cookie) {
- ((NotificationClient) GeckoAppShell.getNotificationListener()).remove(id);
- }
-
- public void hideNotification(String id, String handlerKey, String cookie) {
- mClearableNotifications.remove(id);
- closeNotification(id, handlerKey, cookie);
- }
-
- private void clearAll() {
- for (Iterator<String> i = mClearableNotifications.keySet().iterator(); i.hasNext();) {
- final String id = i.next();
- final String json = mClearableNotifications.get(id);
- i.remove();
-
- JSONObject obj;
- try {
- obj = new JSONObject(json);
- } catch (JSONException ex) {
- obj = new JSONObject();
- }
-
- closeNotification(id, obj.optString(HANDLER_ATTR), obj.optString(COOKIE_ATTR));
- }
- }
-
- public static void destroy() {
- if (sInstance != null) {
- sInstance.clearAll();
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/notifications/NotificationReceiver.java b/mobile/android/base/java/org/mozilla/gecko/notifications/NotificationReceiver.java
deleted file mode 100644
index c3dd43297..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/notifications/NotificationReceiver.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.notifications;
-
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoThread;
-import org.mozilla.gecko.mozglue.SafeIntent;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.util.Log;
-
-/**
- * Broadcast receiver for Notifications. Will forward them to GeckoApp (and start Gecko) if they're clicked.
- * If they're being dismissed, it will not start Gecko, but may forward them to JS if Gecko is running.
- * This is also the only entry point for notification intents.
- */
-public class NotificationReceiver extends BroadcastReceiver {
- private static final String LOGTAG = "GeckoNotificationReceiver";
-
- public void onReceive(Context context, Intent intent) {
- final Uri data = intent.getData();
- if (data == null) {
- Log.e(LOGTAG, "handleNotificationEvent: empty data");
- return;
- }
-
- final String action = intent.getAction();
- if (NotificationClient.CLICK_ACTION.equals(action) ||
- NotificationClient.CLOSE_ACTION.equals(action)) {
- onNotificationClientAction(context, action, data, intent);
- return;
- }
-
- final String notificationType = data.getQueryParameter(NotificationHelper.EVENT_TYPE_ATTR);
- if (notificationType == null) {
- return;
- }
-
- // In case the user swiped out the notification, we empty the id set.
- if (NotificationHelper.CLEARED_EVENT.equals(notificationType)) {
- // If Gecko isn't running, we throw away events where the notification was cancelled.
- // i.e. Don't bug the user if they're just closing a bunch of notifications.
- if (GeckoThread.isRunning()) {
- NotificationHelper.getArgsAndSendNotificationIntent(new SafeIntent(intent));
- }
-
- final NotificationClient client = (NotificationClient)
- GeckoAppShell.getNotificationListener();
- client.onNotificationClose(data.getQueryParameter(NotificationHelper.ID_ATTR));
- return;
- }
-
- forwardMessageToActivity(intent, context);
- }
-
- private void forwardMessageToActivity(final Intent intent, final Context context) {
- final ComponentName name = intent.getExtras().getParcelable(NotificationHelper.ORIGINAL_EXTRA_COMPONENT);
- intent.setComponent(name);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(intent);
- }
-
- private void onNotificationClientAction(final Context context, final String action,
- final Uri data, final Intent intent) {
- final String name = data.getQueryParameter("name");
- final String cookie = data.getQueryParameter("cookie");
- final Intent persistentIntent = (Intent)
- intent.getParcelableExtra(NotificationClient.PERSISTENT_INTENT_EXTRA);
-
- if (persistentIntent != null) {
- // Go through GeckoService for persistent notifications.
- context.startService(persistentIntent);
- }
-
- if (NotificationClient.CLICK_ACTION.equals(action)) {
- GeckoAppShell.onNotificationClick(name, cookie);
-
- if (persistentIntent != null) {
- // Don't launch GeckoApp if it's a background persistent notification.
- return;
- }
-
- final Intent appIntent = new Intent(GeckoApp.ACTION_ALERT_CALLBACK);
- appIntent.setComponent(new ComponentName(
- data.getAuthority(), data.getPath().substring(1))); // exclude leading slash.
- appIntent.setData(data);
- appIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(appIntent);
-
- } else if (NotificationClient.CLOSE_ACTION.equals(action)) {
- GeckoAppShell.onNotificationClose(name, cookie);
-
- final NotificationClient client = (NotificationClient)
- GeckoAppShell.getNotificationListener();
- client.onNotificationClose(name);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/notifications/NotificationService.java b/mobile/android/base/java/org/mozilla/gecko/notifications/NotificationService.java
deleted file mode 100644
index 04b94cd1a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/notifications/NotificationService.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.notifications;
-
-import android.app.Notification;
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-
-import org.mozilla.gecko.R;
-
-public final class NotificationService extends Service {
- public static final String EXTRA_NOTIFICATION = "notification";
-
- @Override // Service
- public int onStartCommand(final Intent intent, final int flags, final int startId) {
- final Notification notification = intent.getParcelableExtra(EXTRA_NOTIFICATION);
- if (notification != null) {
- // Start foreground notification.
- startForeground(R.id.foregroundNotification, notification);
- return START_NOT_STICKY;
- }
-
- // Stop foreground notification
- stopForeground(true);
- stopSelfResult(startId);
- return START_NOT_STICKY;
- }
-
- @Override // Service
- public IBinder onBind(final Intent intent) {
- return null;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/notifications/WhatsNewReceiver.java b/mobile/android/base/java/org/mozilla/gecko/notifications/WhatsNewReceiver.java
deleted file mode 100644
index 6e799bf74..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/notifications/WhatsNewReceiver.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.notifications;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.support.v4.app.NotificationCompat;
-import android.text.TextUtils;
-
-import com.keepsafe.switchboard.SwitchBoard;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.Locales;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.Experiments;
-
-import java.util.Locale;
-
-public class WhatsNewReceiver extends BroadcastReceiver {
-
- public static final String EXTRA_WHATSNEW_NOTIFICATION = "whatsnew_notification";
- private static final String ACTION_NOTIFICATION_CANCELLED = "notification_cancelled";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (ACTION_NOTIFICATION_CANCELLED.equals(intent.getAction())) {
- Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, TelemetryContract.Method.NOTIFICATION, EXTRA_WHATSNEW_NOTIFICATION);
- return;
- }
-
- final String dataString = intent.getDataString();
- if (TextUtils.isEmpty(dataString) || !dataString.contains(AppConstants.ANDROID_PACKAGE_NAME)) {
- return;
- }
-
- if (!SwitchBoard.isInExperiment(context, Experiments.WHATSNEW_NOTIFICATION)) {
- return;
- }
-
- if (!isPreferenceEnabled(context)) {
- return;
- }
-
- showWhatsNewNotification(context);
- }
-
- private boolean isPreferenceEnabled(Context context) {
- return GeckoSharedPrefs.forApp(context).getBoolean(GeckoPreferences.PREFS_NOTIFICATIONS_WHATS_NEW, true);
- }
-
- private void showWhatsNewNotification(Context context) {
- final Notification notification = new NotificationCompat.Builder(context)
- .setContentTitle(context.getString(R.string.whatsnew_notification_title))
- .setContentText(context.getString(R.string.whatsnew_notification_summary))
- .setSmallIcon(R.drawable.ic_status_logo)
- .setAutoCancel(true)
- .setContentIntent(getContentIntent(context))
- .setDeleteIntent(getDeleteIntent(context))
- .build();
-
- final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- final int notificationID = EXTRA_WHATSNEW_NOTIFICATION.hashCode();
- notificationManager.notify(notificationID, notification);
-
- Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.NOTIFICATION, EXTRA_WHATSNEW_NOTIFICATION);
- }
-
- private PendingIntent getContentIntent(Context context) {
- final String link = context.getString(R.string.whatsnew_notification_url,
- AppConstants.MOZ_APP_VERSION,
- AppConstants.OS_TARGET,
- Locales.getLanguageTag(Locale.getDefault()));
-
- final Intent i = new Intent(Intent.ACTION_VIEW);
- i.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
- i.setData(Uri.parse(link));
- i.putExtra(EXTRA_WHATSNEW_NOTIFICATION, true);
-
- return PendingIntent.getActivity(context, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- private PendingIntent getDeleteIntent(Context context) {
- final Intent i = new Intent(context, WhatsNewReceiver.class);
- i.setAction(ACTION_NOTIFICATION_CANCELLED);
-
- return PendingIntent.getBroadcast(context, 0, i, PendingIntent.FLAG_CANCEL_CURRENT);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/overlays/OverlayConstants.java b/mobile/android/base/java/org/mozilla/gecko/overlays/OverlayConstants.java
deleted file mode 100644
index 16f5560d3..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/overlays/OverlayConstants.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.overlays;
-
-/**
- * Constants used by the share handler service (and clients).
- * The intent API used by the service is defined herein.
- */
-public class OverlayConstants {
- /*
- * OverlayIntentHandler service intent actions.
- */
-
- /*
- * Causes the service to broadcast an intent containing state necessary for proper display of
- * a UI to select a target share method.
- *
- * Intent parameters:
- *
- * None.
- */
- public static final String ACTION_PREPARE_SHARE = "org.mozilla.gecko.overlays.ACTION_PREPARE_SHARE";
-
- /*
- * Action for sharing a page.
- *
- * Intent parameters:
- *
- * $EXTRA_URL: URL of page to share. (required)
- * $EXTRA_SHARE_METHOD: Method(s) via which to share this url/title combination. Can be either a
- * ShareType or a ShareType[]
- * $EXTRA_TITLE: Title of page to share (optional)
- * $EXTRA_PARAMETERS: Parcelable of extra data to pass to the ShareMethod (optional)
- */
- public static final String ACTION_SHARE = "org.mozilla.gecko.overlays.ACTION_SHARE";
-
- /*
- * OverlayIntentHandler service intent extra field keys.
- */
-
- // The URL/title of the page being shared
- public static final String EXTRA_URL = "URL";
- public static final String EXTRA_TITLE = "TITLE";
-
- // The optional extra Parcelable parameters for a ShareMethod.
- public static final String EXTRA_PARAMETERS = "EXTRA";
-
- // The extra field key used for holding the ShareMethod.Type we wish to use for an operation.
- public static final String EXTRA_SHARE_METHOD = "SHARE_METHOD";
-
- /*
- * ShareMethod UI event intent constants. Broadcast by ShareMethods using LocalBroadcastManager
- * when state has changed that requires an update of any currently-displayed share UI.
- */
-
- /*
- * Action for a ShareMethod UI event.
- *
- * Intent parameters:
- *
- * $EXTRA_SHARE_METHOD: The ShareType to which this event relates.
- * ... ShareType-specific parameters as desired... (optional)
- */
- public static final String SHARE_METHOD_UI_EVENT = "org.mozilla.gecko.overlays.ACTION_SHARE_METHOD_UI_EVENT";
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/overlays/service/OverlayActionService.java b/mobile/android/base/java/org/mozilla/gecko/overlays/service/OverlayActionService.java
deleted file mode 100644
index 7182fcce7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/overlays/service/OverlayActionService.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.overlays.service;
-
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.IBinder;
-import android.util.Log;
-
-import org.mozilla.gecko.overlays.service.sharemethods.AddBookmark;
-import org.mozilla.gecko.overlays.service.sharemethods.SendTab;
-import org.mozilla.gecko.overlays.service.sharemethods.ShareMethod;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.util.EnumMap;
-import java.util.Map;
-
-import static org.mozilla.gecko.overlays.OverlayConstants.ACTION_PREPARE_SHARE;
-import static org.mozilla.gecko.overlays.OverlayConstants.ACTION_SHARE;
-
-/**
- * A service to receive requests from overlays to perform actions.
- * See OverlayConstants for details of the intent API supported by this service.
- *
- * Currently supported operations are:
- *
- * Add bookmark*
- * Send tab (delegates to Sync's existing handler)
- * Future: Load page in background.
- *
- * * Neither of these incur a page fetch on the service... yet. That will require headless Gecko,
- * something we're yet to have. Refactoring Gecko as a service itself and restructing the rest of
- * the app to talk to it seems like the way to go there.
- */
-public class OverlayActionService extends Service {
- private static final String LOGTAG = "GeckoOverlayService";
-
- // Map used for selecting the appropriate helper object when handling a share.
- final Map<ShareMethod.Type, ShareMethod> shareTypes = new EnumMap<>(ShareMethod.Type.class);
-
- // Map relating Strings representing share types to the corresponding ShareMethods.
- // Share methods are initialised (and shown in the UI) in the order they are given here.
- // This map is used to look up the appropriate ShareMethod when handling a request, as well as
- // for identifying which ShareMethod needs re-initialising in response to such an intent (which
- // will be necessary in situations such as the deletion of Sync accounts).
-
- // Not a bindable service.
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (intent == null) {
- return START_NOT_STICKY;
- }
-
- // Dispatch intent to appropriate method according to its action.
- String action = intent.getAction();
-
- switch (action) {
- case ACTION_SHARE:
- handleShare(intent);
- break;
- case ACTION_PREPARE_SHARE:
- initShareMethods(getApplicationContext());
- break;
- default:
- throw new IllegalArgumentException("Unsupported intent action: " + action);
- }
-
- return START_NOT_STICKY;
- }
-
- /**
- * Reinitialise all ShareMethods, causing them to broadcast any UI update events necessary.
- */
- private void initShareMethods(final Context context) {
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- shareTypes.clear();
-
- shareTypes.put(ShareMethod.Type.ADD_BOOKMARK, new AddBookmark(context));
- shareTypes.put(ShareMethod.Type.SEND_TAB, new SendTab(context));
- }
- });
- }
-
- public void handleShare(final Intent intent) {
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- ShareData shareData;
- try {
- shareData = ShareData.fromIntent(intent);
- } catch (IllegalArgumentException e) {
- Log.e(LOGTAG, "Error parsing share intent: ", e);
- return;
- }
-
- ShareMethod shareMethod = shareTypes.get(shareData.shareMethodType);
-
- final ShareMethod.Result result = shareMethod.handle(shareData);
- // Dispatch the share to the targeted ShareMethod.
- switch (result) {
- case SUCCESS:
- Log.d(LOGTAG, "Share was successful");
- break;
- case TRANSIENT_FAILURE:
- // Fall-through
- case PERMANENT_FAILURE:
- Log.e(LOGTAG, "Share failed: " + result);
- break;
- default:
- throw new IllegalStateException("Unknown share method result code: " + result);
- }
- }
- });
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/overlays/service/ShareData.java b/mobile/android/base/java/org/mozilla/gecko/overlays/service/ShareData.java
deleted file mode 100644
index df233d74a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/overlays/service/ShareData.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.overlays.service;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Parcelable;
-import org.mozilla.gecko.overlays.OverlayConstants;
-import org.mozilla.gecko.overlays.service.sharemethods.ShareMethod;
-
-import static org.mozilla.gecko.overlays.OverlayConstants.EXTRA_SHARE_METHOD;
-
-/**
- * Class to hold information related to a particular request to perform a share.
- */
-public class ShareData {
- private static final String LOGTAG = "GeckoShareRequest";
-
- public final String url;
- public final String title;
- public final Parcelable extra;
- public final ShareMethod.Type shareMethodType;
-
- public ShareData(String url, String title, Parcelable extra, ShareMethod.Type shareMethodType) {
- if (url == null) {
- throw new IllegalArgumentException("Null url passed to ShareData!");
- }
-
- this.url = url;
- this.title = title;
- this.extra = extra;
- this.shareMethodType = shareMethodType;
- }
-
- public static ShareData fromIntent(Intent intent) {
- Bundle extras = intent.getExtras();
-
- // Fish the parameters out of the Intent.
- final String url = extras.getString(OverlayConstants.EXTRA_URL);
- final String title = extras.getString(OverlayConstants.EXTRA_TITLE);
- final Parcelable extra = extras.getParcelable(OverlayConstants.EXTRA_PARAMETERS);
- ShareMethod.Type shareMethodType = (ShareMethod.Type) extras.get(EXTRA_SHARE_METHOD);
-
- return new ShareData(url, title, extra, shareMethodType);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/overlays/service/sharemethods/AddBookmark.java b/mobile/android/base/java/org/mozilla/gecko/overlays/service/sharemethods/AddBookmark.java
deleted file mode 100644
index 71931e683..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/overlays/service/sharemethods/AddBookmark.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.overlays.service.sharemethods;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.LocalBrowserDB;
-import org.mozilla.gecko.overlays.service.ShareData;
-
-public class AddBookmark extends ShareMethod {
- private static final String LOGTAG = "GeckoAddBookmark";
-
- @Override
- public Result handle(ShareData shareData) {
- ContentResolver resolver = context.getContentResolver();
-
- LocalBrowserDB browserDB = new LocalBrowserDB(GeckoProfile.DEFAULT_PROFILE);
- browserDB.addBookmark(resolver, shareData.title, shareData.url);
-
- return Result.SUCCESS;
- }
-
- public AddBookmark(Context context) {
- super(context);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/overlays/service/sharemethods/SendTab.java b/mobile/android/base/java/org/mozilla/gecko/overlays/service/sharemethods/SendTab.java
deleted file mode 100644
index 5abcbd99f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/overlays/service/sharemethods/SendTab.java
+++ /dev/null
@@ -1,296 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.overlays.service.sharemethods;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.support.v4.content.LocalBroadcastManager;
-import android.util.Log;
-
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.RemoteClient;
-import org.mozilla.gecko.db.TabsAccessor;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.overlays.OverlayConstants;
-import org.mozilla.gecko.overlays.service.ShareData;
-import org.mozilla.gecko.sync.CommandProcessor;
-import org.mozilla.gecko.sync.CommandRunner;
-import org.mozilla.gecko.sync.GlobalSession;
-import org.mozilla.gecko.sync.SyncConfiguration;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * ShareMethod implementation to handle Sync's "Send tab to device" mechanism.
- * See OverlayConstants for documentation of OverlayIntentHandler service intent API (which is how
- * this class is chiefly interacted with).
- */
-public class SendTab extends ShareMethod {
- private static final String LOGTAG = "GeckoSendTab";
-
- // Key used in the extras Bundle in the share intent used for a send tab ShareMethod.
- public static final String SEND_TAB_TARGET_DEVICES = "SEND_TAB_TARGET_DEVICES";
-
- // Key used in broadcast intent from SendTab ShareMethod specifying available RemoteClients.
- public static final String EXTRA_REMOTE_CLIENT_RECORDS = "RECORDS";
-
- // The intent we should dispatch when the button for this ShareMethod is tapped, instead of
- // taking the normal action (e.g., "Set up Sync!")
- public static final String OVERRIDE_INTENT = "OVERRIDE_INTENT";
-
- private Set<String> validGUIDs;
-
- // A TabSender appropriate to the account type we're connected to.
- private TabSender tabSender;
-
- @Override
- public Result handle(ShareData shareData) {
- if (shareData.extra == null) {
- Log.e(LOGTAG, "No target devices specified!");
-
- // Retrying with an identical lack of devices ain't gonna fix it...
- return Result.PERMANENT_FAILURE;
- }
-
- String[] targetGUIDs = ((Bundle) shareData.extra).getStringArray(SEND_TAB_TARGET_DEVICES);
-
- // Ensure all target GUIDs are devices we actually know about.
- if (!validGUIDs.containsAll(Arrays.asList(targetGUIDs))) {
- // Find the set of invalid GUIDs to provide a nice error message.
- Log.e(LOGTAG, "Not all provided GUIDs are real devices:");
- for (String targetGUID : targetGUIDs) {
- if (!validGUIDs.contains(targetGUID)) {
- Log.e(LOGTAG, "Invalid GUID: " + targetGUID);
- }
- }
-
- return Result.PERMANENT_FAILURE;
- }
-
- Log.i(LOGTAG, "Send tab handler invoked.");
-
- final CommandProcessor processor = CommandProcessor.getProcessor();
-
- final String accountGUID = tabSender.getAccountGUID();
- Log.d(LOGTAG, "Retrieved local account GUID '" + accountGUID + "'.");
-
- if (accountGUID == null) {
- Log.e(LOGTAG, "Cannot determine account GUID");
-
- // It's not completely out of the question that a background sync might come along and
- // fix everything for us...
- return Result.TRANSIENT_FAILURE;
- }
-
- // Queue up the share commands for each destination device.
- // Remember that ShareMethod.handle is always run on the background thread, so the database
- // access here is of no concern.
- for (int i = 0; i < targetGUIDs.length; i++) {
- processor.sendURIToClientForDisplay(shareData.url, targetGUIDs[i], shareData.title, accountGUID, context);
- }
-
- // Request an immediate sync to push these new commands to the network ASAP.
- Log.i(LOGTAG, "Requesting immediate clients stage sync.");
- tabSender.sync();
-
- return Result.SUCCESS;
- // ... Probably.
- }
-
- /**
- * Get an Intent suitable for broadcasting the UI state of this ShareMethod.
- * The caller shall populate the intent with the actual state.
- */
- private Intent getUIStateIntent() {
- Intent uiStateIntent = new Intent(OverlayConstants.SHARE_METHOD_UI_EVENT);
- uiStateIntent.putExtra(OverlayConstants.EXTRA_SHARE_METHOD, (Parcelable) Type.SEND_TAB);
- return uiStateIntent;
- }
-
- /**
- * Broadcast the given intent to any UIs that may be listening.
- */
- private void broadcastUIState(Intent uiStateIntent) {
- LocalBroadcastManager.getInstance(context).sendBroadcast(uiStateIntent);
- }
-
- /**
- * Load the state of the user's Firefox Sync accounts and broadcast it to any registered
- * listeners. This will cause any UIs that may exist that depend on this information to update.
- */
- public SendTab(Context aContext) {
- super(aContext);
- // Initialise the UI state intent...
-
- // Determine if the user has a new or old style sync account and load the available sync
- // clients for it.
- final AccountManager accountManager = AccountManager.get(context);
- final Account[] fxAccounts = accountManager.getAccountsByType(FxAccountConstants.ACCOUNT_TYPE);
-
- if (fxAccounts.length > 0) {
- final AndroidFxAccount fxAccount = new AndroidFxAccount(context, fxAccounts[0]);
- if (fxAccount.getState().getNeededAction() != State.Action.None) {
- // We have a Firefox Account, but it's definitely not able to send a tab
- // right now. Redirect to the status activity.
- Log.w(LOGTAG, "Firefox Account named like " + fxAccount.getObfuscatedEmail() +
- " needs action before it can send a tab; redirecting to status activity.");
-
- setOverrideIntentAction(FxAccountConstants.ACTION_FXA_STATUS);
- return;
- }
-
- tabSender = new FxAccountTabSender(fxAccount);
-
- updateClientList(tabSender);
-
- Log.i(LOGTAG, "Allowing tab send for Firefox Account.");
- registerDisplayURICommand();
- return;
- }
-
- // Have registered UIs offer to set up a Firefox Account.
- setOverrideIntentAction(FxAccountConstants.ACTION_FXA_GET_STARTED);
- }
-
- /**
- * Load the list of Sync clients that are not this device using the given TabSender.
- */
- private void updateClientList(TabSender tabSender) {
- Collection<RemoteClient> otherClients = getOtherClients(tabSender);
-
- // Put the list of RemoteClients into the uiStateIntent and broadcast it.
- RemoteClient[] records = new RemoteClient[otherClients.size()];
- records = otherClients.toArray(records);
-
- validGUIDs = new HashSet<>();
-
- for (RemoteClient client : otherClients) {
- validGUIDs.add(client.guid);
- }
-
- if (validGUIDs.isEmpty()) {
- // Guess we'd better override. We have no clients.
- // This does the broadcast for us.
- setOverrideIntentAction(FxAccountConstants.ACTION_FXA_GET_STARTED);
- return;
- }
-
- Intent uiStateIntent = getUIStateIntent();
- uiStateIntent.putExtra(EXTRA_REMOTE_CLIENT_RECORDS, records);
- broadcastUIState(uiStateIntent);
- }
-
- /**
- * Record our intention to redirect the user to a different activity when they attempt to share
- * with us, usually because we found something wrong with their Sync account (a need to login,
- * register, etc.)
- * This will be recorded in the OVERRIDE_INTENT field of the UI broadcast. Consumers should
- * dispatch this intent instead of attempting to share with this ShareMethod whenever it is
- * non-null.
- *
- * @param action to launch instead of invoking a share.
- */
- protected void setOverrideIntentAction(final String action) {
- Intent intent = new Intent(action);
- // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
- // the soft keyboard not being shown for the started activity. Why, Android, why?
- intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- Intent uiStateIntent = getUIStateIntent();
- uiStateIntent.putExtra(OVERRIDE_INTENT, intent);
-
- broadcastUIState(uiStateIntent);
- }
-
- private static void registerDisplayURICommand() {
- final CommandProcessor processor = CommandProcessor.getProcessor();
- processor.registerCommand("displayURI", new CommandRunner(3) {
- @Override
- public void executeCommand(final GlobalSession session, List<String> args) {
- CommandProcessor.displayURI(args, session.getContext());
- }
- });
- }
-
- /**
- * @return A collection of unique remote clients sorted by most recently used.
- */
- protected Collection<RemoteClient> getOtherClients(final TabSender sender) {
- if (sender == null) {
- Log.w(LOGTAG, "No tab sender when fetching other client IDs.");
- return Collections.emptyList();
- }
-
- final BrowserDB browserDB = BrowserDB.from(context);
- final TabsAccessor tabsAccessor = browserDB.getTabsAccessor();
- final Cursor remoteTabsCursor = tabsAccessor.getRemoteClientsByRecencyCursor(context);
- try {
- if (remoteTabsCursor.getCount() == 0) {
- return Collections.emptyList();
- }
- return tabsAccessor.getClientsWithoutTabsByRecencyFromCursor(remoteTabsCursor);
- } finally {
- remoteTabsCursor.close();
- }
- }
-
- /**
- * Inteface for interacting with Sync accounts. Used to hide the difference in implementation
- * between FXA and "old sync" accounts when sending tabs.
- */
- private interface TabSender {
- public static final String[] STAGES_TO_SYNC = new String[] { "clients", "tabs" };
-
- /**
- * @return Return null if the account isn't correctly initialized. Return
- * the account GUID otherwise.
- */
- String getAccountGUID();
-
- /**
- * Sync this account, specifying only clients and tabs as the engines to sync.
- */
- void sync();
- }
-
- private static class FxAccountTabSender implements TabSender {
- private final AndroidFxAccount fxAccount;
-
- public FxAccountTabSender(AndroidFxAccount fxa) {
- fxAccount = fxa;
- }
-
- @Override
- public String getAccountGUID() {
- try {
- final SharedPreferences prefs = fxAccount.getSyncPrefs();
- return prefs.getString(SyncConfiguration.PREF_ACCOUNT_GUID, null);
- } catch (Exception e) {
- Log.w(LOGTAG, "Could not get Firefox Account parameters or preferences; aborting.");
- return null;
- }
- }
-
- @Override
- public void sync() {
- fxAccount.requestImmediateSync(STAGES_TO_SYNC, null);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/overlays/service/sharemethods/ShareMethod.java b/mobile/android/base/java/org/mozilla/gecko/overlays/service/sharemethods/ShareMethod.java
deleted file mode 100644
index 768176d63..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/overlays/service/sharemethods/ShareMethod.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.overlays.service.sharemethods;
-
-import android.content.Context;
-import android.os.Parcel;
-import android.os.Parcelable;
-import org.mozilla.gecko.overlays.service.ShareData;
-
-/**
- * Represents a method of sharing a URL/title. Add a bookmark? Send to a device? Add to reading list?
- */
-public abstract class ShareMethod {
- protected final Context context;
-
- public ShareMethod(Context aContext) {
- context = aContext;
- }
-
- /**
- * Perform a share for the given title/URL combination. Called on the background thread by the
- * handler service when a request is made. The "extra" parameter is provided should a ShareMethod
- * desire to handle the share differently based on some additional parameters.
- *
- * @param title The page title for the page being shared. May be null if none can be found.
- * @param url The URL of the page to be shared. Never null.
- * @param extra A Parcelable of ShareMethod-specific parameters that may be provided by the
- * caller. Generally null, but this field may be used to provide extra input to
- * the ShareMethod (such as the device to share to in the case of SendTab).
- * @return true if the attempt to share was a success. False in the event of an error.
- */
- public abstract Result handle(ShareData shareData);
-
- /**
- * Enum representing the possible results of performing a share.
- */
- public static enum Result {
- // Victory!
- SUCCESS,
-
- // Failure, but retrying the same action again might lead to success.
- TRANSIENT_FAILURE,
-
- // Failure, and you're not going to succeed until you reinitialise the ShareMethod (ie.
- // until you repeat the entire share action). Examples include broken Sync accounts, or
- // Sync accounts with no valid target devices (so the only way to fix this is to add some
- // and try again: pushing a retry button isn't sane).
- PERMANENT_FAILURE
- }
-
- /**
- * Enum representing types of ShareMethod. Parcelable so it may be efficiently used in Intents.
- */
- public static enum Type implements Parcelable {
- ADD_BOOKMARK,
- SEND_TAB;
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(final Parcel dest, final int flags) {
- dest.writeInt(ordinal());
- }
-
- public static final Creator<Type> CREATOR = new Creator<Type>() {
- @Override
- public Type createFromParcel(final Parcel source) {
- return Type.values()[source.readInt()];
- }
-
- @Override
- public Type[] newArray(final int size) {
- return new Type[size];
- }
- };
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/overlays/ui/OverlayDialogButton.java b/mobile/android/base/java/org/mozilla/gecko/overlays/ui/OverlayDialogButton.java
deleted file mode 100644
index 8b7bc872b..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/overlays/ui/OverlayDialogButton.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.overlays.ui;
-
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-/**
- * A button in the share overlay, such as the "Add to Reading List" button.
- * Has an associated icon and label, and two states: enabled and disabled.
- *
- * When disabled, tapping results in a "pop" animation causing the icon to pulse. When enabled,
- * tapping calls the OnClickListener set by the consumer in the usual way.
- */
-public class OverlayDialogButton extends LinearLayout {
- private static final String LOGTAG = "GeckoOverlayDialogButton";
-
- // We can't use super.isEnabled(), since we want to stay clickable in disabled state.
- private boolean isEnabled = true;
-
- private final ImageView iconView;
- private final TextView labelView;
-
- private String enabledText = "";
- private String disabledText = "";
-
- private OnClickListener enabledOnClickListener;
-
- public OverlayDialogButton(Context context) {
- this(context, null);
- }
-
- public OverlayDialogButton(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- setOrientation(LinearLayout.HORIZONTAL);
-
- LayoutInflater.from(context).inflate(R.layout.overlay_share_button, this);
-
- iconView = (ImageView) findViewById(R.id.overlaybtn_icon);
- labelView = (TextView) findViewById(R.id.overlaybtn_label);
-
- super.setOnClickListener(new OnClickListener() {
-
- @Override
- public void onClick(View v) {
-
- if (isEnabled) {
- if (enabledOnClickListener != null) {
- enabledOnClickListener.onClick(v);
- } else {
- Log.e(LOGTAG, "enabledOnClickListener is null.");
- }
- } else {
- Animation anim = AnimationUtils.loadAnimation(getContext(), R.anim.overlay_pop);
- iconView.startAnimation(anim);
- }
- }
- });
-
- final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.OverlayDialogButton);
-
- Drawable drawable = typedArray.getDrawable(R.styleable.OverlayDialogButton_drawable);
- if (drawable != null) {
- setDrawable(drawable);
- }
-
- String disabledText = typedArray.getString(R.styleable.OverlayDialogButton_disabledText);
- if (disabledText != null) {
- this.disabledText = disabledText;
- }
-
- String enabledText = typedArray.getString(R.styleable.OverlayDialogButton_enabledText);
- if (enabledText != null) {
- this.enabledText = enabledText;
- }
-
- typedArray.recycle();
-
- setEnabled(true);
- }
-
- public void setDrawable(Drawable drawable) {
- iconView.setImageDrawable(drawable);
- }
-
- public void setText(String text) {
- labelView.setText(text);
- }
-
- @Override
- public void setOnClickListener(OnClickListener listener) {
- enabledOnClickListener = listener;
- }
-
- /**
- * Set the enabledness state of this view. We don't call super.setEnabled, as we want to remain
- * clickable even in the disabled state (but with a different click listener).
- */
- @Override
- public void setEnabled(boolean enabled) {
- isEnabled = enabled;
- iconView.setEnabled(enabled);
- labelView.setEnabled(enabled);
-
- if (enabled) {
- setText(enabledText);
- } else {
- setText(disabledText);
- }
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/overlays/ui/SendTabDeviceListArrayAdapter.java b/mobile/android/base/java/org/mozilla/gecko/overlays/ui/SendTabDeviceListArrayAdapter.java
deleted file mode 100644
index 08e9c59f5..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/overlays/ui/SendTabDeviceListArrayAdapter.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.overlays.ui;
-
-import java.util.Collection;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.RemoteClient;
-import org.mozilla.gecko.overlays.ui.SendTabList.State;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-
-public class SendTabDeviceListArrayAdapter extends ArrayAdapter<RemoteClient> {
- @SuppressWarnings("unused")
- private static final String LOGTAG = "GeckoSendTabAdapter";
-
- private State currentState;
-
- // String to display when in a "button-like" special state. Instead of using a
- // RemoteClient we override the rendering using this string.
- private String dummyRecordName;
-
- private final SendTabTargetSelectedListener listener;
-
- private Collection<RemoteClient> records;
-
- // The AlertDialog to show in the event the record is pressed while in the SHOW_DEVICES state.
- // This will show the user a prompt to select a device from a longer list of devices.
- private AlertDialog dialog;
-
- public SendTabDeviceListArrayAdapter(Context context, SendTabTargetSelectedListener aListener) {
- super(context, R.layout.overlay_share_send_tab_item, R.id.overlaybtn_label);
-
- listener = aListener;
-
- // We do this manually and avoid multiple notifications when doing compound operations.
- setNotifyOnChange(false);
- }
-
- /**
- * Get an array of the contents of this adapter were it in the LIST state.
- * Useful for determining the "real" contents of the adapter.
- */
- public RemoteClient[] toArray() {
- return records.toArray(new RemoteClient[records.size()]);
- }
-
- public void setRemoteClientsList(Collection<RemoteClient> remoteClientsList) {
- records = remoteClientsList;
- updateRecordList();
- }
-
- /**
- * Ensure the contents of the Adapter are synchronised with the `records` field. This may not
- * be the case if records has recently changed, or if we have experienced a state change.
- */
- public void updateRecordList() {
- if (currentState != State.LIST) {
- return;
- }
-
- clear();
-
- setNotifyOnChange(false); // So we don't notify for each add.
- addAll(records);
-
- notifyDataSetChanged();
- }
-
- @Override
- public View getView(final int position, View convertView, ViewGroup parent) {
- final Context context = getContext();
-
- // Reuse View objects if they exist.
- OverlayDialogButton row = (OverlayDialogButton) convertView;
- if (row == null) {
- row = (OverlayDialogButton) View.inflate(context, R.layout.overlay_share_send_tab_item, null);
- }
-
- // The first view in the list has a unique style.
- if (position == 0) {
- row.setBackgroundResource(R.drawable.overlay_share_button_background_first);
- } else {
- row.setBackgroundResource(R.drawable.overlay_share_button_background);
- }
-
- if (currentState != State.LIST) {
- // If we're in a special "Button-like" state, use the override string and a generic icon.
- final Drawable sendTabIcon = context.getResources().getDrawable(R.drawable.shareplane);
- row.setText(dummyRecordName);
- row.setDrawable(sendTabIcon);
- }
-
- // If we're just a button to launch the dialog, set the listener and abort.
- if (currentState == State.SHOW_DEVICES) {
- row.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- dialog.show();
- }
- });
-
- return row;
- }
-
- // The remaining states delegate to the SentTabTargetSelectedListener.
- final RemoteClient remoteClient = getItem(position);
- if (currentState == State.LIST) {
- final Drawable clientIcon = context.getResources().getDrawable(getImage(remoteClient));
- row.setText(remoteClient.name);
- row.setDrawable(clientIcon);
-
- final String listenerGUID = remoteClient.guid;
-
- row.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- listener.onSendTabTargetSelected(listenerGUID);
- }
- });
- } else {
- row.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- listener.onSendTabActionSelected();
- }
- });
- }
-
- return row;
- }
-
- private static int getImage(RemoteClient record) {
- if ("mobile".equals(record.deviceType)) {
- return R.drawable.device_mobile;
- }
-
- return R.drawable.device_desktop;
- }
-
- public void switchState(State newState) {
- if (currentState == newState) {
- return;
- }
-
- currentState = newState;
-
- switch (newState) {
- case LIST:
- updateRecordList();
- break;
- case NONE:
- showDummyRecord(getContext().getResources().getString(R.string.overlay_share_send_tab_btn_label));
- break;
- case SHOW_DEVICES:
- showDummyRecord(getContext().getResources().getString(R.string.overlay_share_send_other));
- break;
- default:
- throw new IllegalStateException("Unexpected state transition: " + newState);
- }
- }
-
- /**
- * Set the dummy override string to the given value and clear the list.
- */
- private void showDummyRecord(String name) {
- dummyRecordName = name;
- clear();
- add(null);
- notifyDataSetChanged();
- }
-
- public void setDialog(AlertDialog aDialog) {
- dialog = aDialog;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/overlays/ui/SendTabList.java b/mobile/android/base/java/org/mozilla/gecko/overlays/ui/SendTabList.java
deleted file mode 100644
index 4fc6caaa9..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/overlays/ui/SendTabList.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.overlays.ui;
-
-import static org.mozilla.gecko.overlays.ui.SendTabList.State.LOADING;
-import static org.mozilla.gecko.overlays.ui.SendTabList.State.SHOW_DEVICES;
-
-import java.util.Arrays;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.RemoteClient;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.util.AttributeSet;
-import android.widget.ListAdapter;
-import android.widget.ListView;
-
-/**
- * The SendTab button has a few different states depending on the available devices (and whether
- * we've loaded them yet...)
- *
- * Initially, the view resembles a disabled button. (the LOADING state)
- * Once state is loaded from Sync's database, we know how many devices the user may send their tab
- * to.
- *
- * If there are no targets, the user was found to not have a Sync account, or their Sync account is
- * in a state that prevents it from being able to send a tab, we enter the NONE state and display
- * a generic button which launches an appropriate activity to fix the situation when tapped (such
- * as the set up Sync wizard).
- *
- * If the number of targets does not MAX_INLINE_SYNC_TARGETS, we present a button for each of them.
- * (the LIST state)
- *
- * Otherwise, we enter the SHOW_DEVICES state, in which we display a "Send to other devices" button
- * that takes the user to a menu for selecting a target device from their complete list of many
- * devices.
- */
-public class SendTabList extends ListView {
- @SuppressWarnings("unused")
- private static final String LOGTAG = "GeckoSendTabList";
-
- // The maximum number of target devices to show in the main list. Further devices are available
- // from a secondary menu.
- public static final int MAXIMUM_INLINE_ELEMENTS = R.integer.number_of_inline_share_devices;
-
- private SendTabDeviceListArrayAdapter clientListAdapter;
-
- // Listener to fire when a share target is selected (either directly or via the prompt)
- private SendTabTargetSelectedListener listener;
-
- private final State currentState = LOADING;
-
- /**
- * Enum defining the states this view may occupy.
- */
- public enum State {
- // State when no sync targets exist (a generic "Send to Firefox Sync" button which launches
- // an activity to set it up)
- NONE,
-
- // As NONE, but disabled. Initial state. Used until we get information from Sync about what
- // we really want.
- LOADING,
-
- // A list of devices to share to.
- LIST,
-
- // A single button prompting the user to select a device to share to.
- SHOW_DEVICES
- }
-
- public SendTabList(Context context) {
- super(context);
- }
-
- public SendTabList(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public void setAdapter(ListAdapter adapter) {
- if (!(adapter instanceof SendTabDeviceListArrayAdapter)) {
- throw new IllegalArgumentException("adapter must be a SendTabDeviceListArrayAdapter instance");
- }
-
- clientListAdapter = (SendTabDeviceListArrayAdapter) adapter;
- super.setAdapter(adapter);
- }
-
- public void setSendTabTargetSelectedListener(SendTabTargetSelectedListener aListener) {
- listener = aListener;
- }
-
- public void switchState(State state) {
- if (state == currentState) {
- return;
- }
-
- clientListAdapter.switchState(state);
- if (state == SHOW_DEVICES) {
- clientListAdapter.setDialog(getDialog());
- }
- }
-
- public void setSyncClients(final RemoteClient[] c) {
- final RemoteClient[] clients = c == null ? new RemoteClient[0] : c;
-
- clientListAdapter.setRemoteClientsList(Arrays.asList(clients));
- }
-
- /**
- * Get an AlertDialog listing all devices, allowing the user to select the one they want.
- * Used when more than MAXIMUM_INLINE_ELEMENTS devices are found (to avoid displaying them all
- * inline and looking crazy).
- */
- public AlertDialog getDialog() {
- final Context context = getContext();
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
-
- final RemoteClient[] records = clientListAdapter.toArray();
- final String[] dialogElements = new String[records.length];
-
- for (int i = 0; i < records.length; i++) {
- dialogElements[i] = records[i].name;
- }
-
- builder.setTitle(R.string.overlay_share_select_device)
- .setItems(dialogElements, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int index) {
- listener.onSendTabTargetSelected(records[index].guid);
- }
- })
- .setOnCancelListener(new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialogInterface) {
- Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, TelemetryContract.Method.SHARE_OVERLAY, "device_selection_cancel");
- }
- });
-
- return builder.create();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/overlays/ui/SendTabTargetSelectedListener.java b/mobile/android/base/java/org/mozilla/gecko/overlays/ui/SendTabTargetSelectedListener.java
deleted file mode 100644
index 79da526da..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/overlays/ui/SendTabTargetSelectedListener.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
-package org.mozilla.gecko.overlays.ui;
-
-/**
- * Interface for classes that wish to listen for the selection of an element from a SendTabList.
- */
-public interface SendTabTargetSelectedListener {
- /**
- * Called when a row in the SendTabList is clicked.
- *
- * @param targetGUID The GUID of the ClientRecord the element represents (if any, otherwise null)
- */
- public void onSendTabTargetSelected(String targetGUID);
-
- /**
- * Called when the overall Send Tab item is clicked.
- *
- * This implies that the clients list was unavailable.
- */
- public void onSendTabActionSelected();
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/overlays/ui/ShareDialog.java b/mobile/android/base/java/org/mozilla/gecko/overlays/ui/ShareDialog.java
deleted file mode 100644
index 156fdda2a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/overlays/ui/ShareDialog.java
+++ /dev/null
@@ -1,493 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.overlays.ui;
-
-import java.net.URISyntaxException;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.Locales;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.LocalBrowserDB;
-import org.mozilla.gecko.db.RemoteClient;
-import org.mozilla.gecko.overlays.OverlayConstants;
-import org.mozilla.gecko.overlays.service.OverlayActionService;
-import org.mozilla.gecko.overlays.service.sharemethods.SendTab;
-import org.mozilla.gecko.overlays.service.sharemethods.ShareMethod;
-import org.mozilla.gecko.sync.setup.activities.WebURLFinder;
-import org.mozilla.gecko.util.IntentUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.UIAsyncTask;
-
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.support.v4.content.LocalBroadcastManager;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.animation.AnimationSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.widget.TextView;
-import android.widget.Toast;
-
-/**
- * A transparent activity that displays the share overlay.
- */
-public class ShareDialog extends Locales.LocaleAwareActivity implements SendTabTargetSelectedListener {
-
- private enum State {
- DEFAULT,
- DEVICES_ONLY // Only display the device list.
- }
-
- private static final String LOGTAG = "GeckoShareDialog";
-
- /** Flag to indicate that we should always show the device list; specific to this release channel. **/
- public static final String INTENT_EXTRA_DEVICES_ONLY =
- AppConstants.ANDROID_PACKAGE_NAME + ".intent.extra.DEVICES_ONLY";
-
- /** The maximum number of devices we'll show in the dialog when in State.DEFAULT. **/
- private static final int MAXIMUM_INLINE_DEVICES = 2;
-
- private State state;
-
- private SendTabList sendTabList;
- private OverlayDialogButton bookmarkButton;
-
- // The bookmark button drawable set from XML - we need this to reset state.
- private Drawable bookmarkButtonDrawable;
-
- private String url;
- private String title;
-
- // The override intent specified by SendTab (if any). See SendTab.java.
- private Intent sendTabOverrideIntent;
-
- // Flag set during animation to prevent animation multiple-start.
- private boolean isAnimating;
-
- // BroadcastReceiver to receive callbacks from ShareMethods which are changing state.
- private final BroadcastReceiver uiEventListener = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- ShareMethod.Type originShareMethod = intent.getParcelableExtra(OverlayConstants.EXTRA_SHARE_METHOD);
- switch (originShareMethod) {
- case SEND_TAB:
- handleSendTabUIEvent(intent);
- break;
- default:
- throw new IllegalArgumentException("UIEvent broadcast from ShareMethod that isn't thought to support such broadcasts.");
- }
- }
- };
-
- /**
- * Called when a UI event broadcast is received from the SendTab ShareMethod.
- */
- protected void handleSendTabUIEvent(Intent intent) {
- sendTabOverrideIntent = intent.getParcelableExtra(SendTab.OVERRIDE_INTENT);
-
- RemoteClient[] remoteClientRecords = (RemoteClient[]) intent.getParcelableArrayExtra(SendTab.EXTRA_REMOTE_CLIENT_RECORDS);
-
- // Escape hatch: we don't show the option to open this dialog in this state so this should
- // never be run. However, due to potential inconsistencies in synced client state
- // (e.g. bug 1122302 comment 47), we might fail.
- if (state == State.DEVICES_ONLY &&
- (remoteClientRecords == null || remoteClientRecords.length == 0)) {
- Log.e(LOGTAG, "In state: " + State.DEVICES_ONLY + " and received 0 synced clients. Finishing...");
- Toast.makeText(this, getResources().getText(R.string.overlay_no_synced_devices), Toast.LENGTH_SHORT)
- .show();
- finish();
- return;
- }
-
- sendTabList.setSyncClients(remoteClientRecords);
-
- if (state == State.DEVICES_ONLY ||
- remoteClientRecords == null ||
- remoteClientRecords.length <= MAXIMUM_INLINE_DEVICES) {
- // Show the list of devices in-line.
- sendTabList.switchState(SendTabList.State.LIST);
-
- // The first item in the list has a unique style. If there are no items
- // in the list, the next button appears to be the first item in the list.
- //
- // Note: a more thorough implementation would add this
- // (and other non-ListView buttons) into a custom ListView.
- if (remoteClientRecords == null || remoteClientRecords.length == 0) {
- bookmarkButton.setBackgroundResource(
- R.drawable.overlay_share_button_background_first);
- }
- return;
- }
-
- // Just show a button to launch the list of devices to choose from.
- sendTabList.switchState(SendTabList.State.SHOW_DEVICES);
- }
-
- @Override
- protected void onDestroy() {
- // Remove the listener when the activity is destroyed: we no longer care.
- // Note: The activity can be destroyed without onDestroy being called. However, this occurs
- // only when the application is killed, something which also kills the registered receiver
- // list, and the service, and everything else: so we don't care.
- LocalBroadcastManager.getInstance(this).unregisterReceiver(uiEventListener);
-
- super.onDestroy();
- }
-
- /**
- * Show a toast indicating we were started with no URL, and then stop.
- */
- private void abortDueToNoURL() {
- Log.e(LOGTAG, "Unable to process shared intent. No URL found!");
-
- // Display toast notifying the user of failure (most likely a developer who screwed up
- // trying to send a share intent).
- Toast toast = Toast.makeText(this, getResources().getText(R.string.overlay_share_no_url), Toast.LENGTH_SHORT);
- toast.show();
- finish();
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.overlay_share_dialog);
-
- LocalBroadcastManager.getInstance(this).registerReceiver(uiEventListener,
- new IntentFilter(OverlayConstants.SHARE_METHOD_UI_EVENT));
-
- // Send tab.
- sendTabList = (SendTabList) findViewById(R.id.overlay_send_tab_btn);
-
- // Register ourselves as both the listener and the context for the Adapter.
- final SendTabDeviceListArrayAdapter adapter = new SendTabDeviceListArrayAdapter(this, this);
- sendTabList.setAdapter(adapter);
- sendTabList.setSendTabTargetSelectedListener(this);
-
- bookmarkButton = (OverlayDialogButton) findViewById(R.id.overlay_share_bookmark_btn);
-
- bookmarkButtonDrawable = bookmarkButton.getBackground();
-
- // Bookmark button
- bookmarkButton = (OverlayDialogButton) findViewById(R.id.overlay_share_bookmark_btn);
- bookmarkButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- addBookmark();
- }
- });
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- final Intent intent = getIntent();
-
- state = intent.getBooleanExtra(INTENT_EXTRA_DEVICES_ONLY, false) ?
- State.DEVICES_ONLY : State.DEFAULT;
-
- // If the Activity is being reused, we need to reset the state. Ideally, we create a
- // new instance for each call, but Android L breaks this (bug 1137928).
- sendTabList.switchState(SendTabList.State.LOADING);
- bookmarkButton.setBackgroundDrawable(bookmarkButtonDrawable);
-
- // The URL is usually hiding somewhere in the extra text. Extract it.
- final String extraText = IntentUtils.getStringExtraSafe(intent, Intent.EXTRA_TEXT);
- if (TextUtils.isEmpty(extraText)) {
- abortDueToNoURL();
- return;
- }
-
- final String pageUrl = new WebURLFinder(extraText).bestWebURL();
- if (TextUtils.isEmpty(pageUrl)) {
- abortDueToNoURL();
- return;
- }
-
- // Have the service start any initialisation work that's necessary for us to show the correct
- // UI. The results of such work will come in via the BroadcastListener.
- Intent serviceStartupIntent = new Intent(this, OverlayActionService.class);
- serviceStartupIntent.setAction(OverlayConstants.ACTION_PREPARE_SHARE);
- startService(serviceStartupIntent);
-
- // Start the slide-up animation.
- getWindow().setWindowAnimations(0);
- final Animation anim = AnimationUtils.loadAnimation(this, R.anim.overlay_slide_up);
- findViewById(R.id.sharedialog).startAnimation(anim);
-
- // If provided, we use the subject text to give us something nice to display.
- // If not, we wing it with the URL.
-
- // TODO: Consider polling Fennec databases to find better information to display.
- final String subjectText = intent.getStringExtra(Intent.EXTRA_SUBJECT);
-
- final String telemetryExtras = "title=" + (subjectText != null);
- if (subjectText != null) {
- ((TextView) findViewById(R.id.title)).setText(subjectText);
- }
-
- Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.SHARE_OVERLAY, telemetryExtras);
-
- title = subjectText;
- url = pageUrl;
-
- // Set the subtitle text on the view and cause it to marquee if it's too long (which it will
- // be, since it's a URL).
- final TextView subtitleView = (TextView) findViewById(R.id.subtitle);
- subtitleView.setText(pageUrl);
- subtitleView.setEllipsize(TextUtils.TruncateAt.MARQUEE);
- subtitleView.setSingleLine(true);
- subtitleView.setMarqueeRepeatLimit(5);
- subtitleView.setSelected(true);
-
- final View titleView = findViewById(R.id.title);
-
- if (state == State.DEVICES_ONLY) {
- bookmarkButton.setVisibility(View.GONE);
-
- titleView.setOnClickListener(null);
- subtitleView.setOnClickListener(null);
- return;
- }
-
- bookmarkButton.setVisibility(View.VISIBLE);
-
- // Configure buttons.
- final View.OnClickListener launchBrowser = new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- ShareDialog.this.launchBrowser();
- }
- };
-
- titleView.setOnClickListener(launchBrowser);
- subtitleView.setOnClickListener(launchBrowser);
-
- final LocalBrowserDB browserDB = new LocalBrowserDB(getCurrentProfile());
- setButtonState(url, browserDB);
- }
-
- @Override
- protected void onNewIntent(final Intent intent) {
- super.onNewIntent(intent);
-
- // The intent returned by getIntent is not updated automatically.
- setIntent(intent);
- }
-
- /**
- * Sets the state of the bookmark/reading list buttons: they are disabled if the given URL is
- * already in the corresponding list.
- */
- private void setButtonState(final String pageURL, final LocalBrowserDB browserDB) {
- new UIAsyncTask.WithoutParams<Void>(ThreadUtils.getBackgroundHandler()) {
- // Flags to hold the result
- boolean isBookmark;
-
- @Override
- protected Void doInBackground() {
- final ContentResolver contentResolver = getApplicationContext().getContentResolver();
-
- isBookmark = browserDB.isBookmark(contentResolver, pageURL);
-
- return null;
- }
-
- @Override
- protected void onPostExecute(Void aVoid) {
- findViewById(R.id.overlay_share_bookmark_btn).setEnabled(!isBookmark);
- }
- }.execute();
- }
-
- /**
- * Helper method to get an overlay service intent populated with the data held in this dialog.
- */
- private Intent getServiceIntent(ShareMethod.Type method) {
- final Intent serviceIntent = new Intent(this, OverlayActionService.class);
- serviceIntent.setAction(OverlayConstants.ACTION_SHARE);
-
- serviceIntent.putExtra(OverlayConstants.EXTRA_SHARE_METHOD, (Parcelable) method);
- serviceIntent.putExtra(OverlayConstants.EXTRA_URL, url);
- serviceIntent.putExtra(OverlayConstants.EXTRA_TITLE, title);
-
- return serviceIntent;
- }
-
- @Override
- public void finish() {
- finish(true);
- }
-
- private void finish(final boolean shouldOverrideAnimations) {
- super.finish();
- if (shouldOverrideAnimations) {
- // Don't perform an activity-dismiss animation.
- overridePendingTransition(0, 0);
- }
- }
-
- /*
- * Button handlers. Send intents to the background service responsible for processing requests
- * on Fennec in the background. (a nice extensible mechanism for "doing stuff without properly
- * launching Fennec").
- */
-
- @Override
- public void onSendTabActionSelected() {
- // This requires an override intent.
- if (sendTabOverrideIntent == null) {
- throw new IllegalStateException("sendTabOverrideIntent must not be null");
- }
-
- startActivity(sendTabOverrideIntent);
- finish();
- }
-
- @Override
- public void onSendTabTargetSelected(String targetGUID) {
- // targetGUID being null with no override intent should be an impossible state.
- if (targetGUID == null) {
- throw new IllegalStateException("targetGUID must not be null");
- }
-
- Intent serviceIntent = getServiceIntent(ShareMethod.Type.SEND_TAB);
-
- // Currently, only one extra parameter is necessary (the GUID of the target device).
- Bundle extraParameters = new Bundle();
-
- // Future: Handle multiple-selection. Bug 1061297.
- extraParameters.putStringArray(SendTab.SEND_TAB_TARGET_DEVICES, new String[] { targetGUID });
-
- serviceIntent.putExtra(OverlayConstants.EXTRA_PARAMETERS, extraParameters);
-
- startService(serviceIntent);
- animateOut(true);
-
- Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.SHARE_OVERLAY, "sendtab");
- }
-
- public void addBookmark() {
- startService(getServiceIntent(ShareMethod.Type.ADD_BOOKMARK));
- animateOut(true);
-
- Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.SHARE_OVERLAY, "bookmark");
- }
-
- public void launchBrowser() {
- try {
- // This can launch in the guest profile. Sorry.
- final Intent i = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
- i.setClassName(AppConstants.ANDROID_PACKAGE_NAME, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
- startActivity(i);
- } catch (URISyntaxException e) {
- // Nothing much we can do.
- } finally {
- // Since we're changing apps, users expect the default app switch animations.
- finish(false);
- }
- }
-
- private String getCurrentProfile() {
- return GeckoProfile.DEFAULT_PROFILE;
- }
-
- /**
- * Slide the overlay down off the screen, display
- * a check (if given), and finish the activity.
- */
- private void animateOut(final boolean shouldDisplayConfirmation) {
- if (isAnimating) {
- return;
- }
-
- isAnimating = true;
- final Animation slideOutAnim = AnimationUtils.loadAnimation(this, R.anim.overlay_slide_down);
-
- final Animation animationToFinishActivity;
- if (!shouldDisplayConfirmation) {
- animationToFinishActivity = slideOutAnim;
- } else {
- final View check = findViewById(R.id.check);
- check.setVisibility(View.VISIBLE);
- final Animation checkEntryAnim = AnimationUtils.loadAnimation(this, R.anim.overlay_check_entry);
- final Animation checkExitAnim = AnimationUtils.loadAnimation(this, R.anim.overlay_check_exit);
- checkExitAnim.setStartOffset(checkEntryAnim.getDuration() + 500);
-
- final AnimationSet checkAnimationSet = new AnimationSet(this, null);
- checkAnimationSet.addAnimation(checkEntryAnim);
- checkAnimationSet.addAnimation(checkExitAnim);
-
- check.startAnimation(checkAnimationSet);
- animationToFinishActivity = checkExitAnim;
- }
-
- findViewById(R.id.sharedialog).startAnimation(slideOutAnim);
- animationToFinishActivity.setAnimationListener(new Animation.AnimationListener() {
- @Override
- public void onAnimationStart(Animation animation) { /* Unused. */ }
-
- @Override
- public void onAnimationEnd(Animation animation) {
- finish();
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) { /* Unused. */ }
- });
-
- // Allows the user to dismiss the animation early.
- setFullscreenFinishOnClickListener();
- }
-
- /**
- * Sets a fullscreen {@link #finish()} click listener. We do this rather than attaching an
- * onClickListener to the root View because in that case, we need to remove all of the
- * existing listeners, which is less robust.
- */
- private void setFullscreenFinishOnClickListener() {
- final View clickTarget = findViewById(R.id.fullscreen_click_target);
- clickTarget.setVisibility(View.VISIBLE);
- clickTarget.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- finish();
- }
- });
- }
-
- /**
- * Close the dialog if back is pressed.
- */
- @Override
- public void onBackPressed() {
- animateOut(false);
- Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, TelemetryContract.Method.SHARE_OVERLAY);
- }
-
- /**
- * Close the dialog if the anything that isn't a button is tapped.
- */
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- animateOut(false);
- Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, TelemetryContract.Method.SHARE_OVERLAY);
- return true;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/AlignRightLinkPreference.java b/mobile/android/base/java/org/mozilla/gecko/preferences/AlignRightLinkPreference.java
deleted file mode 100644
index b68a018f2..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/AlignRightLinkPreference.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-class AlignRightLinkPreference extends LinkPreference {
-
- public AlignRightLinkPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- setLayoutResource(R.layout.preference_rightalign_icon);
- }
-
- public AlignRightLinkPreference(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- setLayoutResource(R.layout.preference_rightalign_icon);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/AndroidImport.java b/mobile/android/base/java/org/mozilla/gecko/preferences/AndroidImport.java
deleted file mode 100644
index bb71ce78b..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/AndroidImport.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import android.content.ContentValues;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.os.Build;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserContract.Bookmarks;
-import org.mozilla.gecko.db.LocalBrowserDB;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.IconsHelper;
-import org.mozilla.gecko.icons.storage.DiskStorage;
-
-import android.content.ContentProviderOperation;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.OperationApplicationException;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.provider.BaseColumns;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.ArrayList;
-
-public class AndroidImport implements Runnable {
- /**
- * The Android M SDK removed several fields and methods from android.provider.Browser. This class is used as a
- * replacement to support building with the new SDK but at the same time still use these fields on lower Android
- * versions.
- */
- private static class LegacyBrowserProvider {
- public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
-
- // Incomplete: This are just the fields we currently use in our code base
- public static class BookmarkColumns implements BaseColumns {
- public static final String URL = "url";
- public static final String VISITS = "visits";
- public static final String DATE = "date";
- public static final String BOOKMARK = "bookmark";
- public static final String TITLE = "title";
- public static final String CREATED = "created";
- public static final String FAVICON = "favicon";
- }
- }
-
- public static final Uri SAMSUNG_BOOKMARKS_URI = Uri.parse("content://com.sec.android.app.sbrowser.browser/bookmarks");
- public static final Uri SAMSUNG_HISTORY_URI = Uri.parse("content://com.sec.android.app.sbrowser.browser/history");
- public static final String SAMSUNG_MANUFACTURER = "samsung";
-
- private static final String LOGTAG = "AndroidImport";
- private final Context mContext;
- private final Runnable mOnDoneRunnable;
- private final ArrayList<ContentProviderOperation> mOperations;
- private final ContentResolver mCr;
- private final LocalBrowserDB mDB;
- private final boolean mImportBookmarks;
- private final boolean mImportHistory;
-
- public AndroidImport(Context context, Runnable onDoneRunnable,
- boolean doBookmarks, boolean doHistory) {
- mContext = context;
- mOnDoneRunnable = onDoneRunnable;
- mOperations = new ArrayList<ContentProviderOperation>();
- mCr = mContext.getContentResolver();
- mDB = new LocalBrowserDB(GeckoProfile.get(context).getName());
- mImportBookmarks = doBookmarks;
- mImportHistory = doHistory;
- }
-
- public void mergeBookmarks() {
- Cursor cursor = null;
- try {
- cursor = query(LegacyBrowserProvider.BOOKMARKS_URI,
- SAMSUNG_BOOKMARKS_URI,
- LegacyBrowserProvider.BookmarkColumns.BOOKMARK + " = 1");
-
- if (cursor != null) {
- final int faviconCol = cursor.getColumnIndexOrThrow(LegacyBrowserProvider.BookmarkColumns.FAVICON);
- final int titleCol = cursor.getColumnIndexOrThrow(LegacyBrowserProvider.BookmarkColumns.TITLE);
- final int urlCol = cursor.getColumnIndexOrThrow(LegacyBrowserProvider.BookmarkColumns.URL);
- // http://code.google.com/p/android/issues/detail?id=17969
- final int createCol = cursor.getColumnIndex(LegacyBrowserProvider.BookmarkColumns.CREATED);
-
- cursor.moveToFirst();
- while (!cursor.isAfterLast()) {
- String url = cursor.getString(urlCol);
- String title = cursor.getString(titleCol);
- long created;
- if (createCol >= 0) {
- created = cursor.getLong(createCol);
- } else {
- created = System.currentTimeMillis();
- }
- // Need to set it to the current time so Sync picks it up.
- long modified = System.currentTimeMillis();
- byte[] data = cursor.getBlob(faviconCol);
- mDB.updateBookmarkInBatch(mCr, mOperations,
- url, title, null, -1,
- created, modified,
- BrowserContract.Bookmarks.DEFAULT_POSITION,
- null, Bookmarks.TYPE_BOOKMARK);
- if (data != null) {
- storeBitmap(data, url);
- }
- cursor.moveToNext();
- }
- }
- } finally {
- if (cursor != null)
- cursor.close();
- }
-
- flushBatchOperations();
- }
-
- public void mergeHistory() {
- ArrayList<ContentValues> visitsToSynthesize = new ArrayList<>();
- Cursor cursor = null;
- try {
- cursor = query (LegacyBrowserProvider.BOOKMARKS_URI,
- SAMSUNG_HISTORY_URI,
- LegacyBrowserProvider.BookmarkColumns.BOOKMARK + " = 0 AND " +
- LegacyBrowserProvider.BookmarkColumns.VISITS + " > 0");
-
- if (cursor != null) {
- final int dateCol = cursor.getColumnIndexOrThrow(LegacyBrowserProvider.BookmarkColumns.DATE);
- final int faviconCol = cursor.getColumnIndexOrThrow(LegacyBrowserProvider.BookmarkColumns.FAVICON);
- final int titleCol = cursor.getColumnIndexOrThrow(LegacyBrowserProvider.BookmarkColumns.TITLE);
- final int urlCol = cursor.getColumnIndexOrThrow(LegacyBrowserProvider.BookmarkColumns.URL);
- final int visitsCol = cursor.getColumnIndexOrThrow(LegacyBrowserProvider.BookmarkColumns.VISITS);
-
- cursor.moveToFirst();
- while (!cursor.isAfterLast()) {
- String url = cursor.getString(urlCol);
- String title = cursor.getString(titleCol);
- long date = cursor.getLong(dateCol);
- int visits = cursor.getInt(visitsCol);
- byte[] data = cursor.getBlob(faviconCol);
- mDB.updateHistoryInBatch(mCr, mOperations, url, title, date, visits);
- if (data != null) {
- storeBitmap(data, url);
- }
- ContentValues visitData = new ContentValues();
- visitData.put(LocalBrowserDB.HISTORY_VISITS_DATE, date);
- visitData.put(LocalBrowserDB.HISTORY_VISITS_URL, url);
- visitData.put(LocalBrowserDB.HISTORY_VISITS_COUNT, visits);
- visitsToSynthesize.add(visitData);
- cursor.moveToNext();
- }
- }
- } finally {
- if (cursor != null)
- cursor.close();
- }
-
- flushBatchOperations();
-
- // Now that we have flushed history records, we need to synthesize individual visits. We have
- // gathered information about all of the visits we need to synthesize into visitsForSynthesis.
- mDB.insertVisitsFromImportHistoryInBatch(mCr, mOperations, visitsToSynthesize);
-
- flushBatchOperations();
- }
-
- private void storeBitmap(byte[] data, String url) {
- if (TextUtils.isEmpty(url) || data == null) {
- return;
- }
-
- final Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
- if (bitmap == null) {
- return;
- }
-
- final String iconUrl = IconsHelper.guessDefaultFaviconURL(url);
- if (iconUrl == null) {
- return;
- }
-
- final DiskStorage storage = DiskStorage.get(mContext);
-
- storage.putIcon(url, bitmap);
- storage.putMapping(url, iconUrl);
- }
-
- protected Cursor query(Uri mainUri, Uri fallbackUri, String condition) {
- final Cursor cursor = mCr.query(mainUri, null, condition, null, null);
- if (Build.MANUFACTURER.equals(SAMSUNG_MANUFACTURER) && (cursor == null || cursor.getCount() == 0)) {
- if (cursor != null) {
- cursor.close();
- }
- return mCr.query(fallbackUri, null, null, null, null);
- }
- return cursor;
- }
-
- protected void flushBatchOperations() {
- Log.d(LOGTAG, "Flushing " + mOperations.size() + " DB operations");
- try {
- // We don't really care for the results, this is best-effort.
- mCr.applyBatch(BrowserContract.AUTHORITY, mOperations);
- } catch (RemoteException e) {
- Log.e(LOGTAG, "Remote exception while updating db: ", e);
- } catch (OperationApplicationException e) {
- // Bug 716729 means this happens even in normal circumstances
- Log.d(LOGTAG, "Error while applying database updates: ", e);
- }
- mOperations.clear();
- }
-
- @Override
- public void run() {
- if (mImportBookmarks) {
- mergeBookmarks();
- }
- if (mImportHistory) {
- mergeHistory();
- }
-
- mOnDoneRunnable.run();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/AndroidImportPreference.java b/mobile/android/base/java/org/mozilla/gecko/preferences/AndroidImportPreference.java
deleted file mode 100644
index 0f1d3ec3f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/AndroidImportPreference.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.restrictions.Restrictable;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.util.Set;
-
-import android.app.ProgressDialog;
-import android.content.Context;
-import android.preference.Preference;
-import android.util.AttributeSet;
-import android.util.Log;
-
-class AndroidImportPreference extends MultiPrefMultiChoicePreference {
- private static final String LOGTAG = "AndroidImport";
- public static final String PREF_KEY = "android.not_a_preference.import_android";
- private static final String PREF_KEY_PREFIX = "import_android.data.";
- private final Context mContext;
-
- public static class Handler implements GeckoPreferences.PrefHandler {
- public boolean setupPref(Context context, Preference pref) {
- // Feature disabled on devices running Android M+ (Bug 1183559)
- return Versions.preMarshmallow && Restrictions.isAllowed(context, Restrictable.IMPORT_SETTINGS);
- }
-
- public void onChange(Context context, Preference pref, Object newValue) { }
- }
-
- public AndroidImportPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- }
-
- @Override
- protected void onDialogClosed(boolean positiveResult) {
- super.onDialogClosed(positiveResult);
-
- if (!positiveResult)
- return;
-
- boolean bookmarksChecked = false;
- boolean historyChecked = false;
-
- Set<String> values = getValues();
-
- for (String value : values) {
- // Import checkbox values are stored in Android prefs to
- // remember their check states. The key names are import_android.data.X
- String key = value.substring(PREF_KEY_PREFIX.length());
- if ("bookmarks".equals(key)) {
- bookmarksChecked = true;
- } else if ("history".equals(key)) {
- historyChecked = true;
- }
- }
-
- runImport(bookmarksChecked, historyChecked);
- }
-
- protected void runImport(final boolean doBookmarks, final boolean doHistory) {
- Log.i(LOGTAG, "Importing Android history/bookmarks");
- if (!doBookmarks && !doHistory) {
- return;
- }
-
- final String dialogTitle;
- if (doBookmarks && doHistory) {
- dialogTitle = mContext.getString(R.string.bookmarkhistory_import_both);
- } else if (doBookmarks) {
- dialogTitle = mContext.getString(R.string.bookmarkhistory_import_bookmarks);
- } else {
- dialogTitle = mContext.getString(R.string.bookmarkhistory_import_history);
- }
-
- final ProgressDialog dialog =
- ProgressDialog.show(mContext,
- dialogTitle,
- mContext.getString(R.string.bookmarkhistory_import_wait),
- true);
-
- final Runnable stopCallback = new Runnable() {
- @Override
- public void run() {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- dialog.dismiss();
- }
- });
- }
- };
-
- ThreadUtils.postToBackgroundThread(
- // Constructing AndroidImport may need finding the profile,
- // which hits disk, so it needs to go into a Runnable too.
- new Runnable() {
- @Override
- public void run() {
- new AndroidImport(mContext, stopCallback, doBookmarks, doHistory).run();
- }
- }
- );
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/AppCompatPreferenceActivity.java b/mobile/android/base/java/org/mozilla/gecko/preferences/AppCompatPreferenceActivity.java
deleted file mode 100644
index fb4a8f751..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/AppCompatPreferenceActivity.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.mozilla.gecko.preferences;
-
-
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.preference.PreferenceActivity;
-import android.support.annotation.LayoutRes;
-import android.support.annotation.Nullable;
-import android.support.v7.app.ActionBar;
-import android.support.v7.app.AppCompatDelegate;
-import android.support.v7.widget.Toolbar;
-import android.view.MenuInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
- * to be used with AppCompat.
- *
- * This technique can be used with an {@link android.app.Activity} class, not just
- * {@link android.preference.PreferenceActivity}.
- *
- * This class was directly imported (without any modifications) from Android SDK examples, at:
- * https://android.googlesource.com/platform/development/+/master/samples/Support7Demos/src/com/example/android/supportv7/app/AppCompatPreferenceActivity.java
- */
-public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
- private AppCompatDelegate mDelegate;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- getDelegate().installViewFactory();
- getDelegate().onCreate(savedInstanceState);
- super.onCreate(savedInstanceState);
- }
- @Override
- protected void onPostCreate(Bundle savedInstanceState) {
- super.onPostCreate(savedInstanceState);
- getDelegate().onPostCreate(savedInstanceState);
- }
- public ActionBar getSupportActionBar() {
- return getDelegate().getSupportActionBar();
- }
- public void setSupportActionBar(@Nullable Toolbar toolbar) {
- getDelegate().setSupportActionBar(toolbar);
- }
- @Override
- public MenuInflater getMenuInflater() {
- return getDelegate().getMenuInflater();
- }
- @Override
- public void setContentView(@LayoutRes int layoutResID) {
- getDelegate().setContentView(layoutResID);
- }
- @Override
- public void setContentView(View view) {
- getDelegate().setContentView(view);
- }
- @Override
- public void setContentView(View view, ViewGroup.LayoutParams params) {
- getDelegate().setContentView(view, params);
- }
- @Override
- public void addContentView(View view, ViewGroup.LayoutParams params) {
- getDelegate().addContentView(view, params);
- }
- @Override
- protected void onPostResume() {
- super.onPostResume();
- getDelegate().onPostResume();
- }
- @Override
- protected void onTitleChanged(CharSequence title, int color) {
- super.onTitleChanged(title, color);
- getDelegate().setTitle(title);
- }
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- getDelegate().onConfigurationChanged(newConfig);
- }
- @Override
- protected void onStop() {
- super.onStop();
- getDelegate().onStop();
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- getDelegate().onDestroy();
- }
- public void invalidateOptionsMenu() {
- getDelegate().invalidateOptionsMenu();
- }
- private AppCompatDelegate getDelegate() {
- if (mDelegate == null) {
- mDelegate = AppCompatDelegate.create(this, null);
- }
- return mDelegate;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/ClearOnShutdownPref.java b/mobile/android/base/java/org/mozilla/gecko/preferences/ClearOnShutdownPref.java
deleted file mode 100644
index 5218cd06d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/ClearOnShutdownPref.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import java.util.HashSet;
-import java.util.Set;
-
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.util.PrefUtils;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.preference.Preference;
-
-public class ClearOnShutdownPref implements GeckoPreferences.PrefHandler {
- public static final String PREF = GeckoPreferences.NON_PREF_PREFIX + "history.clear_on_exit";
-
- @Override
- public boolean setupPref(Context context, Preference pref) {
- // The pref is initialized asynchronously. Read the pref explicitly
- // here to make sure we have the data.
- final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
- final Set<String> clearItems = PrefUtils.getStringSet(prefs, PREF, new HashSet<String>());
- ((ListCheckboxPreference) pref).setChecked(clearItems.size() > 0);
- return true;
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public void onChange(Context context, Preference pref, Object newValue) {
- final Set<String> vals = (Set<String>) newValue;
- ((ListCheckboxPreference) pref).setChecked(vals.size() > 0);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/CustomCheckBoxPreference.java b/mobile/android/base/java/org/mozilla/gecko/preferences/CustomCheckBoxPreference.java
deleted file mode 100644
index 2934ca88e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/CustomCheckBoxPreference.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import android.content.Context;
-import android.preference.CheckBoxPreference;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.TextView;
-
-/**
- * Represents a Checkbox element in a preference menu.
- * The title of the Checkbox can be larger than the view.
- * In this case, it will be displayed in 2 or more lines.
- * The default behavior of the class CheckBoxPreference
- * doesn't wrap the title.
- */
-
-public class CustomCheckBoxPreference extends CheckBoxPreference {
-
- public CustomCheckBoxPreference(Context context) {
- super(context);
- }
-
- public CustomCheckBoxPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- public CustomCheckBoxPreference(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- protected void onBindView(View view) {
- super.onBindView(view);
- final TextView title = (TextView) view.findViewById(android.R.id.title);
- if (title != null) {
- title.setSingleLine(false);
- title.setEllipsize(null);
- }
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/CustomListCategory.java b/mobile/android/base/java/org/mozilla/gecko/preferences/CustomListCategory.java
deleted file mode 100644
index ee5a46bef..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/CustomListCategory.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import android.content.Context;
-import android.preference.PreferenceCategory;
-import android.util.AttributeSet;
-
-public abstract class CustomListCategory extends PreferenceCategory {
- protected CustomListPreference mDefaultReference;
-
- public CustomListCategory(Context context) {
- super(context);
- }
-
- public CustomListCategory(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public CustomListCategory(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- protected void onAttachedToActivity() {
- super.onAttachedToActivity();
-
- setOrderingAsAdded(true);
- }
-
- /**
- * Set the default to some available list item. Used if the current default is removed or
- * disabled.
- */
- protected void setFallbackDefault() {
- if (getPreferenceCount() > 0) {
- CustomListPreference aItem = (CustomListPreference) getPreference(0);
- setDefault(aItem);
- }
- }
-
- /**
- * Removes the given item from the set of available list items.
- * This only updates the UI, so callers are responsible for persisting any state.
- *
- * @param item The given item to remove.
- */
- public void uninstall(CustomListPreference item) {
- removePreference(item);
- if (item == mDefaultReference) {
- // If the default is being deleted, set a new default.
- setFallbackDefault();
- }
- }
-
- /**
- * Sets the given item as the current default.
- * This only updates the UI, so callers are responsible for persisting any state.
- *
- * @param item The intended new default.
- */
- public void setDefault(CustomListPreference item) {
- if (mDefaultReference != null) {
- mDefaultReference.setIsDefault(false);
- }
-
- item.setIsDefault(true);
- mDefaultReference = item;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/CustomListPreference.java b/mobile/android/base/java/org/mozilla/gecko/preferences/CustomListPreference.java
deleted file mode 100644
index 8b7e0e7b3..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/CustomListPreference.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import org.mozilla.gecko.R;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.Resources;
-import android.preference.Preference;
-import android.view.View;
-import android.widget.TextView;
-
-/**
- * Represents an element in a <code>CustomListCategory</code> preference menu.
- * This preference con display a dialog when clicked, and also supports
- * being set as a default item within the preference list category.
- */
-
-public abstract class CustomListPreference extends Preference implements View.OnLongClickListener {
- protected String LOGTAG = "CustomListPreference";
-
- // Indices of the buttons of the Dialog.
- public static final int INDEX_SET_DEFAULT_BUTTON = 0;
-
- // Dialog item labels.
- private String[] mDialogItems;
-
- // Dialog displayed when this element is tapped.
- protected AlertDialog mDialog;
-
- // Cache label to avoid repeated use of the resource system.
- protected final String LABEL_IS_DEFAULT;
- protected final String LABEL_SET_AS_DEFAULT;
- protected final String LABEL_REMOVE;
-
- protected boolean mIsDefault;
-
- // Enclosing parent category that contains this preference.
- protected final CustomListCategory mParentCategory;
-
- /**
- * Create a preference object to represent a list preference that is attached to
- * a category.
- *
- * @param context The activity context we operate under.
- * @param parentCategory The PreferenceCategory this object exists within.
- */
- public CustomListPreference(Context context, CustomListCategory parentCategory) {
- super(context);
-
- mParentCategory = parentCategory;
- setLayoutResource(getPreferenceLayoutResource());
-
- setOnPreferenceClickListener(new OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference preference) {
- CustomListPreference sPref = (CustomListPreference) preference;
- sPref.showDialog();
- return true;
- }
- });
-
- Resources res = getContext().getResources();
-
- // Fetch these strings now, instead of every time we ever want to relabel a button.
- LABEL_IS_DEFAULT = res.getString(R.string.pref_default);
- LABEL_SET_AS_DEFAULT = res.getString(R.string.pref_dialog_set_default);
- LABEL_REMOVE = res.getString(R.string.pref_dialog_remove);
- }
-
- /**
- * Returns the Android resource id for the layout.
- */
- protected abstract int getPreferenceLayoutResource();
-
- /**
- * Set whether this object's UI should display this as the default item.
- * Note: This must be called from the UI thread because it touches the view hierarchy.
- *
- * To ensure proper ordering, this method should only be called after this Preference
- * is added to the PreferenceCategory.
- *
- * @param isDefault Flag indicating if this represents the default list item.
- */
- public void setIsDefault(boolean isDefault) {
- mIsDefault = isDefault;
- if (isDefault) {
- setOrder(0);
- setSummary(LABEL_IS_DEFAULT);
- } else {
- setOrder(1);
- setSummary("");
- }
- }
-
- private String[] getCachedDialogItems() {
- if (mDialogItems == null) {
- mDialogItems = createDialogItems();
- }
- return mDialogItems;
- }
-
- /**
- * Returns the strings to be displayed in the dialog.
- */
- abstract protected String[] createDialogItems();
-
- /**
- * Display a dialog for this preference, when the preference is clicked.
- */
- public void showDialog() {
- final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
- builder.setTitle(getTitle().toString());
- builder.setItems(getCachedDialogItems(), new DialogInterface.OnClickListener() {
- // Forward relevant events to the container class for handling.
- @Override
- public void onClick(DialogInterface dialog, int indexClicked) {
- hideDialog();
- onDialogIndexClicked(indexClicked);
- }
- });
-
- configureDialogBuilder(builder);
-
- // We have to construct the dialog itself on the UI thread.
- mDialog = builder.create();
- mDialog.setOnShowListener(new DialogInterface.OnShowListener() {
- // Called when the dialog is shown (so we're finally able to manipulate button enabledness).
- @Override
- public void onShow(DialogInterface dialog) {
- configureShownDialog();
- }
- });
- mDialog.show();
- }
-
- /**
- * (Optional) Configure the AlertDialog builder.
- */
- protected void configureDialogBuilder(AlertDialog.Builder builder) {
- return;
- }
-
- abstract protected void onDialogIndexClicked(int index);
-
- /**
- * Disables buttons in the shown AlertDialog as required. The button elements are not created
- * until after show is called, so this method has to be called from the onShowListener above.
- * @see this.showDialog
- */
- protected void configureShownDialog() {
- // If this is already the default list item, disable the button for setting this as the default.
- final TextView defaultButton = (TextView) mDialog.getListView().getChildAt(INDEX_SET_DEFAULT_BUTTON);
- if (mIsDefault) {
- defaultButton.setEnabled(false);
-
- // Failure to unregister this listener leads to tapping the button dismissing the dialog
- // without doing anything.
- defaultButton.setOnClickListener(null);
- }
- }
-
- /**
- * Hide the dialog we previously created, if any.
- */
- public void hideDialog() {
- if (mDialog != null && mDialog.isShowing()) {
- mDialog.dismiss();
- }
- }
-
- @Override
- public boolean onLongClick(View view) {
- // Show the preference dialog on long-press.
- showDialog();
- return true;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/DistroSharedPrefsImport.java b/mobile/android/base/java/org/mozilla/gecko/preferences/DistroSharedPrefsImport.java
deleted file mode 100644
index 1e235640e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/DistroSharedPrefsImport.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.distribution.Distribution;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.util.Log;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.Iterator;
-
-public class DistroSharedPrefsImport {
-
- public static final String LOGTAG = DistroSharedPrefsImport.class.getSimpleName();
-
- public static void importPreferences(final Context context, final Distribution distribution) {
- if (distribution == null) {
- return;
- }
-
- final JSONObject preferences = distribution.getAndroidPreferences();
- if (preferences.length() == 0) {
- return;
- }
-
- final Iterator<?> keys = preferences.keys();
- final SharedPreferences.Editor sharedPreferences = GeckoSharedPrefs.forProfile(context).edit();
-
- while (keys.hasNext()) {
- final String key = (String) keys.next();
- final Object value;
- try {
- value = preferences.get(key);
- } catch (JSONException e) {
- Log.e(LOGTAG, "Unable to completely process Android Preferences JSON.", e);
- continue;
- }
-
- // We currently don't support Float preferences.
- if (value instanceof String) {
- sharedPreferences.putString(GeckoPreferences.NON_PREF_PREFIX + key, (String) value);
- } else if (value instanceof Boolean) {
- sharedPreferences.putBoolean(GeckoPreferences.NON_PREF_PREFIX + key, (boolean) value);
- } else if (value instanceof Integer) {
- sharedPreferences.putInt(GeckoPreferences.NON_PREF_PREFIX + key, (int) value);
- } else if (value instanceof Long) {
- sharedPreferences.putLong(GeckoPreferences.NON_PREF_PREFIX + key, (long) value);
- } else {
- Log.d(LOGTAG, "Unknown preference value type whilst importing android preferences from distro file.");
- }
- }
- sharedPreferences.apply();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/FontSizePreference.java b/mobile/android/base/java/org/mozilla/gecko/preferences/FontSizePreference.java
deleted file mode 100644
index c77c2cc23..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/FontSizePreference.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import org.mozilla.gecko.R;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.preference.DialogPreference;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.ScrollView;
-import android.widget.TextView;
-
-import java.util.HashMap;
-
-class FontSizePreference extends DialogPreference {
- private static final String LOGTAG = "FontSizePreference";
- private static final int TWIP_TO_PT_RATIO = 20; // 20 twip = 1 point.
- private static final int PREVIEW_FONT_SIZE_UNIT = TypedValue.COMPLEX_UNIT_PT;
- private static final int DEFAULT_FONT_INDEX = 2;
-
- private final Context mContext;
- /** Container for mPreviewFontView to allow for scrollable padding at the top of the view. */
- private ScrollView mScrollingContainer;
- private TextView mPreviewFontView;
- private Button mIncreaseFontButton;
- private Button mDecreaseFontButton;
-
- private final String[] mFontTwipValues;
- private final String[] mFontSizeNames; // Ex: "Small".
- /** Index into the above arrays for the saved preference value (from Gecko). */
- private int mSavedFontIndex = DEFAULT_FONT_INDEX;
- /** Index into the above arrays for the currently displayed font size (the preview). */
- private int mPreviewFontIndex = mSavedFontIndex;
- private final HashMap<String, Integer> mFontTwipToIndexMap;
-
- public FontSizePreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
-
- final Resources res = mContext.getResources();
- mFontTwipValues = res.getStringArray(R.array.pref_font_size_values);
- mFontSizeNames = res.getStringArray(R.array.pref_font_size_entries);
- mFontTwipToIndexMap = new HashMap<String, Integer>();
- for (int i = 0; i < mFontTwipValues.length; ++i) {
- mFontTwipToIndexMap.put(mFontTwipValues[i], i);
- }
- }
-
- @Override
- protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
- final LayoutInflater inflater =
- (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- View dialogView = inflater.inflate(R.layout.font_size_preference, null);
- initInternalViews(dialogView);
- updatePreviewFontSize(mFontTwipValues[mPreviewFontIndex]);
-
- builder.setTitle(null);
- builder.setView(dialogView);
- }
-
- /** Saves relevant views to instance variables and initializes their settings. */
- private void initInternalViews(View dialogView) {
- mScrollingContainer = (ScrollView) dialogView.findViewById(R.id.scrolling_container);
- // Background cannot be set in XML (see bug 783597 - TODO: Change this to XML when bug is fixed).
- mScrollingContainer.setBackgroundColor(Color.WHITE);
- mPreviewFontView = (TextView) dialogView.findViewById(R.id.preview);
-
- mDecreaseFontButton = (Button) dialogView.findViewById(R.id.decrease_preview_font_button);
- mIncreaseFontButton = (Button) dialogView.findViewById(R.id.increase_preview_font_button);
- setButtonState(mPreviewFontIndex);
- mDecreaseFontButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mPreviewFontIndex = Math.max(mPreviewFontIndex - 1, 0);
- updatePreviewFontSize(mFontTwipValues[mPreviewFontIndex]);
- mIncreaseFontButton.setEnabled(true);
- // If we reached the minimum index, disable the button.
- if (mPreviewFontIndex == 0) {
- mDecreaseFontButton.setEnabled(false);
- }
- }
- });
- mIncreaseFontButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mPreviewFontIndex = Math.min(mPreviewFontIndex + 1, mFontTwipValues.length - 1);
- updatePreviewFontSize(mFontTwipValues[mPreviewFontIndex]);
-
- mDecreaseFontButton.setEnabled(true);
- // If we reached the maximum index, disable the button.
- if (mPreviewFontIndex == mFontTwipValues.length - 1) {
- mIncreaseFontButton.setEnabled(false);
- }
- }
- });
- }
-
- @Override
- protected void onDialogClosed(boolean positiveResult) {
- super.onDialogClosed(positiveResult);
- if (!positiveResult) {
- mPreviewFontIndex = mSavedFontIndex;
- return;
- }
- mSavedFontIndex = mPreviewFontIndex;
- final String twipVal = mFontTwipValues[mSavedFontIndex];
- final OnPreferenceChangeListener prefChangeListener = getOnPreferenceChangeListener();
- if (prefChangeListener == null) {
- Log.e(LOGTAG, "PreferenceChangeListener is null. FontSizePreference will not be saved to Gecko.");
- return;
- }
- prefChangeListener.onPreferenceChange(this, twipVal);
- }
-
- /**
- * Finds the index of the given twip value and sets it as the saved preference value. Also the
- * current preview text size to the given value. Does not update the mPreviewFontView text size.
- */
- protected void setSavedFontSize(String twip) {
- final Integer index = mFontTwipToIndexMap.get(twip);
- if (index != null) {
- mSavedFontIndex = index;
- mPreviewFontIndex = mSavedFontIndex;
- return;
- }
- resetSavedFontSizeToDefault();
- Log.e(LOGTAG, "setSavedFontSize: Given font size does not exist in twip values map. Reverted to default font size.");
- }
-
- /**
- * Updates the mPreviewFontView to the given text size, resets the container's scroll to the top
- * left, and invalidates the view. Does not update the font indices.
- */
- private void updatePreviewFontSize(String twip) {
- float pt = convertTwipStrToPT(twip);
- // Android will not render a font size of 0 pt but for Gecko, 0 twip turns off font
- // inflation. Thus we special case 0 twip to display a renderable font size.
- if (pt == 0) {
- // Android adds an inexplicable extra margin on the smallest font size so to get around
- // this, we reinflate the view.
- ViewGroup parentView = (ViewGroup) mScrollingContainer.getParent();
- parentView.removeAllViews();
- final LayoutInflater inflater =
- (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- View dialogView = inflater.inflate(R.layout.font_size_preference, parentView);
- initInternalViews(dialogView);
- mPreviewFontView.setTextSize(PREVIEW_FONT_SIZE_UNIT, 1);
- } else {
- mPreviewFontView.setTextSize(PREVIEW_FONT_SIZE_UNIT, pt);
- }
- mScrollingContainer.scrollTo(0, 0);
- }
-
- /**
- * Resets the font indices to the default value. Does not update the mPreviewFontView text size.
- */
- private void resetSavedFontSizeToDefault() {
- mSavedFontIndex = DEFAULT_FONT_INDEX;
- mPreviewFontIndex = mSavedFontIndex;
- }
-
- private void setButtonState(int index) {
- if (index == 0) {
- mDecreaseFontButton.setEnabled(false);
- } else if (index == mFontTwipValues.length - 1) {
- mIncreaseFontButton.setEnabled(false);
- }
- }
-
- /**
- * Returns the name of the font size (ex: "Small") at the currently saved preference value.
- */
- protected String getSavedFontSizeName() {
- return mFontSizeNames[mSavedFontIndex];
- }
-
- private float convertTwipStrToPT(String twip) {
- return Float.parseFloat(twip) / TWIP_TO_PT_RATIO;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferenceFragment.java b/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferenceFragment.java
deleted file mode 100644
index 6be9e6ea5..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferenceFragment.java
+++ /dev/null
@@ -1,296 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import java.util.Locale;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.BrowserLocaleManager;
-import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.LocaleManager;
-import org.mozilla.gecko.PrefsHelper;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.TelemetryContract.Method;
-import org.mozilla.gecko.fxa.AccountLoader;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-
-import android.accounts.Account;
-import android.app.ActionBar;
-import android.app.Activity;
-import android.app.LoaderManager;
-import android.content.Context;
-import android.content.Loader;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.preference.PreferenceActivity;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceScreen;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuInflater;
-
-import com.squareup.leakcanary.RefWatcher;
-
-/* A simple implementation of PreferenceFragment for large screen devices
- * This will strip category headers (so that they aren't shown to the user twice)
- * as well as initializing Gecko prefs when a fragment is shown.
-*/
-public class GeckoPreferenceFragment extends PreferenceFragment {
-
- public static final int ACCOUNT_LOADER_ID = 1;
- private AccountLoaderCallbacks accountLoaderCallbacks;
- private SyncPreference syncPreference;
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- Log.d(LOGTAG, "onConfigurationChanged: " + newConfig.locale);
-
- final Activity context = getActivity();
-
- final LocaleManager localeManager = BrowserLocaleManager.getInstance();
- final Locale changed = localeManager.onSystemConfigurationChanged(context, getResources(), newConfig, lastLocale);
- if (changed != null) {
- applyLocale(changed);
- }
- }
-
- private static final String LOGTAG = "GeckoPreferenceFragment";
- private PrefsHelper.PrefHandler mPrefsRequest;
- private Locale lastLocale = Locale.getDefault();
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // Write prefs to our custom GeckoSharedPrefs file.
- getPreferenceManager().setSharedPreferencesName(GeckoSharedPrefs.APP_PREFS_NAME);
-
- int res = getResource();
- if (res == R.xml.preferences) {
- Telemetry.startUISession(TelemetryContract.Session.SETTINGS);
- } else {
- final String resourceName = getArguments().getString("resource");
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, Method.SETTINGS, resourceName);
- }
-
- // Display a menu for Search preferences.
- if (res == R.xml.preferences_search) {
- setHasOptionsMenu(true);
- }
-
- addPreferencesFromResource(res);
-
- PreferenceScreen screen = getPreferenceScreen();
- setPreferenceScreen(screen);
- mPrefsRequest = ((GeckoPreferences)getActivity()).setupPreferences(screen);
- syncPreference = (SyncPreference) findPreference(GeckoPreferences.PREFS_SYNC);
- }
-
- /**
- * Return the title to use for this preference fragment.
- *
- * We only return titles for the preference screens that are
- * launched directly, and thus might need to be redisplayed.
- *
- * This method sets the title that you see on non-multi-pane devices.
- */
- private String getTitle() {
- final int res = getResource();
- if (res == R.xml.preferences) {
- return getString(R.string.settings_title);
- }
-
- // We can launch this category from the Data Reporting notification.
- if (res == R.xml.preferences_privacy) {
- return getString(R.string.pref_category_privacy_short);
- }
-
- // We can launch this category from the the magnifying glass in the quick search bar.
- if (res == R.xml.preferences_search) {
- return getString(R.string.pref_category_search);
- }
-
- // Launched as action from content notifications.
- if (res == R.xml.preferences_notifications) {
- return getString(R.string.pref_category_notifications);
- }
-
- return null;
- }
-
- /**
- * Return the header id for this preference fragment. This allows
- * us to select the correct header when launching a preference
- * screen directly.
- *
- * We only return titles for the preference screens that are
- * launched directly.
- */
- private int getHeader() {
- final int res = getResource();
- if (res == R.xml.preferences) {
- return R.id.pref_header_general;
- }
-
- // We can launch this category from the Data Reporting notification.
- if (res == R.xml.preferences_privacy) {
- return R.id.pref_header_privacy;
- }
-
- // We can launch this category from the the magnifying glass in the quick search bar.
- if (res == R.xml.preferences_search) {
- return R.id.pref_header_search;
- }
-
- // Launched as action from content notifications.
- if (res == R.xml.preferences_notifications) {
- return R.id.pref_header_notifications;
- }
-
- return -1;
- }
-
- private void updateTitle() {
- final String newTitle = getTitle();
- if (newTitle == null) {
- Log.d(LOGTAG, "No new title to show.");
- return;
- }
-
- final GeckoPreferences activity = (GeckoPreferences) getActivity();
- if (activity.isMultiPane()) {
- // In a multi-pane activity, the title is "Settings", and the action
- // bar is along the top of the screen. We don't want to change those.
- activity.showBreadCrumbs(newTitle, newTitle);
- activity.switchToHeader(getHeader());
- return;
- }
-
- Log.v(LOGTAG, "Setting activity title to " + newTitle);
- activity.setTitle(newTitle);
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- accountLoaderCallbacks = new AccountLoaderCallbacks();
- getLoaderManager().initLoader(ACCOUNT_LOADER_ID, null, accountLoaderCallbacks);
- }
-
- @Override
- public void onResume() {
- // This is a little delicate. Ensure that you do nothing prior to
- // super.onResume that you wouldn't do in onCreate.
- applyLocale(Locale.getDefault());
- super.onResume();
-
- // Force reload as the account may have been deleted while the app was in background.
- getLoaderManager().restartLoader(ACCOUNT_LOADER_ID, null, accountLoaderCallbacks);
- }
-
- private void applyLocale(final Locale currentLocale) {
- final Context context = getActivity().getApplicationContext();
-
- BrowserLocaleManager.getInstance().updateConfiguration(context, currentLocale);
-
- if (!currentLocale.equals(lastLocale)) {
- // Locales differ. Let's redisplay.
- Log.d(LOGTAG, "Locale changed: " + currentLocale);
- this.lastLocale = currentLocale;
-
- // Rebuild the list to reflect the current locale.
- getPreferenceScreen().removeAll();
- addPreferencesFromResource(getResource());
- }
-
- // Fix the parent title regardless.
- updateTitle();
- }
-
- /*
- * Get the resource from Fragment arguments and return it.
- *
- * If no resource can be found, return the resource id of the default preference screen.
- */
- private int getResource() {
- int resid = 0;
-
- final String resourceName = getArguments().getString("resource");
- final Activity activity = getActivity();
-
- if (resourceName != null) {
- // Fetch resource id by resource name.
- final Resources resources = activity.getResources();
- final String packageName = activity.getPackageName();
- resid = resources.getIdentifier(resourceName, "xml", packageName);
- }
-
- if (resid == 0) {
- // The resource was invalid. Use the default resource.
- Log.e(LOGTAG, "Failed to find resource: " + resourceName + ". Displaying default settings.");
-
- boolean isMultiPane = ((GeckoPreferences) activity).isMultiPane();
- resid = isMultiPane ? R.xml.preferences_general_tablet : R.xml.preferences;
- }
-
- return resid;
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- inflater.inflate(R.menu.preferences_search_menu, menu);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (mPrefsRequest != null) {
- PrefsHelper.removeObserver(mPrefsRequest);
- mPrefsRequest = null;
- }
-
- final int res = getResource();
- if (res == R.xml.preferences) {
- Telemetry.stopUISession(TelemetryContract.Session.SETTINGS);
- }
-
- GeckoApplication.watchReference(getActivity(), this);
- }
-
- private class AccountLoaderCallbacks implements LoaderManager.LoaderCallbacks<Account> {
- @Override
- public Loader<Account> onCreateLoader(int id, Bundle args) {
- return new AccountLoader(getActivity());
- }
-
- @Override
- public void onLoadFinished(Loader<Account> loader, Account account) {
- if (syncPreference == null) {
- return;
- }
-
- if (account == null) {
- syncPreference.update(null);
- return;
- }
-
- syncPreference.update(new AndroidFxAccount(getActivity(), account));
- }
-
- @Override
- public void onLoaderReset(Loader<Account> loader) {
- if (syncPreference != null) {
- syncPreference.update(null);
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java b/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
deleted file mode 100644
index aab5be2de..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/GeckoPreferences.java
+++ /dev/null
@@ -1,1514 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import org.json.JSONArray;
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.AdjustConstants;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.BrowserLocaleManager;
-import org.mozilla.gecko.DataReportingNotification;
-import org.mozilla.gecko.DynamicToolbar;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoActivityStatus;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.LocaleManager;
-import org.mozilla.gecko.Locales;
-import org.mozilla.gecko.PrefsHelper;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.SnackbarBuilder;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.TelemetryContract.Method;
-import org.mozilla.gecko.activitystream.ActivityStream;
-import org.mozilla.gecko.background.common.GlobalConstants;
-import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
-import org.mozilla.gecko.feeds.FeedService;
-import org.mozilla.gecko.feeds.action.CheckForUpdatesAction;
-import org.mozilla.gecko.permissions.Permissions;
-import org.mozilla.gecko.restrictions.Restrictable;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.tabqueue.TabQueueHelper;
-import org.mozilla.gecko.tabqueue.TabQueuePrompt;
-import org.mozilla.gecko.updater.UpdateService;
-import org.mozilla.gecko.updater.UpdateServiceHelper;
-import org.mozilla.gecko.util.ContextUtils;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.InputOptionsUtils;
-import org.mozilla.gecko.util.NativeEventListener;
-import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.annotation.TargetApi;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.NotificationManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.res.Configuration;
-import android.Manifest;
-import android.os.Build;
-import android.os.Bundle;
-import android.preference.CheckBoxPreference;
-import android.preference.EditTextPreference;
-import android.preference.ListPreference;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceChangeListener;
-import android.preference.Preference.OnPreferenceClickListener;
-import android.preference.PreferenceActivity;
-import android.preference.PreferenceGroup;
-import android.preference.SwitchPreference;
-import android.preference.TwoStatePreference;
-import android.support.design.widget.Snackbar;
-import android.support.design.widget.TextInputLayout;
-import android.support.v4.content.LocalBroadcastManager;
-import android.support.v7.app.ActionBar;
-import android.text.Editable;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.util.Log;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.ListAdapter;
-import android.widget.ListView;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
-public class GeckoPreferences
-extends AppCompatPreferenceActivity
-implements
-GeckoActivityStatus,
-NativeEventListener,
-OnPreferenceChangeListener,
-OnSharedPreferenceChangeListener
-{
- private static final String LOGTAG = "GeckoPreferences";
-
- // We have a white background, which makes transitions on
- // some devices look bad. Don't use transitions on those
- // devices.
- private static final boolean NO_TRANSITIONS = HardwareUtils.IS_KINDLE_DEVICE;
- private static final int NO_SUCH_ID = 0;
-
- public static final String NON_PREF_PREFIX = "android.not_a_preference.";
- public static final String INTENT_EXTRA_RESOURCES = "resource";
- public static final String PREFS_TRACKING_PROTECTION_PROMPT_SHOWN = NON_PREF_PREFIX + "trackingProtectionPromptShown";
- public static String PREFS_HEALTHREPORT_UPLOAD_ENABLED = NON_PREF_PREFIX + "healthreport.uploadEnabled";
- public static final String PREFS_SYNC = NON_PREF_PREFIX + "sync";
-
- private static boolean sIsCharEncodingEnabled;
- private boolean mInitialized;
- private PrefsHelper.PrefHandler mPrefsRequest;
- private List<Header> mHeaders;
-
- // These match keys in resources/xml*/preferences*.xml
- private static final String PREFS_SEARCH_RESTORE_DEFAULTS = NON_PREF_PREFIX + "search.restore_defaults";
- private static final String PREFS_DATA_REPORTING_PREFERENCES = NON_PREF_PREFIX + "datareporting.preferences";
- private static final String PREFS_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
- private static final String PREFS_CRASHREPORTER_ENABLED = "datareporting.crashreporter.submitEnabled";
- private static final String PREFS_MENU_CHAR_ENCODING = "browser.menu.showCharacterEncoding";
- private static final String PREFS_MP_ENABLED = "privacy.masterpassword.enabled";
- private static final String PREFS_UPDATER_AUTODOWNLOAD = "app.update.autodownload";
- private static final String PREFS_UPDATER_URL = "app.update.url.android";
- private static final String PREFS_GEO_REPORTING = NON_PREF_PREFIX + "app.geo.reportdata";
- private static final String PREFS_GEO_LEARN_MORE = NON_PREF_PREFIX + "geo.learn_more";
- private static final String PREFS_HEALTHREPORT_LINK = NON_PREF_PREFIX + "healthreport.link";
- private static final String PREFS_DEVTOOLS_REMOTE_USB_ENABLED = "devtools.remote.usb.enabled";
- private static final String PREFS_DEVTOOLS_REMOTE_WIFI_ENABLED = "devtools.remote.wifi.enabled";
- private static final String PREFS_DEVTOOLS_REMOTE_LINK = NON_PREF_PREFIX + "remote_debugging.link";
- private static final String PREFS_TRACKING_PROTECTION = "privacy.trackingprotection.state";
- private static final String PREFS_TRACKING_PROTECTION_PB = "privacy.trackingprotection.pbmode.enabled";
- private static final String PREFS_ZOOMED_VIEW_ENABLED = "ui.zoomedview.enabled";
- public static final String PREFS_VOICE_INPUT_ENABLED = NON_PREF_PREFIX + "voice_input_enabled";
- public static final String PREFS_QRCODE_ENABLED = NON_PREF_PREFIX + "qrcode_enabled";
- private static final String PREFS_TRACKING_PROTECTION_PRIVATE_BROWSING = "privacy.trackingprotection.pbmode.enabled";
- private static final String PREFS_TRACKING_PROTECTION_LEARN_MORE = NON_PREF_PREFIX + "trackingprotection.learn_more";
- private static final String PREFS_CLEAR_PRIVATE_DATA = NON_PREF_PREFIX + "privacy.clear";
- private static final String PREFS_CLEAR_PRIVATE_DATA_EXIT = NON_PREF_PREFIX + "history.clear_on_exit";
- private static final String PREFS_SCREEN_ADVANCED = NON_PREF_PREFIX + "advanced_screen";
- public static final String PREFS_HOMEPAGE = NON_PREF_PREFIX + "homepage";
- public static final String PREFS_HOMEPAGE_PARTNER_COPY = GeckoPreferences.PREFS_HOMEPAGE + ".partner";
- public static final String PREFS_HISTORY_SAVED_SEARCH = NON_PREF_PREFIX + "search.search_history.enabled";
- private static final String PREFS_FAQ_LINK = NON_PREF_PREFIX + "faq.link";
- private static final String PREFS_FEEDBACK_LINK = NON_PREF_PREFIX + "feedback.link";
- public static final String PREFS_NOTIFICATIONS_CONTENT = NON_PREF_PREFIX + "notifications.content";
- public static final String PREFS_NOTIFICATIONS_CONTENT_LEARN_MORE = NON_PREF_PREFIX + "notifications.content.learn_more";
- public static final String PREFS_NOTIFICATIONS_WHATS_NEW = NON_PREF_PREFIX + "notifications.whats_new";
- public static final String PREFS_APP_UPDATE_LAST_BUILD_ID = "app.update.last_build_id";
- public static final String PREFS_READ_PARTNER_CUSTOMIZATIONS_PROVIDER = NON_PREF_PREFIX + "distribution.read_partner_customizations_provider";
- public static final String PREFS_READ_PARTNER_BOOKMARKS_PROVIDER = NON_PREF_PREFIX + "distribution.read_partner_bookmarks_provider";
- public static final String PREFS_CUSTOM_TABS = NON_PREF_PREFIX + "customtabs";
- public static final String PREFS_ACTIVITY_STREAM = NON_PREF_PREFIX + "activitystream";
- public static final String PREFS_CATEGORY_EXPERIMENTAL_FEATURES = NON_PREF_PREFIX + "category_experimental";
-
- private static final String ACTION_STUMBLER_UPLOAD_PREF = "STUMBLER_PREF";
-
-
- // This isn't a Gecko pref, even if it looks like one.
- private static final String PREFS_BROWSER_LOCALE = "locale";
-
- public static final String PREFS_RESTORE_SESSION = NON_PREF_PREFIX + "restoreSession3";
- public static final String PREFS_RESTORE_SESSION_FROM_CRASH = "browser.sessionstore.resume_from_crash";
- public static final String PREFS_RESTORE_SESSION_MAX_CRASH_RESUMES = "browser.sessionstore.max_resumed_crashes";
- public static final String PREFS_TAB_QUEUE = NON_PREF_PREFIX + "tab_queue";
- public static final String PREFS_TAB_QUEUE_LAST_SITE = NON_PREF_PREFIX + "last_site";
- public static final String PREFS_TAB_QUEUE_LAST_TIME = NON_PREF_PREFIX + "last_time";
-
- private static final String PREFS_DYNAMIC_TOOLBAR = "browser.chrome.dynamictoolbar";
-
- // These values are chosen to be distinct from other Activity constants.
- private static final int REQUEST_CODE_PREF_SCREEN = 5;
- private static final int RESULT_CODE_EXIT_SETTINGS = 6;
-
- // Result code used when a locale preference changes.
- // Callers can recognize this code to refresh themselves to
- // accommodate a locale change.
- public static final int RESULT_CODE_LOCALE_DID_CHANGE = 7;
-
- private static final int REQUEST_CODE_TAB_QUEUE = 8;
-
- private final Map<String, PrefHandler> HANDLERS;
- {
- final HashMap<String, PrefHandler> tempHandlers = new HashMap<>(2);
- tempHandlers.put(ClearOnShutdownPref.PREF, new ClearOnShutdownPref());
- tempHandlers.put(AndroidImportPreference.PREF_KEY, new AndroidImportPreference.Handler());
- HANDLERS = Collections.unmodifiableMap(tempHandlers);
- }
-
- private SwitchPreference tabQueuePreference;
-
- /**
- * Track the last locale so we know whether to redisplay.
- */
- private Locale lastLocale = Locale.getDefault();
- private boolean localeSwitchingIsEnabled;
-
- private void startActivityForResultChoosingTransition(final Intent intent, final int requestCode) {
- startActivityForResult(intent, requestCode);
- if (NO_TRANSITIONS) {
- overridePendingTransition(0, 0);
- }
- }
-
- private void finishChoosingTransition() {
- finish();
- if (NO_TRANSITIONS) {
- overridePendingTransition(0, 0);
- }
- }
- private void updateActionBarTitle(int title) {
- final String newTitle = getString(title);
- if (newTitle != null) {
- Log.v(LOGTAG, "Setting action bar title to " + newTitle);
-
- setTitle(newTitle);
- }
- }
-
- /**
- * We only call this method for pre-HC versions of Android.
- */
- private void updateTitleForPrefsResource(int res) {
- // At present we only need to do this for non-leaf prefs views
- // and the locale switcher itself.
- int title = -1;
- if (res == R.xml.preferences) {
- title = R.string.settings_title;
- } else if (res == R.xml.preferences_locale) {
- title = R.string.pref_category_language;
- } else if (res == R.xml.preferences_vendor) {
- title = R.string.pref_category_vendor;
- } else if (res == R.xml.preferences_general) {
- title = R.string.pref_category_general;
- } else if (res == R.xml.preferences_search) {
- title = R.string.pref_category_search;
- }
- if (title != -1) {
- setTitle(title);
- }
- }
-
- private void onLocaleChanged(Locale newLocale) {
- Log.d(LOGTAG, "onLocaleChanged: " + newLocale);
-
- BrowserLocaleManager.getInstance().updateConfiguration(getApplicationContext(), newLocale);
- this.lastLocale = newLocale;
-
- if (isMultiPane()) {
- // This takes care of the left pane.
- invalidateHeaders();
-
- // Detach and reattach the current prefs pane so that it
- // reflects the new locale.
- final FragmentManager fragmentManager = getFragmentManager();
- int id = getResources().getIdentifier("android:id/prefs", null, null);
- final Fragment current = fragmentManager.findFragmentById(id);
- if (current != null) {
- fragmentManager.beginTransaction()
- .disallowAddToBackStack()
- .detach(current)
- .attach(current)
- .commitAllowingStateLoss();
- } else {
- Log.e(LOGTAG, "No prefs fragment to reattach!");
- }
-
- // Because Android just rebuilt the activity itself with the
- // old language, we need to update the top title and other
- // wording again.
- if (onIsMultiPane()) {
- updateActionBarTitle(R.string.settings_title);
- }
-
- // Update the title to for the preference pane that we're currently showing.
- final int titleId = getIntent().getExtras().getInt(PreferenceActivity.EXTRA_SHOW_FRAGMENT_TITLE);
- if (titleId != NO_SUCH_ID) {
- setTitle(titleId);
- } else {
- throw new IllegalStateException("Title id not found in intent bundle extras");
- }
-
- // Don't finish the activity -- we just reloaded all of the
- // individual parts! -- but when it returns, make sure that the
- // caller knows the locale changed.
- setResult(RESULT_CODE_LOCALE_DID_CHANGE);
- return;
- }
-
- refreshSuggestedSites();
-
- // Cause the current fragment to redisplay, the hard way.
- // This avoids nonsense with trying to reach inside fragments and force them
- // to redisplay themselves.
- // We also don't need to update the title.
- final Intent intent = (Intent) getIntent().clone();
- intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
- startActivityForResultChoosingTransition(intent, REQUEST_CODE_PREF_SCREEN);
-
- setResult(RESULT_CODE_LOCALE_DID_CHANGE);
- finishChoosingTransition();
- }
-
- private void checkLocale() {
- final Locale currentLocale = Locale.getDefault();
- Log.v(LOGTAG, "Checking locale: " + currentLocale + " vs " + lastLocale);
- if (currentLocale.equals(lastLocale)) {
- return;
- }
-
- onLocaleChanged(currentLocale);
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- // Apply the current user-selected locale, if necessary.
- checkLocale();
-
- // Track this so we can decide whether to show locale options.
- // See also the workaround below for Bug 1015209.
- localeSwitchingIsEnabled = BrowserLocaleManager.getInstance().isEnabled();
-
- // For Android v11+ where we use Fragments (v11+ only due to bug 866352),
- // check that PreferenceActivity.EXTRA_SHOW_FRAGMENT has been set
- // (or set it) before super.onCreate() is called so Android can display
- // the correct Fragment resource.
- // Note: this seems to only be required for non-multipane devices, multipane
- // manages to automatically select the correct fragments.
- if (!getIntent().hasExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT)) {
- // Set up the default fragment if there is no explicit fragment to show.
- setupTopLevelFragmentIntent();
- }
-
- // We must call this before setTitle to avoid crashes. Most devices don't seem to care
- // (we used to call onCreate later), however the ASUS TF300T (running 4.2) crashes
- // with an NPE in android.support.v7.app.AppCompatDelegateImplV7.ensureSubDecor(), and it's
- // likely other strange devices (other Asus devices, some Samsungs) could do the same.
- super.onCreate(savedInstanceState);
-
- if (onIsMultiPane()) {
- // So that Android doesn't put the fragment title (or nothing at
- // all) in the action bar.
- updateActionBarTitle(R.string.settings_title);
-
- if (Build.VERSION.SDK_INT < 13) {
- // Affected by Bug 1015209 -- no detach/attach.
- // If we try rejigging fragments, we'll crash, so don't
- // enable locale switching at all.
- localeSwitchingIsEnabled = false;
- throw new IllegalStateException("foobar");
- }
- }
-
- // Use setResourceToOpen to specify these extras.
- Bundle intentExtras = getIntent().getExtras();
-
- EventDispatcher.getInstance().registerGeckoThreadListener(this,
- "Sanitize:Finished",
- "Snackbar:Show");
-
- // Add handling for long-press click.
- // This is only for Android 3.0 and below (which use the long-press-context-menu paradigm).
- final ListView mListView = getListView();
- mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
- // Call long-click handler if it the item implements it.
- final ListAdapter listAdapter = ((ListView) parent).getAdapter();
- final Object listItem = listAdapter.getItem(position);
-
- // Only CustomListPreference handles long clicks.
- if (listItem instanceof CustomListPreference && listItem instanceof View.OnLongClickListener) {
- final View.OnLongClickListener longClickListener = (View.OnLongClickListener) listItem;
- return longClickListener.onLongClick(view);
- }
- return false;
- }
- });
-
- // N.B., if we ever need to redisplay the locale selection UI without
- // just finishing and recreating the activity, right here we'll need to
- // capture EXTRA_SHOW_FRAGMENT_TITLE from the intent and store the title ID.
-
- // If launched from notification, explicitly cancel the notification.
- if (intentExtras != null && intentExtras.containsKey(DataReportingNotification.ALERT_NAME_DATAREPORTING_NOTIFICATION)) {
- Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH, Method.NOTIFICATION, "settings-data-choices");
- NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
- notificationManager.cancel(DataReportingNotification.ALERT_NAME_DATAREPORTING_NOTIFICATION.hashCode());
- }
-
- // Launched from "Notifications settings" action button in a notification.
- if (intentExtras != null && intentExtras.containsKey(CheckForUpdatesAction.EXTRA_CONTENT_NOTIFICATION)) {
- Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(this));
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, Method.BUTTON, "notification-settings");
- Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, FeedService.getEnabledExperiment(this));
- }
- }
-
- /**
- * Set intent to display top-level settings fragment,
- * and show the correct title.
- */
- private void setupTopLevelFragmentIntent() {
- Intent intent = getIntent();
- // Check intent to determine settings screen to display.
- Bundle intentExtras = intent.getExtras();
- Bundle fragmentArgs = new Bundle();
- // Add resource argument to fragment if it exists.
- if (intentExtras != null && intentExtras.containsKey(INTENT_EXTRA_RESOURCES)) {
- String resourceName = intentExtras.getString(INTENT_EXTRA_RESOURCES);
- fragmentArgs.putString(INTENT_EXTRA_RESOURCES, resourceName);
- } else {
- // Use top-level settings screen.
- if (!onIsMultiPane()) {
- fragmentArgs.putString(INTENT_EXTRA_RESOURCES, "preferences");
- } else {
- fragmentArgs.putString(INTENT_EXTRA_RESOURCES, "preferences_general_tablet");
- }
- }
-
- // Build fragment intent.
- intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, GeckoPreferenceFragment.class.getName());
- intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
- // Used to get fragment title when locale changes (see onLocaleChanged method above)
- intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_TITLE, R.string.settings_title);
- }
-
- @Override
- public boolean isValidFragment(String fragmentName) {
- return GeckoPreferenceFragment.class.getName().equals(fragmentName);
- }
-
- @TargetApi(11)
- @Override
- public void onBuildHeaders(List<Header> target) {
- if (onIsMultiPane()) {
- loadHeadersFromResource(R.xml.preference_headers, target);
-
- Iterator<Header> iterator = target.iterator();
-
- while (iterator.hasNext()) {
- Header header = iterator.next();
-
- if (header.id == R.id.pref_header_advanced && !Restrictions.isAllowed(this, Restrictable.ADVANCED_SETTINGS)) {
- iterator.remove();
- } else if (header.id == R.id.pref_header_clear_private_data
- && !Restrictions.isAllowed(this, Restrictable.CLEAR_HISTORY)) {
- iterator.remove();
- }
- }
-
- mHeaders = target;
- }
- }
-
- @TargetApi(11)
- public void switchToHeader(int id) {
- if (mHeaders == null) {
- // Can't switch to a header if there are no headers!
- return;
- }
-
- for (Header header : mHeaders) {
- if (header.id == id) {
- switchToHeader(header);
- return;
- }
- }
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
- if (!hasFocus || mInitialized)
- return;
-
- mInitialized = true;
- }
-
- @Override
- public void onBackPressed() {
- super.onBackPressed();
-
- if (NO_TRANSITIONS) {
- overridePendingTransition(0, 0);
- }
-
- Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, Method.BACK, "settings");
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
-
- EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
- "Sanitize:Finished",
- "Snackbar:Show");
-
- if (mPrefsRequest != null) {
- PrefsHelper.removeObserver(mPrefsRequest);
- mPrefsRequest = null;
- }
- }
-
- @Override
- public void onPause() {
- // Symmetric with onResume.
- if (isMultiPane()) {
- SharedPreferences prefs = GeckoSharedPrefs.forApp(this);
- prefs.unregisterOnSharedPreferenceChangeListener(this);
- }
-
- super.onPause();
-
- if (getApplication() instanceof GeckoApplication) {
- ((GeckoApplication) getApplication()).onActivityPause(this);
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- if (getApplication() instanceof GeckoApplication) {
- ((GeckoApplication) getApplication()).onActivityResume(this);
- }
-
- // Watch prefs, otherwise we don't reliably get told when they change.
- // See documentation for onSharedPreferenceChange for more.
- // Inexplicably only needed on tablet.
- if (isMultiPane()) {
- SharedPreferences prefs = GeckoSharedPrefs.forApp(this);
- prefs.registerOnSharedPreferenceChangeListener(this);
- }
- }
-
- @Override
- public void startActivity(Intent intent) {
- // For settings, we want to be able to pass results up the chain
- // of preference screens so Settings can behave as a single unit.
- // Specifically, when we open a link, we want to back out of all
- // the settings screens.
- // We need to start nested PreferenceScreens withStartActivityForResult().
- // Android doesn't let us do that (see Preference.onClick), so we're overriding here.
- startActivityForResultChoosingTransition(intent, REQUEST_CODE_PREF_SCREEN);
- }
-
- @Override
- public void startWithFragment(String fragmentName, Bundle args,
- Fragment resultTo, int resultRequestCode, int titleRes, int shortTitleRes) {
- Log.v(LOGTAG, "Starting with fragment: " + fragmentName + ", title " + titleRes);
-
- // Overriding because we want to use startActivityForResult for Fragment intents.
- Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes);
- if (resultTo == null) {
- startActivityForResultChoosingTransition(intent, REQUEST_CODE_PREF_SCREEN);
- } else {
- resultTo.startActivityForResult(intent, resultRequestCode);
- if (NO_TRANSITIONS) {
- overridePendingTransition(0, 0);
- }
- }
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- // We might have just returned from a settings activity that allows us
- // to switch locales, so reflect any change that occurred.
- checkLocale();
-
- switch (requestCode) {
- case REQUEST_CODE_PREF_SCREEN:
- switch (resultCode) {
- case RESULT_CODE_EXIT_SETTINGS:
- updateActionBarTitle(R.string.settings_title);
-
- // Pass this result up to the parent activity.
- setResult(RESULT_CODE_EXIT_SETTINGS);
- finishChoosingTransition();
- break;
- }
- break;
- case REQUEST_CODE_TAB_QUEUE:
- if (TabQueueHelper.processTabQueuePromptResponse(resultCode, this)) {
- tabQueuePreference.setChecked(true);
- }
- break;
- }
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
- Permissions.onRequestPermissionsResult(this, permissions, grantResults);
- }
-
- @Override
- public void handleMessage(final String event, final NativeJSObject message, final EventCallback callback) {
- try {
- switch (event) {
- case "Sanitize:Finished":
- boolean success = message.getBoolean("success");
- final int stringRes = success ? R.string.private_data_success : R.string.private_data_fail;
-
- SnackbarBuilder.builder(GeckoPreferences.this)
- .message(stringRes)
- .duration(Snackbar.LENGTH_LONG)
- .buildAndShow();
- break;
- case "Snackbar:Show":
- SnackbarBuilder.builder(this)
- .fromEvent(message)
- .callback(callback)
- .buildAndShow();
- break;
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
- }
- }
-
- /**
- * Initialize all of the preferences (native of Gecko ones) for this screen.
- *
- * @param prefs The android.preference.PreferenceGroup to initialize
- * @return The integer id for the PrefsHelper.PrefHandlerBase listener added
- * to monitor changes to Gecko prefs.
- */
- public PrefsHelper.PrefHandler setupPreferences(PreferenceGroup prefs) {
- ArrayList<String> list = new ArrayList<String>();
- setupPreferences(prefs, list);
- return getGeckoPreferences(prefs, list);
- }
-
- /**
- * Recursively loop through a PreferenceGroup. Initialize native Android prefs,
- * and build a list of Gecko preferences in the passed in prefs array
- *
- * @param preferences The android.preference.PreferenceGroup to initialize
- * @param prefs An ArrayList to fill with Gecko preferences that need to be
- * initialized
- * @return The integer id for the PrefsHelper.PrefHandlerBase listener added
- * to monitor changes to Gecko prefs.
- */
- private void setupPreferences(PreferenceGroup preferences, ArrayList<String> prefs) {
- for (int i = 0; i < preferences.getPreferenceCount(); i++) {
- final Preference pref = preferences.getPreference(i);
-
- // Eliminate locale switching if necessary.
- // This logic will need to be extended when
- // content language selection (Bug 881510) is implemented.
- if (!localeSwitchingIsEnabled &&
- "preferences_locale".equals(pref.getExtras().getString("resource"))) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
-
- String key = pref.getKey();
- if (pref instanceof PreferenceGroup) {
- // If datareporting is disabled, remove UI.
- if (PREFS_DATA_REPORTING_PREFERENCES.equals(key)) {
- if (!AppConstants.MOZ_DATA_REPORTING || !Restrictions.isAllowed(this, Restrictable.DATA_CHOICES)) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_SCREEN_ADVANCED.equals(key) &&
- !Restrictions.isAllowed(this, Restrictable.ADVANCED_SETTINGS)) {
- preferences.removePreference(pref);
- i--;
- continue;
- } else if (PREFS_CATEGORY_EXPERIMENTAL_FEATURES.equals(key)
- && !AppConstants.MOZ_ANDROID_ACTIVITY_STREAM
- && !AppConstants.MOZ_ANDROID_CUSTOM_TABS) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- setupPreferences((PreferenceGroup) pref, prefs);
- } else {
- if (HANDLERS.containsKey(key)) {
- PrefHandler handler = HANDLERS.get(key);
- if (!handler.setupPref(this, pref)) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- }
-
- pref.setOnPreferenceChangeListener(this);
- if (PREFS_UPDATER_AUTODOWNLOAD.equals(key)) {
- if (!AppConstants.MOZ_UPDATER || ContextUtils.isInstalledFromGooglePlay(this)) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_TRACKING_PROTECTION.equals(key)) {
- // Remove UI for global TP pref in non-Nightly builds.
- if (!AppConstants.NIGHTLY_BUILD) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_TRACKING_PROTECTION_PB.equals(key)) {
- // Remove UI for private-browsing-only TP pref in Nightly builds.
- if (AppConstants.NIGHTLY_BUILD) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_TELEMETRY_ENABLED.equals(key)) {
- if (!AppConstants.MOZ_TELEMETRY_REPORTING || !Restrictions.isAllowed(this, Restrictable.DATA_CHOICES)) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_HEALTHREPORT_UPLOAD_ENABLED.equals(key) ||
- PREFS_HEALTHREPORT_LINK.equals(key)) {
- if (!AppConstants.MOZ_SERVICES_HEALTHREPORT || !Restrictions.isAllowed(this, Restrictable.DATA_CHOICES)) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_GEO_REPORTING.equals(key) ||
- PREFS_GEO_LEARN_MORE.equals(key)) {
- if (!AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED || !Restrictions.isAllowed(this, Restrictable.DATA_CHOICES)) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_DEVTOOLS_REMOTE_USB_ENABLED.equals(key)) {
- if (!Restrictions.isAllowed(this, Restrictable.REMOTE_DEBUGGING)) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_DEVTOOLS_REMOTE_WIFI_ENABLED.equals(key)) {
- if (!Restrictions.isAllowed(this, Restrictable.REMOTE_DEBUGGING)) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- if (!InputOptionsUtils.supportsQrCodeReader(getApplicationContext())) {
- // WiFi debugging requires a QR code reader
- pref.setEnabled(false);
- pref.setSummary(getString(R.string.pref_developer_remotedebugging_wifi_disabled_summary));
- continue;
- }
- } else if (PREFS_DEVTOOLS_REMOTE_LINK.equals(key)) {
- // Remove the "Learn more" link if remote debugging is disabled
- if (!Restrictions.isAllowed(this, Restrictable.REMOTE_DEBUGGING)) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_RESTORE_SESSION.equals(key) ||
- PREFS_BROWSER_LOCALE.equals(key)) {
- // Set the summary string to the current entry. The summary
- // for other list prefs will be set in the PrefsHelper
- // callback, but since this pref doesn't live in Gecko, we
- // need to handle it separately.
- ListPreference listPref = (ListPreference) pref;
- CharSequence selectedEntry = listPref.getEntry();
- listPref.setSummary(selectedEntry);
- continue;
- } else if (PREFS_SYNC.equals(key)) {
- // Don't show sync prefs while in guest mode.
- if (!Restrictions.isAllowed(this, Restrictable.MODIFY_ACCOUNTS)) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_SEARCH_RESTORE_DEFAULTS.equals(key)) {
- pref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference preference) {
- GeckoPreferences.this.restoreDefaultSearchEngines();
- Telemetry.sendUIEvent(TelemetryContract.Event.SEARCH_RESTORE_DEFAULTS, Method.LIST_ITEM);
- return true;
- }
- });
- } else if (PREFS_TAB_QUEUE.equals(key)) {
- tabQueuePreference = (SwitchPreference) pref;
- // Only show tab queue pref on nightly builds with the tab queue build flag.
- if (!TabQueueHelper.TAB_QUEUE_ENABLED) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_ZOOMED_VIEW_ENABLED.equals(key)) {
- if (!AppConstants.NIGHTLY_BUILD) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_VOICE_INPUT_ENABLED.equals(key)) {
- if (!InputOptionsUtils.supportsVoiceRecognizer(getApplicationContext(), getResources().getString(R.string.voicesearch_prompt))) {
- // Remove UI for voice input on non nightly builds.
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_QRCODE_ENABLED.equals(key)) {
- if (!InputOptionsUtils.supportsQrCodeReader(getApplicationContext())) {
- // Remove UI for qr code input on non nightly builds
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_TRACKING_PROTECTION_PRIVATE_BROWSING.equals(key)) {
- if (!Restrictions.isAllowed(this, Restrictable.PRIVATE_BROWSING)) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_TRACKING_PROTECTION_LEARN_MORE.equals(key)) {
- if (!Restrictions.isAllowed(this, Restrictable.PRIVATE_BROWSING)) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_MP_ENABLED.equals(key)) {
- if (!Restrictions.isAllowed(this, Restrictable.MASTER_PASSWORD)) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_CLEAR_PRIVATE_DATA.equals(key) || PREFS_CLEAR_PRIVATE_DATA_EXIT.equals(key)) {
- if (!Restrictions.isAllowed(this, Restrictable.CLEAR_HISTORY)) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_HOMEPAGE.equals(key)) {
- String setUrl = GeckoSharedPrefs.forProfile(getBaseContext()).getString(PREFS_HOMEPAGE, AboutPages.HOME);
- setHomePageSummary(pref, setUrl);
- pref.setOnPreferenceChangeListener(this);
- } else if (PREFS_FAQ_LINK.equals(key)) {
- // Format the FAQ link
- final String VERSION = AppConstants.MOZ_APP_VERSION;
- final String OS = AppConstants.OS_TARGET;
- final String LOCALE = Locales.getLanguageTag(Locale.getDefault());
-
- final String url = getResources().getString(R.string.faq_link, VERSION, OS, LOCALE);
- ((LinkPreference) pref).setUrl(url);
- } else if (PREFS_FEEDBACK_LINK.equals(key)) {
- // Format the feedback link. We can't easily use this "app.feedbackURL"
- // Gecko preference because the URL must be formatted.
- final String url = getResources().getString(R.string.feedback_link, AppConstants.MOZ_APP_VERSION, AppConstants.MOZ_UPDATE_CHANNEL);
- ((LinkPreference) pref).setUrl(url);
- } else if (PREFS_DYNAMIC_TOOLBAR.equals(key)) {
- if (DynamicToolbar.isForceDisabled()) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_NOTIFICATIONS_CONTENT.equals(key) ||
- PREFS_NOTIFICATIONS_CONTENT_LEARN_MORE.equals(key)) {
- if (!FeedService.isInExperiment(this)) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
- } else if (PREFS_CUSTOM_TABS.equals(key) && !AppConstants.MOZ_ANDROID_CUSTOM_TABS) {
- preferences.removePreference(pref);
- i--;
- continue;
- } else if (PREFS_ACTIVITY_STREAM.equals(key) && !ActivityStream.isUserEligible(this)) {
- preferences.removePreference(pref);
- i--;
- continue;
- }
-
- // Some Preference UI elements are not actually preferences,
- // but they require a key to work correctly. For example,
- // "Clear private data" requires a key for its state to be
- // saved when the orientation changes. It uses the
- // "android.not_a_preference.privacy.clear" key - which doesn't
- // exist in Gecko - to satisfy this requirement.
- if (isGeckoPref(key)) {
- prefs.add(key);
- }
- }
- }
- }
-
- private void setHomePageSummary(Preference pref, String value) {
- if (!TextUtils.isEmpty(value)) {
- pref.setSummary(value);
- } else {
- pref.setSummary(AboutPages.HOME);
- }
- }
-
- private boolean isGeckoPref(String key) {
- if (TextUtils.isEmpty(key)) {
- return false;
- }
-
- if (key.startsWith(NON_PREF_PREFIX)) {
- return false;
- }
-
- if (key.equals(PREFS_BROWSER_LOCALE)) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Restore default search engines in Gecko and retrigger a search engine refresh.
- */
- protected void restoreDefaultSearchEngines() {
- GeckoAppShell.notifyObservers("SearchEngines:RestoreDefaults", null);
-
- // Send message to Gecko to get engines. SearchPreferenceCategory listens for the response.
- GeckoAppShell.notifyObservers("SearchEngines:GetVisible", null);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- int itemId = item.getItemId();
- switch (itemId) {
- case android.R.id.home:
- finishChoosingTransition();
- return true;
- }
-
- // Generated R.id.* apparently aren't constant expressions, so they can't be switched.
- if (itemId == R.id.restore_defaults) {
- restoreDefaultSearchEngines();
- Telemetry.sendUIEvent(TelemetryContract.Event.SEARCH_RESTORE_DEFAULTS, Method.MENU);
- return true;
- }
-
- return super.onOptionsItemSelected(item);
- }
-
- final private int DIALOG_CREATE_MASTER_PASSWORD = 0;
- final private int DIALOG_REMOVE_MASTER_PASSWORD = 1;
-
- public static void setCharEncodingState(boolean enabled) {
- sIsCharEncodingEnabled = enabled;
- }
-
- public static boolean getCharEncodingState() {
- return sIsCharEncodingEnabled;
- }
-
- public static void broadcastAction(final Context context, final Intent intent) {
- fillIntentWithProfileInfo(context, intent);
- LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
- }
-
- private static void fillIntentWithProfileInfo(final Context context, final Intent intent) {
- // There is a race here, but GeckoProfile returns the default profile
- // when Gecko is not explicitly running for a different profile. In a
- // multi-profile world, this will need to be updated (possibly to
- // broadcast settings for all profiles). See Bug 882182.
- GeckoProfile profile = GeckoProfile.get(context);
- if (profile != null) {
- intent.putExtra("profileName", profile.getName())
- .putExtra("profilePath", profile.getDir().getAbsolutePath());
- }
- }
-
- /**
- * Broadcast the provided value as the value of the
- * <code>PREFS_GEO_REPORTING</code> pref.
- */
- public static void broadcastStumblerPref(final Context context, final boolean value) {
- Intent intent = new Intent(ACTION_STUMBLER_UPLOAD_PREF)
- .putExtra("pref", PREFS_GEO_REPORTING)
- .putExtra("branch", GeckoSharedPrefs.APP_PREFS_NAME)
- .putExtra("enabled", value)
- .putExtra("moz_mozilla_api_key", AppConstants.MOZ_MOZILLA_API_KEY);
- if (GeckoAppShell.getGeckoInterface() != null) {
- intent.putExtra("user_agent", GeckoAppShell.getGeckoInterface().getDefaultUAString());
- }
- broadcastAction(context, intent);
- }
-
- /**
- * Broadcast the current value of the
- * <code>PREFS_GEO_REPORTING</code> pref.
- */
- public static void broadcastStumblerPref(final Context context) {
- final boolean value = getBooleanPref(context, PREFS_GEO_REPORTING, false);
- broadcastStumblerPref(context, value);
- }
-
- /**
- * Return the value of the named preference in the default preferences file.
- *
- * This corresponds to the storage that backs preferences.xml.
- * @param context a <code>Context</code>; the
- * <code>PreferenceActivity</code> will suffice, but this
- * method is intended to be called from other contexts
- * within the application, not just this <code>Activity</code>.
- * @param name the name of the preference to retrieve.
- * @param def the default value to return if the preference is not present.
- * @return the value of the preference, or the default.
- */
- public static boolean getBooleanPref(final Context context, final String name, boolean def) {
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
- return prefs.getBoolean(name, def);
- }
-
- /**
- * Immediately handle the user's selection of a browser locale.
- *
- * Earlier locale-handling code did this with centralized logic in
- * GeckoApp, delegating to LocaleManager for persistence and refreshing
- * the activity as necessary.
- *
- * We no longer handle this by sending a message to GeckoApp, for
- * several reasons:
- *
- * * GeckoApp might not be running. Activities don't always stick around.
- * A Java bridge message might not be handled.
- * * We need to adapt the preferences UI to the locale ourselves.
- * * The user might not hit Back (or Up) -- they might hit Home and never
- * come back.
- *
- * We handle the case of the user returning to the browser via the
- * onActivityResult mechanism: see {@link BrowserApp#onActivityResult(int, int, Intent)}.
- */
- private boolean onLocaleSelected(final String currentLocale, final String newValue) {
- final Context context = getApplicationContext();
-
- // LocaleManager operations need to occur on the background thread.
- // ... but activity operations need to occur on the UI thread. So we
- // have nested runnables.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- final LocaleManager localeManager = BrowserLocaleManager.getInstance();
-
- if (TextUtils.isEmpty(newValue)) {
- BrowserLocaleManager.getInstance().resetToSystemLocale(context);
- Telemetry.sendUIEvent(TelemetryContract.Event.LOCALE_BROWSER_RESET);
- } else {
- if (null == localeManager.setSelectedLocale(context, newValue)) {
- localeManager.updateConfiguration(context, Locale.getDefault());
- }
- Telemetry.sendUIEvent(TelemetryContract.Event.LOCALE_BROWSER_UNSELECTED, Method.NONE,
- currentLocale == null ? "unknown" : currentLocale);
- Telemetry.sendUIEvent(TelemetryContract.Event.LOCALE_BROWSER_SELECTED, Method.NONE, newValue);
- }
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- onLocaleChanged(Locale.getDefault());
- }
- });
- }
- });
-
- return true;
- }
-
- private void refreshSuggestedSites() {
- final ContentResolver cr = getApplicationContext().getContentResolver();
-
- // This will force all active suggested sites cursors
- // to request a refresh (e.g. cursor loaders).
- cr.notifyChange(SuggestedSites.CONTENT_URI, null);
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
-
- Log.d(LOGTAG, "onConfigurationChanged: " + newConfig.locale);
-
- if (lastLocale.equals(newConfig.locale)) {
- Log.d(LOGTAG, "Old locale same as new locale. Short-circuiting.");
- return;
- }
-
- final LocaleManager localeManager = BrowserLocaleManager.getInstance();
- final Locale changed = localeManager.onSystemConfigurationChanged(this, getResources(), newConfig, lastLocale);
- if (changed != null) {
- onLocaleChanged(changed);
- }
- }
-
- /**
- * Implementation for the {@link OnSharedPreferenceChangeListener} interface,
- * which we use to watch changes in our prefs file.
- *
- * This is reliably called whenever the pref changes, which is not the case
- * for multiple consecutive changes in the case of onPreferenceChange.
- *
- * Note that this listener is not always registered: we use it only on
- * tablets, Honeycomb and up, where we'll have a multi-pane view and prefs
- * changing multiple times.
- */
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- if (PREFS_BROWSER_LOCALE.equals(key)) {
- onLocaleSelected(Locales.getLanguageTag(lastLocale),
- sharedPreferences.getString(key, null));
- }
- }
-
- public interface PrefHandler {
- // Allows the pref to do any initialization it needs. Return false to have the pref removed
- // from the prefs screen entirely.
- public boolean setupPref(Context context, Preference pref);
- public void onChange(Context context, Preference pref, Object newValue);
- }
-
- private void recordSettingChangeTelemetry(String prefName, Object newValue) {
- final String value;
- if (newValue instanceof Boolean) {
- value = (Boolean) newValue ? "1" : "0";
- } else if (prefName.equals(PREFS_HOMEPAGE)) {
- // Don't record the user's homepage preference.
- value = "*";
- } else {
- value = newValue.toString();
- }
-
- final JSONArray extras = new JSONArray();
- extras.put(prefName);
- extras.put(value);
- Telemetry.sendUIEvent(TelemetryContract.Event.EDIT, Method.SETTINGS, extras.toString());
- }
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- final String prefName = preference.getKey();
- Log.i(LOGTAG, "Changed " + prefName + " = " + newValue);
- recordSettingChangeTelemetry(prefName, newValue);
-
- if (PREFS_MP_ENABLED.equals(prefName)) {
- showDialog((Boolean) newValue ? DIALOG_CREATE_MASTER_PASSWORD : DIALOG_REMOVE_MASTER_PASSWORD);
-
- // We don't want the "use master password" pref to change until the
- // user has gone through the dialog.
- return false;
- }
-
- if (PREFS_HOMEPAGE.equals(prefName)) {
- setHomePageSummary(preference, String.valueOf(newValue));
- }
-
- if (PREFS_BROWSER_LOCALE.equals(prefName)) {
- // Even though this is a list preference, we don't want to handle it
- // below, so we return here.
- return onLocaleSelected(Locales.getLanguageTag(lastLocale), (String) newValue);
- }
-
- if (PREFS_MENU_CHAR_ENCODING.equals(prefName)) {
- setCharEncodingState(((String) newValue).equals("true"));
- } else if (PREFS_UPDATER_AUTODOWNLOAD.equals(prefName)) {
- UpdateServiceHelper.setAutoDownloadPolicy(this, UpdateService.AutoDownloadPolicy.get((String) newValue));
- } else if (PREFS_UPDATER_URL.equals(prefName)) {
- UpdateServiceHelper.setUpdateUrl(this, (String) newValue);
- } else if (PREFS_HEALTHREPORT_UPLOAD_ENABLED.equals(prefName)) {
- final Boolean newBooleanValue = (Boolean) newValue;
- AdjustConstants.getAdjustHelper().setEnabled(newBooleanValue);
- } else if (PREFS_GEO_REPORTING.equals(prefName)) {
- if ((Boolean) newValue) {
- enableStumbler((CheckBoxPreference) preference);
- return false;
- } else {
- broadcastStumblerPref(GeckoPreferences.this, false);
- return true;
- }
- } else if (PREFS_TAB_QUEUE.equals(prefName)) {
- if ((Boolean) newValue && !TabQueueHelper.canDrawOverlays(this)) {
- Intent promptIntent = new Intent(this, TabQueuePrompt.class);
- startActivityForResult(promptIntent, REQUEST_CODE_TAB_QUEUE);
- return false;
- }
- } else if (PREFS_NOTIFICATIONS_CONTENT.equals(prefName)) {
- FeedService.setup(this);
- } else if (PREFS_ACTIVITY_STREAM.equals(prefName)) {
- ThreadUtils.postDelayedToUiThread(new Runnable() {
- @Override
- public void run() {
- GeckoAppShell.scheduleRestart();
- }
- }, 1000);
- } else if (HANDLERS.containsKey(prefName)) {
- PrefHandler handler = HANDLERS.get(prefName);
- handler.onChange(this, preference, newValue);
- }
-
- // Send Gecko-side pref changes to Gecko
- if (isGeckoPref(prefName)) {
- PrefsHelper.setPref(prefName, newValue, true /* flush */);
- }
-
- if (preference instanceof ListPreference) {
- // We need to find the entry for the new value
- int newIndex = ((ListPreference) preference).findIndexOfValue((String) newValue);
- CharSequence newEntry = ((ListPreference) preference).getEntries()[newIndex];
- ((ListPreference) preference).setSummary(newEntry);
- } else if (preference instanceof LinkPreference) {
- setResult(RESULT_CODE_EXIT_SETTINGS);
- finishChoosingTransition();
- } else if (preference instanceof FontSizePreference) {
- final FontSizePreference fontSizePref = (FontSizePreference) preference;
- fontSizePref.setSummary(fontSizePref.getSavedFontSizeName());
- }
-
- return true;
- }
-
- private void enableStumbler(final CheckBoxPreference preference) {
- Permissions
- .from(this)
- .withPermissions(Manifest.permission.ACCESS_FINE_LOCATION)
- .onUIThread()
- .andFallback(new Runnable() {
- @Override
- public void run() {
- preference.setChecked(false);
- }
- })
- .run(new Runnable() {
- @Override
- public void run() {
- preference.setChecked(true);
- broadcastStumblerPref(GeckoPreferences.this, true);
- }
- });
- }
-
- private TextInputLayout getTextBox(int aHintText) {
- final EditText input = new EditText(this);
- int inputtype = InputType.TYPE_CLASS_TEXT;
- inputtype |= InputType.TYPE_TEXT_VARIATION_PASSWORD | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
- input.setInputType(inputtype);
-
- input.setHint(aHintText);
-
- final TextInputLayout layout = new TextInputLayout(this);
- layout.addView(input);
-
- return layout;
- }
-
- private class PasswordTextWatcher implements TextWatcher {
- EditText input1;
- EditText input2;
- AlertDialog dialog;
-
- PasswordTextWatcher(EditText aInput1, EditText aInput2, AlertDialog aDialog) {
- input1 = aInput1;
- input2 = aInput2;
- dialog = aDialog;
- }
-
- @Override
- public void afterTextChanged(Editable s) {
- if (dialog == null)
- return;
-
- String text1 = input1.getText().toString();
- String text2 = input2.getText().toString();
- boolean disabled = TextUtils.isEmpty(text1) || TextUtils.isEmpty(text2) || !text1.equals(text2);
- dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(!disabled);
- }
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) { }
- }
-
- private class EmptyTextWatcher implements TextWatcher {
- EditText input;
- AlertDialog dialog;
-
- EmptyTextWatcher(EditText aInput, AlertDialog aDialog) {
- input = aInput;
- dialog = aDialog;
- }
-
- @Override
- public void afterTextChanged(Editable s) {
- if (dialog == null)
- return;
-
- String text = input.getText().toString();
- boolean disabled = TextUtils.isEmpty(text);
- dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(!disabled);
- }
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) { }
- }
-
- @Override
- protected Dialog onCreateDialog(int id) {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- LinearLayout linearLayout = new LinearLayout(this);
- linearLayout.setOrientation(LinearLayout.VERTICAL);
- AlertDialog dialog;
- switch (id) {
- case DIALOG_CREATE_MASTER_PASSWORD:
- final TextInputLayout inputLayout1 = getTextBox(R.string.masterpassword_password);
- final TextInputLayout inputLayout2 = getTextBox(R.string.masterpassword_confirm);
- linearLayout.addView(inputLayout1);
- linearLayout.addView(inputLayout2);
-
- final EditText input1 = inputLayout1.getEditText();
- final EditText input2 = inputLayout2.getEditText();
-
- builder.setTitle(R.string.masterpassword_create_title)
- .setView((View) linearLayout)
- .setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- PrefsHelper.setPref(PREFS_MP_ENABLED,
- input1.getText().toString(),
- /* flush */ true);
- }
- })
- .setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- return;
- }
- });
- dialog = builder.create();
- dialog.setOnShowListener(new DialogInterface.OnShowListener() {
- @Override
- public void onShow(DialogInterface dialog) {
- input1.setText("");
- input2.setText("");
- input1.requestFocus();
- }
- });
-
- PasswordTextWatcher watcher = new PasswordTextWatcher(input1, input2, dialog);
- input1.addTextChangedListener((TextWatcher) watcher);
- input2.addTextChangedListener((TextWatcher) watcher);
-
- break;
- case DIALOG_REMOVE_MASTER_PASSWORD:
- final TextInputLayout inputLayout = getTextBox(R.string.masterpassword_password);
- linearLayout.addView(inputLayout);
- final EditText input = inputLayout.getEditText();
-
- builder.setTitle(R.string.masterpassword_remove_title)
- .setView((View) linearLayout)
- .setPositiveButton(R.string.button_ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- PrefsHelper.setPref(PREFS_MP_ENABLED, input.getText().toString());
- }
- })
- .setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- return;
- }
- });
- dialog = builder.create();
- dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
- @Override
- public void onDismiss(DialogInterface dialog) {
- input.setText("");
- }
- });
- dialog.setOnShowListener(new DialogInterface.OnShowListener() {
- @Override
- public void onShow(DialogInterface dialog) {
- input.setText("");
- }
- });
- input.addTextChangedListener(new EmptyTextWatcher(input, dialog));
- break;
- default:
- return null;
- }
-
- return dialog;
- }
-
- // Initialize preferences by requesting the preference values from Gecko
- private static class PrefCallbacks extends PrefsHelper.PrefHandlerBase {
- private final PreferenceGroup screen;
-
- public PrefCallbacks(final PreferenceGroup screen) {
- this.screen = screen;
- }
-
- private Preference getField(String prefName) {
- return screen.findPreference(prefName);
- }
-
- @Override
- public void prefValue(String prefName, final boolean value) {
- final TwoStatePreference pref = (TwoStatePreference) getField(prefName);
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- if (pref.isChecked() != value) {
- pref.setChecked(value);
- }
- }
- });
- }
-
- @Override
- public void prefValue(String prefName, final String value) {
- final Preference pref = getField(prefName);
- if (pref instanceof EditTextPreference) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- ((EditTextPreference) pref).setText(value);
- }
- });
- } else if (pref instanceof ListPreference) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- ((ListPreference) pref).setValue(value);
- // Set the summary string to the current entry
- CharSequence selectedEntry = ((ListPreference) pref).getEntry();
- ((ListPreference) pref).setSummary(selectedEntry);
- }
- });
- } else if (pref instanceof FontSizePreference) {
- final FontSizePreference fontSizePref = (FontSizePreference) pref;
- fontSizePref.setSavedFontSize(value);
- final String fontSizeName = fontSizePref.getSavedFontSizeName();
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- fontSizePref.setSummary(fontSizeName); // Ex: "Small".
- }
- });
- }
- }
-
- @Override
- public void prefValue(String prefName, final int value) {
- final Preference pref = getField(prefName);
- Log.w(LOGTAG, "Unhandled int value for pref [" + pref + "]");
- }
-
- @Override
- public void finish() {
- // enable all preferences once we have them from gecko
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- screen.setEnabled(true);
- }
- });
- }
- }
-
- private PrefsHelper.PrefHandler getGeckoPreferences(final PreferenceGroup screen,
- ArrayList<String> prefs) {
- final PrefsHelper.PrefHandler prefHandler = new PrefCallbacks(screen);
- final String[] prefNames = prefs.toArray(new String[prefs.size()]);
- PrefsHelper.addObserver(prefNames, prefHandler);
- return prefHandler;
- }
-
- @Override
- public boolean isGeckoActivityOpened() {
- return false;
- }
-
- /**
- * Given an Intent instance, add extras to specify which settings section to
- * open.
- *
- * resource should be a valid Android XML resource identifier.
- *
- * The mechanism to open a section differs based on Android version.
- */
- public static void setResourceToOpen(final Intent intent, final String resource) {
- if (intent == null) {
- throw new IllegalArgumentException("intent must not be null");
- }
- if (resource == null) {
- return;
- }
-
- intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, GeckoPreferenceFragment.class.getName());
-
- Bundle fragmentArgs = new Bundle();
- fragmentArgs.putString("resource", resource);
- intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/LinkPreference.java b/mobile/android/base/java/org/mozilla/gecko/preferences/LinkPreference.java
deleted file mode 100644
index 774f78c53..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/LinkPreference.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import org.mozilla.gecko.Tabs;
-
-import android.content.Context;
-import android.preference.Preference;
-import android.util.AttributeSet;
-
-class LinkPreference extends Preference {
- private String mUrl;
-
- public LinkPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- mUrl = attrs.getAttributeValue(null, "url");
- }
- public LinkPreference(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mUrl = attrs.getAttributeValue(null, "url");
- }
-
- public void setUrl(String url) {
- mUrl = url;
- }
-
- @Override
- protected void onClick() {
- Tabs.getInstance().loadUrlInTab(mUrl);
- callChangeListener(mUrl);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/ListCheckboxPreference.java b/mobile/android/base/java/org/mozilla/gecko/preferences/ListCheckboxPreference.java
deleted file mode 100644
index f56ea58b9..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/ListCheckboxPreference.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.Checkable;
-
-import org.mozilla.gecko.R;
-
-/**
- * This preference shows a checkbox on its left hand side, but will show a menu when clicked.
- * Its used for preferences like "Clear on Exit" that have a boolean on-off state, but that represent
- * multiple boolean options inside.
- **/
-class ListCheckboxPreference extends MultiChoicePreference implements Checkable {
- private static final String LOGTAG = "GeckoListCheckboxPreference";
- private boolean checked;
-
- public ListCheckboxPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- setWidgetLayoutResource(R.layout.preference_checkbox);
- }
-
- @Override
- public boolean isChecked() {
- return checked;
- }
-
- @Override
- protected void onBindView(View view) {
- super.onBindView(view);
-
- View checkboxView = view.findViewById(R.id.checkbox);
- if (checkboxView instanceof Checkable) {
- ((Checkable) checkboxView).setChecked(checked);
- }
- }
-
- @Override
- public void setChecked(boolean checked) {
- boolean changed = checked != this.checked;
- this.checked = checked;
- if (changed) {
- notifyDependencyChange(shouldDisableDependents());
- notifyChanged();
- }
- }
-
- @Override
- public void toggle() {
- checked = !checked;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/LocaleListPreference.java b/mobile/android/base/java/org/mozilla/gecko/preferences/LocaleListPreference.java
deleted file mode 100644
index c962a3d19..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/LocaleListPreference.java
+++ /dev/null
@@ -1,316 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import java.nio.ByteBuffer;
-import java.text.Collator;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Set;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.BrowserLocaleManager;
-import org.mozilla.gecko.Locales;
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.preference.ListPreference;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-
-public class LocaleListPreference extends ListPreference {
- private static final String LOG_TAG = "GeckoLocaleList";
-
- /**
- * With thanks to <http://stackoverflow.com/a/22679283/22003> for the
- * initial solution.
- *
- * This class encapsulates an approach to checking whether a script
- * is usable on a device. We attempt to draw a character from the
- * script (e.g., ব). If the fonts on the device don't have the correct
- * glyph, Android typically renders whitespace (rather than .notdef).
- *
- * Pass in part of the name of the locale in its local representation,
- * and a whitespace character; this class performs the graphical comparison.
- *
- * See Bug 1023451 Comment 24 for extensive explanation.
- */
- private static class CharacterValidator {
- private static final int BITMAP_WIDTH = 32;
- private static final int BITMAP_HEIGHT = 48;
-
- private final Paint paint = new Paint();
- private final byte[] missingCharacter;
-
- public CharacterValidator(String missing) {
- this.missingCharacter = getPixels(drawBitmap(missing));
- }
-
- private Bitmap drawBitmap(String text) {
- Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ALPHA_8);
- Canvas c = new Canvas(b);
- c.drawText(text, 0, BITMAP_HEIGHT / 2, this.paint);
- return b;
- }
-
- private static byte[] getPixels(final Bitmap b) {
- final int byteCount;
- if (Versions.feature19Plus) {
- byteCount = b.getAllocationByteCount();
- } else {
- // Close enough for government work.
- // Equivalent to getByteCount, but works on <12.
- byteCount = b.getRowBytes() * b.getHeight();
- }
-
- final ByteBuffer buffer = ByteBuffer.allocate(byteCount);
- try {
- b.copyPixelsToBuffer(buffer);
- } catch (RuntimeException e) {
- // Android throws this if there's not enough space in the buffer.
- // This should never occur, but if it does, we don't
- // really care -- we probably don't need the entire image.
- // This is awful. I apologize.
- if ("Buffer not large enough for pixels".equals(e.getMessage())) {
- return buffer.array();
- }
- throw e;
- }
-
- return buffer.array();
- }
-
- public boolean characterIsMissingInFont(String ch) {
- byte[] rendered = getPixels(drawBitmap(ch));
- return Arrays.equals(rendered, missingCharacter);
- }
- }
-
- private volatile Locale entriesLocale;
- private final CharacterValidator characterValidator;
-
- public LocaleListPreference(Context context) {
- this(context, null);
- }
-
- public LocaleListPreference(Context context, AttributeSet attributes) {
- super(context, attributes);
-
- // Thus far, missing glyphs are replaced by whitespace, not a box
- // or other Unicode codepoint.
- this.characterValidator = new CharacterValidator(" ");
- buildList();
- }
-
- private static final class LocaleDescriptor implements Comparable<LocaleDescriptor> {
- // We use Locale.US here to ensure a stable ordering of entries.
- private static final Collator COLLATOR = Collator.getInstance(Locale.US);
-
- public final String tag;
- private final String nativeName;
-
- public LocaleDescriptor(String tag) {
- this(Locales.parseLocaleCode(tag), tag);
- }
-
- public LocaleDescriptor(Locale locale, String tag) {
- this.tag = tag;
-
- final String displayName = locale.getDisplayName(locale);
- if (TextUtils.isEmpty(displayName)) {
- // There's nothing sane we can do.
- Log.w(LOG_TAG, "Display name is empty. Using " + locale.toString());
- this.nativeName = locale.toString();
- return;
- }
-
- // For now, uppercase the first character of LTR locale names.
- // This is pretty much what Android does. This is a reasonable hack
- // for Bug 1014602, but it won't generalize to all locales.
- final byte directionality = Character.getDirectionality(displayName.charAt(0));
- if (directionality == Character.DIRECTIONALITY_LEFT_TO_RIGHT) {
- this.nativeName = displayName.substring(0, 1).toUpperCase(locale) +
- displayName.substring(1);
- return;
- }
-
- this.nativeName = displayName;
- }
-
- public String getTag() {
- return this.tag;
- }
-
- public String getDisplayName() {
- return this.nativeName;
- }
-
- @Override
- public String toString() {
- return this.nativeName;
- }
-
-
- @Override
- public int compareTo(LocaleDescriptor another) {
- // We sort by name, so we use Collator.
- return COLLATOR.compare(this.nativeName, another.nativeName);
- }
-
- /**
- * See Bug 1023451 Comment 10 for the research that led to
- * this method.
- *
- * @return true if this locale can be used for displaying UI
- * on this device without known issues.
- */
- public boolean isUsable(CharacterValidator validator) {
- if (Versions.preLollipop && this.tag.matches("[a-zA-Z]{3}.*")) {
- // Earlier versions of Android can't load three-char locale code
- // resources.
- return false;
- }
-
- // Oh, for Java 7 switch statements.
- if (this.tag.equals("bn-IN")) {
- // Bengali sometimes has an English label if the Bengali script
- // is missing. This prevents us from simply checking character
- // rendering for bn-IN; we'll get a false positive for "B", not "ব".
- //
- // This doesn't seem to affect other Bengali-script locales
- // (below), which always have a label in native script.
- if (!this.nativeName.startsWith("বাংলা")) {
- // We're on an Android version that doesn't even have
- // characters to say বাংলা. Definite failure.
- return false;
- }
- }
-
- // These locales use a script that is often unavailable
- // on common Android devices. Make sure we can show them.
- // See documentation for CharacterValidator.
- // Note that bn-IN is checked here even if it passed above.
- if (this.tag.equals("or") ||
- this.tag.equals("my") ||
- this.tag.equals("pa-IN") ||
- this.tag.equals("gu-IN") ||
- this.tag.equals("bn-IN")) {
- if (validator.characterIsMissingInFont(this.nativeName.substring(0, 1))) {
- return false;
- }
- }
-
- return true;
- }
- }
-
- /**
- * Not every locale we ship can be used on every device, due to
- * font or rendering constraints.
- *
- * This method filters down the list before generating the descriptor array.
- */
- private LocaleDescriptor[] getUsableLocales() {
- Collection<String> shippingLocales = BrowserLocaleManager.getPackagedLocaleTags(getContext());
-
- // Future: single-locale builds should be specified, too.
- if (shippingLocales == null) {
- final String fallbackTag = BrowserLocaleManager.getInstance().getFallbackLocaleTag();
- return new LocaleDescriptor[] { new LocaleDescriptor(fallbackTag) };
- }
-
- final int initialCount = shippingLocales.size();
- final Set<LocaleDescriptor> locales = new HashSet<LocaleDescriptor>(initialCount);
- for (String tag : shippingLocales) {
- final LocaleDescriptor descriptor = new LocaleDescriptor(tag);
-
- if (!descriptor.isUsable(this.characterValidator)) {
- Log.w(LOG_TAG, "Skipping locale " + tag + " on this device.");
- continue;
- }
-
- locales.add(descriptor);
- }
-
- final int usableCount = locales.size();
- final LocaleDescriptor[] descriptors = locales.toArray(new LocaleDescriptor[usableCount]);
- Arrays.sort(descriptors, 0, usableCount);
- return descriptors;
- }
-
- @Override
- protected void onDialogClosed(boolean positiveResult) {
- // The superclass will take care of persistence.
- super.onDialogClosed(positiveResult);
-
- // Use this hook to try to fix up the environment ASAP.
- // Do this so that the redisplayed fragment is inflated
- // with the right locale.
- final Locale selectedLocale = getSelectedLocale();
- final Context context = getContext();
- BrowserLocaleManager.getInstance().updateConfiguration(context, selectedLocale);
- }
-
- private Locale getSelectedLocale() {
- final String tag = getValue();
- if (tag == null || tag.equals("")) {
- return Locale.getDefault();
- }
- return Locales.parseLocaleCode(tag);
- }
-
- @Override
- public CharSequence getSummary() {
- final String value = getValue();
-
- if (TextUtils.isEmpty(value)) {
- return getContext().getString(R.string.locale_system_default);
- }
-
- // We can't trust super.getSummary() across locale changes,
- // apparently, so let's do the same work.
- return new LocaleDescriptor(value).getDisplayName();
- }
-
- private void buildList() {
- final Locale currentLocale = Locale.getDefault();
- Log.d(LOG_TAG, "Building locales list. Current locale: " + currentLocale);
-
- if (currentLocale.equals(this.entriesLocale) &&
- getEntries() != null) {
- Log.v(LOG_TAG, "No need to build list.");
- return;
- }
-
- final LocaleDescriptor[] descriptors = getUsableLocales();
- final int count = descriptors.length;
-
- this.entriesLocale = currentLocale;
-
- // We leave room for "System default".
- final String[] entries = new String[count + 1];
- final String[] values = new String[count + 1];
-
- entries[0] = getContext().getString(R.string.locale_system_default);
- values[0] = "";
-
- for (int i = 0; i < count; ++i) {
- final String displayName = descriptors[i].getDisplayName();
- final String tag = descriptors[i].getTag();
- Log.v(LOG_TAG, displayName + " => " + tag);
- entries[i + 1] = displayName;
- values[i + 1] = tag;
- }
-
- setEntries(entries);
- setEntryValues(values);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/ModifiableHintPreference.java b/mobile/android/base/java/org/mozilla/gecko/preferences/ModifiableHintPreference.java
deleted file mode 100644
index c545472e2..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/ModifiableHintPreference.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.preference.Preference;
-import android.text.Spanned;
-import android.text.SpannableStringBuilder;
-import android.text.style.ImageSpan;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-class ModifiableHintPreference extends Preference {
- private static final String LOGTAG = "ModifiableHintPref";
- private final Context mContext;
-
- private final String MATCH_STRING = "%I";
- private final int RESID_TEXT_VIEW = R.id.label_search_hint;
- private final int RESID_DRAWABLE = R.drawable.ab_add_search_engine;
- private final double SCALE_FACTOR = 0.5;
-
- public ModifiableHintPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- }
-
- public ModifiableHintPreference(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mContext = context;
- }
-
- @Override
- protected View onCreateView(ViewGroup parent) {
- View thisView = super.onCreateView(parent);
- configurePreferenceView(thisView);
- return thisView;
- }
-
- private void configurePreferenceView(View view) {
- TextView textView = (TextView) view.findViewById(RESID_TEXT_VIEW);
- String searchHint = textView.getText().toString();
-
- // Use an ImageSpan to include the "add search" icon in the Tip.
- int imageSpanIndex = searchHint.indexOf(MATCH_STRING);
- if (imageSpanIndex != -1) {
- // Scale the resource.
- Drawable drawable = mContext.getResources().getDrawable(RESID_DRAWABLE);
- drawable.setBounds(0, 0, (int) (drawable.getIntrinsicWidth() * SCALE_FACTOR),
- (int) (drawable.getIntrinsicHeight() * SCALE_FACTOR));
-
- ImageSpan searchIcon = new ImageSpan(drawable);
- final SpannableStringBuilder hintBuilder = new SpannableStringBuilder(searchHint);
-
- // Insert the image.
- hintBuilder.setSpan(searchIcon, imageSpanIndex, imageSpanIndex + MATCH_STRING.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- textView.setText(hintBuilder, TextView.BufferType.SPANNABLE);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/MultiChoicePreference.java b/mobile/android/base/java/org/mozilla/gecko/preferences/MultiChoicePreference.java
deleted file mode 100644
index 5749bf29d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/MultiChoicePreference.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.util.PrefUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.app.AlertDialog.Builder;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.TypedArray;
-import android.content.SharedPreferences;
-import android.preference.DialogPreference;
-import android.util.AttributeSet;
-
-import java.util.HashSet;
-import java.util.Set;
-
-class MultiChoicePreference extends DialogPreference implements DialogInterface.OnMultiChoiceClickListener {
- private static final String LOGTAG = "GeckoMultiChoicePreference";
-
- private boolean mValues[];
- private boolean mPrevValues[];
- private CharSequence mEntryValues[];
- private CharSequence mEntries[];
- private CharSequence mInitialValues[];
-
- public MultiChoicePreference(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultiChoicePreference);
- mEntries = a.getTextArray(R.styleable.MultiChoicePreference_entries);
- mEntryValues = a.getTextArray(R.styleable.MultiChoicePreference_entryValues);
- mInitialValues = a.getTextArray(R.styleable.MultiChoicePreference_initialValues);
- a.recycle();
-
- loadPersistedValues();
- }
-
- public MultiChoicePreference(Context context) {
- this(context, null);
- }
-
- /**
- * Sets the human-readable entries to be shown in the list. This will be
- * shown in subsequent dialogs.
- * <p>
- * Each entry must have a corresponding index in
- * {@link #setEntryValues(CharSequence[])} and
- * {@link #setInitialValues(CharSequence[])}.
- *
- * @param entries The entries.
- */
- public void setEntries(CharSequence[] entries) {
- mEntries = entries.clone();
- }
-
- /**
- * @param entriesResId The entries array as a resource.
- */
- public void setEntries(int entriesResId) {
- setEntries(getContext().getResources().getTextArray(entriesResId));
- }
-
- /**
- * Sets the preference values for preferences shown in the list.
- *
- * @param entryValues The entry values.
- */
- public void setEntryValues(CharSequence[] entryValues) {
- mEntryValues = entryValues.clone();
- loadPersistedValues();
- }
-
- /**
- * Entry values define a separate pref for each row in the dialog.
- *
- * @param entryValuesResId The entryValues array as a resource.
- */
- public void setEntryValues(int entryValuesResId) {
- setEntryValues(getContext().getResources().getTextArray(entryValuesResId));
- }
-
- /**
- * The array of initial entry values in this list. Each entryValue
- * corresponds to an entryKey. These values are used if a) the preference
- * isn't persisted, or b) the preference is persisted but hasn't yet been
- * set.
- *
- * @param initialValues The entry values
- */
- public void setInitialValues(CharSequence[] initialValues) {
- mInitialValues = initialValues.clone();
- loadPersistedValues();
- }
-
- /**
- * @param initialValuesResId The initialValues array as a resource.
- */
- public void setInitialValues(int initialValuesResId) {
- setInitialValues(getContext().getResources().getTextArray(initialValuesResId));
- }
-
- /**
- * The list of translated strings corresponding to each preference.
- *
- * @return The array of entries.
- */
- public CharSequence[] getEntries() {
- return mEntries.clone();
- }
-
- /**
- * The list of values corresponding to each preference.
- *
- * @return The array of values.
- */
- public CharSequence[] getEntryValues() {
- return mEntryValues.clone();
- }
-
- /**
- * The list of initial values for each preference. Each string in this list
- * should be either "true" or "false".
- *
- * @return The array of initial values.
- */
- public CharSequence[] getInitialValues() {
- return mInitialValues.clone();
- }
-
- public void setValue(final int i, final boolean value) {
- mValues[i] = value;
- mPrevValues = mValues.clone();
- }
-
- /**
- * The list of values for each preference. These values are updated after
- * the dialog has been displayed.
- *
- * @return The array of values.
- */
- public Set<String> getValues() {
- final Set<String> values = new HashSet<String>();
-
- if (mValues == null) {
- return values;
- }
-
- for (int i = 0; i < mValues.length; i++) {
- if (mValues[i]) {
- values.add(mEntryValues[i].toString());
- }
- }
-
- return values;
- }
-
- @Override
- public void onClick(DialogInterface dialog, int which, boolean val) {
- }
-
- @Override
- protected void onPrepareDialogBuilder(Builder builder) {
- if (mEntries == null || mInitialValues == null || mEntryValues == null) {
- throw new IllegalStateException(
- "MultiChoicePreference requires entries, entryValues, and initialValues arrays.");
- }
-
- if (mEntries.length != mEntryValues.length || mEntries.length != mInitialValues.length) {
- throw new IllegalStateException(
- "MultiChoicePreference entries, entryValues, and initialValues arrays must be the same length");
- }
-
- builder.setMultiChoiceItems(mEntries, mValues, this);
- }
-
- @Override
- protected void onDialogClosed(boolean positiveResult) {
- if (mPrevValues == null || mInitialValues == null) {
- // Initialization is done asynchronously, so these values may not
- // have been set before the dialog was closed.
- return;
- }
-
- if (!positiveResult) {
- // user cancelled; reset checkbox values to their previous state
- mValues = mPrevValues.clone();
- return;
- }
-
- mPrevValues = mValues.clone();
-
- if (!callChangeListener(getValues())) {
- return;
- }
-
- persist();
- }
-
- /* Persists the current data stored by this pref to SharedPreferences. */
- public boolean persist() {
- if (isPersistent()) {
- final SharedPreferences.Editor edit = GeckoSharedPrefs.forProfile(getContext()).edit();
- final boolean res = persist(edit);
- edit.apply();
- return res;
- }
-
- return false;
- }
-
- /* Internal persist method. Take an edit so that multiple prefs can be persisted in a single commit. */
- protected boolean persist(SharedPreferences.Editor edit) {
- if (isPersistent()) {
- Set<String> vals = getValues();
- PrefUtils.putStringSet(edit, getKey(), vals).apply();;
- return true;
- }
-
- return false;
- }
-
- /* Returns a list of EntryValues that are currently enabled. */
- public Set<String> getPersistedStrings(Set<String> defaultVal) {
- if (!isPersistent()) {
- return defaultVal;
- }
-
- final SharedPreferences prefs = GeckoSharedPrefs.forProfile(getContext());
- return PrefUtils.getStringSet(prefs, getKey(), defaultVal);
- }
-
- /**
- * Loads persistent prefs from shared preferences. If the preferences
- * aren't persistent or haven't yet been stored, they will be set to their
- * initial values.
- */
- protected void loadPersistedValues() {
- final int entryCount = mInitialValues.length;
- mValues = new boolean[entryCount];
-
- if (entryCount != mEntries.length || entryCount != mEntryValues.length) {
- throw new IllegalStateException(
- "MultiChoicePreference entryValues and initialValues arrays must be the same length");
- }
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- final Set<String> stringVals = getPersistedStrings(null);
-
- for (int i = 0; i < entryCount; i++) {
- if (stringVals != null) {
- mValues[i] = stringVals.contains(mEntryValues[i]);
- } else {
- final boolean defaultVal = mInitialValues[i].equals("true");
- mValues[i] = defaultVal;
- }
- }
-
- mPrevValues = mValues.clone();
- }
- });
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/MultiPrefMultiChoicePreference.java b/mobile/android/base/java/org/mozilla/gecko/preferences/MultiPrefMultiChoicePreference.java
deleted file mode 100644
index 580d613ca..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/MultiPrefMultiChoicePreference.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.TypedArray;
-import android.content.SharedPreferences;
-import android.widget.Button;
-import android.util.AttributeSet;
-import android.util.Log;
-
-import java.util.Set;
-
-/* Provides backwards compatibility for some old multi-choice pref types used by Gecko.
- * This will import the old data from the old prefs the first time it is run.
- */
-class MultiPrefMultiChoicePreference extends MultiChoicePreference {
- private static final String LOGTAG = "GeckoMultiPrefPreference";
- private static final String IMPORT_SUFFIX = "_imported_";
- private final CharSequence[] keys;
-
- public MultiPrefMultiChoicePreference(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultiPrefMultiChoicePreference);
- keys = a.getTextArray(R.styleable.MultiPrefMultiChoicePreference_entryKeys);
- a.recycle();
-
- loadPersistedValues();
- }
-
- // Helper method for reading a boolean pref.
- private boolean getPersistedBoolean(SharedPreferences prefs, String key, boolean defaultReturnValue) {
- if (!isPersistent()) {
- return defaultReturnValue;
- }
-
- return prefs.getBoolean(key, defaultReturnValue);
- }
-
- // Overridden to do a one time import for the old preference type to the new one.
- @Override
- protected synchronized void loadPersistedValues() {
- // This will load the new pref if it exists.
- super.loadPersistedValues();
-
- // First check if we've already done the import the old data. If so, nothing to load.
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(getContext());
- final boolean imported = getPersistedBoolean(prefs, getKey() + IMPORT_SUFFIX, false);
- if (imported) {
- return;
- }
-
- // Load the data we'll need to find the old style prefs
- final CharSequence[] init = getInitialValues();
- final CharSequence[] entries = getEntries();
- if (keys == null || init == null) {
- return;
- }
-
- final int entryCount = keys.length;
- if (entryCount != entries.length || entryCount != init.length) {
- throw new IllegalStateException("MultiChoicePreference entryKeys and initialValues arrays must be the same length");
- }
-
- // Now iterate through the entries on a background thread.
- final SharedPreferences.Editor edit = prefs.edit();
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- try {
- // Use one editor to batch as many changes as we can.
- for (int i = 0; i < entryCount; i++) {
- String key = keys[i].toString();
- boolean initialValue = "true".equals(init[i]);
- boolean val = getPersistedBoolean(prefs, key, initialValue);
-
- // Save the pref and remove the old preference.
- setValue(i, val);
- edit.remove(key);
- }
-
- persist(edit);
- edit.putBoolean(getKey() + IMPORT_SUFFIX, true);
- edit.apply();
- } catch (Exception ex) {
- Log.i(LOGTAG, "Err", ex);
- }
- }
- });
- }
-
-
- @Override
- public void onClick(DialogInterface dialog, int which, boolean val) {
- // enable positive button only if at least one item is checked
- boolean enabled = false;
- final Set<String> values = getValues();
-
- enabled = (values.size() > 0);
- final Button button = ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE);
- if (button.isEnabled() != enabled) {
- button.setEnabled(enabled);
- }
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/PanelsPreference.java b/mobile/android/base/java/org/mozilla/gecko/preferences/PanelsPreference.java
deleted file mode 100644
index 337d9dd2f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/PanelsPreference.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.animation.PropertyAnimator.Property;
-import org.mozilla.gecko.animation.ViewHelper;
-import org.mozilla.gecko.R;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.content.DialogInterface.OnShowListener;
-import android.content.res.Resources;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-public class PanelsPreference extends CustomListPreference {
- protected String LOGTAG = "PanelsPreference";
-
- // Position state of this Preference in enclosing category.
- private static final int STATE_IS_FIRST = 0;
- private static final int STATE_IS_LAST = 1;
-
- /**
- * Index of the context menu button for controlling display options.
- * For (removable) Dynamic panels, this button removes the panel.
- * For built-in panels, this button toggles showing or hiding the panel.
- */
- private static final int INDEX_DISPLAY_BUTTON = 1;
- private static final int INDEX_REORDER_BUTTON = 2;
-
- // Indices of buttons in context menu for reordering.
- private static final int INDEX_MOVE_UP_BUTTON = 0;
- private static final int INDEX_MOVE_DOWN_BUTTON = 1;
-
- private String LABEL_HIDE;
- private String LABEL_SHOW;
-
- private View preferenceView;
- protected boolean mIsHidden;
- private final boolean mIsRemovable;
-
- private boolean mAnimate;
- private static final int ANIMATION_DURATION_MS = 400;
-
- // State for reordering.
- private int mPositionState = -1;
- private final int mIndex;
-
- public PanelsPreference(Context context, CustomListCategory parentCategory, boolean isRemovable, int index, boolean animate) {
- super(context, parentCategory);
- mIsRemovable = isRemovable;
- mIndex = index;
- mAnimate = animate;
- }
-
- @Override
- protected int getPreferenceLayoutResource() {
- return R.layout.preference_panels;
- }
-
- @Override
- protected void onBindView(View view) {
- super.onBindView(view);
-
- // Override view handling so we can grey out "hidden" PanelPreferences.
- view.setEnabled(!mIsHidden);
-
- if (view instanceof ViewGroup) {
- final ViewGroup group = (ViewGroup) view;
- for (int i = 0; i < group.getChildCount(); i++) {
- group.getChildAt(i).setEnabled(!mIsHidden);
- }
- preferenceView = group;
- }
-
- if (mAnimate) {
- ViewHelper.setAlpha(preferenceView, 0);
-
- final PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION_MS);
- animator.attach(preferenceView, Property.ALPHA, 1);
- animator.start();
-
- // Clear animate flag.
- mAnimate = false;
- }
- }
-
- @Override
- protected String[] createDialogItems() {
- final Resources res = getContext().getResources();
- final String labelReorder = res.getString(R.string.pref_panels_reorder);
-
- if (mIsRemovable) {
- return new String[] { LABEL_SET_AS_DEFAULT, LABEL_REMOVE, labelReorder };
- }
-
- // Built-in panels can't be removed, so use show/hide options.
- LABEL_HIDE = res.getString(R.string.pref_panels_hide);
- LABEL_SHOW = res.getString(R.string.pref_panels_show);
-
- return new String[] { LABEL_SET_AS_DEFAULT, LABEL_HIDE, labelReorder };
- }
-
- @Override
- public void setIsDefault(boolean isDefault) {
- mIsDefault = isDefault;
- if (isDefault) {
- setSummary(LABEL_IS_DEFAULT);
- if (mIsHidden) {
- // Unhide the panel if it's being set as the default.
- setHidden(false);
- }
- } else {
- setSummary("");
- }
- }
-
- @Override
- protected void onDialogIndexClicked(int index) {
- switch (index) {
- case INDEX_SET_DEFAULT_BUTTON:
- mParentCategory.setDefault(this);
- break;
-
- case INDEX_DISPLAY_BUTTON:
- // Handle display options for the panel.
- if (mIsRemovable) {
- // For removable panels, the button displays text for removing the panel.
- mParentCategory.uninstall(this);
- } else {
- // Otherwise, the button toggles between text for showing or hiding the panel.
- ((PanelsPreferenceCategory) mParentCategory).setHidden(this, !mIsHidden);
- }
- break;
-
- case INDEX_REORDER_BUTTON:
- // Display dialog for changing preference order.
- final Dialog orderDialog = makeReorderDialog();
- orderDialog.show();
- break;
-
- default:
- Log.w(LOGTAG, "Selected index out of range: " + index);
- }
- }
-
- @Override
- protected void configureShownDialog() {
- super.configureShownDialog();
-
- // Handle Show/Hide buttons.
- if (!mIsRemovable) {
- final TextView hideButton = (TextView) mDialog.getListView().getChildAt(INDEX_DISPLAY_BUTTON);
- hideButton.setText(mIsHidden ? LABEL_SHOW : LABEL_HIDE);
- }
- }
-
-
- private Dialog makeReorderDialog() {
- final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
-
- final Resources res = getContext().getResources();
- final String labelUp = res.getString(R.string.pref_panels_move_up);
- final String labelDown = res.getString(R.string.pref_panels_move_down);
-
- builder.setTitle(getTitle());
- builder.setItems(new String[] { labelUp, labelDown }, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int index) {
- dialog.dismiss();
- switch (index) {
- case INDEX_MOVE_UP_BUTTON:
- ((PanelsPreferenceCategory) mParentCategory).moveUp(PanelsPreference.this);
- break;
-
- case INDEX_MOVE_DOWN_BUTTON:
- ((PanelsPreferenceCategory) mParentCategory).moveDown(PanelsPreference.this);
- break;
- }
- }
- });
-
- final Dialog dialog = builder.create();
- dialog.setOnShowListener(new OnShowListener() {
- @Override
- public void onShow(DialogInterface dialog) {
- setReorderItemsEnabled(dialog);
- }
- });
-
- return dialog;
- }
-
- public void setIsFirst() {
- mPositionState = STATE_IS_FIRST;
- }
-
- public void setIsLast() {
- mPositionState = STATE_IS_LAST;
- }
-
- /**
- * Configure enabled state of the reorder dialog, which must be done after the dialog is shown.
- * @param dialog Dialog to configure
- */
- private void setReorderItemsEnabled(DialogInterface dialog) {
- // Update button enabled-ness for reordering.
- switch (mPositionState) {
- case STATE_IS_FIRST:
- final TextView itemUp = (TextView) ((AlertDialog) dialog).getListView().getChildAt(INDEX_MOVE_UP_BUTTON);
- itemUp.setEnabled(false);
- // Disable clicks to this view.
- itemUp.setOnClickListener(null);
- break;
-
- case STATE_IS_LAST:
- final TextView itemDown = (TextView) ((AlertDialog) dialog).getListView().getChildAt(INDEX_MOVE_DOWN_BUTTON);
- itemDown.setEnabled(false);
- // Disable clicks to this view.
- itemDown.setOnClickListener(null);
- break;
-
- default:
- // Do nothing.
- break;
- }
- }
-
- public void setHidden(boolean toHide) {
- if (toHide) {
- setIsDefault(false);
- }
-
- if (mIsHidden != toHide) {
- mIsHidden = toHide;
- notifyChanged();
- }
- }
-
- public boolean isHidden() {
- return mIsHidden;
- }
-
- public int getIndex() {
- return mIndex;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/PanelsPreferenceCategory.java b/mobile/android/base/java/org/mozilla/gecko/preferences/PanelsPreferenceCategory.java
deleted file mode 100644
index d44b6eaa9..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/PanelsPreferenceCategory.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.TelemetryContract.Method;
-import org.mozilla.gecko.home.HomeConfig;
-import org.mozilla.gecko.home.HomeConfig.PanelConfig;
-import org.mozilla.gecko.home.HomeConfig.State;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.UIAsyncTask;
-
-import android.content.Context;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-
-public class PanelsPreferenceCategory extends CustomListCategory {
- public static final String LOGTAG = "PanelsPrefCategory";
-
- protected HomeConfig mHomeConfig;
- protected HomeConfig.Editor mConfigEditor;
-
- protected UIAsyncTask.WithoutParams<State> mLoadTask;
-
- public PanelsPreferenceCategory(Context context) {
- super(context);
- initConfig(context);
- }
-
- public PanelsPreferenceCategory(Context context, AttributeSet attrs) {
- super(context, attrs);
- initConfig(context);
- }
-
- public PanelsPreferenceCategory(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- initConfig(context);
- }
-
- protected void initConfig(Context context) {
- mHomeConfig = HomeConfig.getDefault(context);
- }
-
- @Override
- public void onAttachedToActivity() {
- super.onAttachedToActivity();
-
- loadHomeConfig(null);
- }
-
- /**
- * Load the Home Panels config and populate the preferences screen and maintain local state.
- */
- private void loadHomeConfig(final String animatePanelId) {
- mLoadTask = new UIAsyncTask.WithoutParams<State>(ThreadUtils.getBackgroundHandler()) {
- @Override
- public HomeConfig.State doInBackground() {
- return mHomeConfig.load();
- }
-
- @Override
- public void onPostExecute(HomeConfig.State configState) {
- mConfigEditor = configState.edit();
- displayHomeConfig(configState, animatePanelId);
- }
- };
- mLoadTask.execute();
- }
-
- /**
- * Simplified refresh of Home Panels when there is no state to be persisted.
- */
- public void refresh() {
- refresh(null, null);
- }
-
- /**
- * Refresh the Home Panels list and animate a panel, if specified.
- * If null, load from HomeConfig.
- *
- * @param State HomeConfig.State to rebuild Home Panels list from.
- * @param String panelId of panel to be animated.
- */
- public void refresh(State state, String animatePanelId) {
- // Clear all the existing home panels.
- removeAll();
-
- if (state == null) {
- loadHomeConfig(animatePanelId);
- } else {
- displayHomeConfig(state, animatePanelId);
- }
- }
-
- private void displayHomeConfig(HomeConfig.State configState, String animatePanelId) {
- int index = 0;
- for (PanelConfig panelConfig : configState) {
- final boolean isRemovable = panelConfig.isDynamic();
-
- // Create and add the pref.
- final String panelId = panelConfig.getId();
- final boolean animate = TextUtils.equals(animatePanelId, panelId);
-
- final PanelsPreference pref = new PanelsPreference(getContext(), PanelsPreferenceCategory.this, isRemovable, index, animate);
- pref.setTitle(panelConfig.getTitle());
- pref.setKey(panelConfig.getId());
- // XXX: Pull icon from PanelInfo.
- addPreference(pref);
-
- if (panelConfig.isDisabled()) {
- pref.setHidden(true);
- }
-
- index++;
- }
-
- setPositionState();
- setDefaultFromConfig();
- }
-
- private void setPositionState() {
- final int prefCount = getPreferenceCount();
-
- // Pass in position state to first and last preference.
- final PanelsPreference firstPref = (PanelsPreference) getPreference(0);
- firstPref.setIsFirst();
-
- final PanelsPreference lastPref = (PanelsPreference) getPreference(prefCount - 1);
- lastPref.setIsLast();
- }
-
- private void setDefaultFromConfig() {
- final String defaultPanelId = mConfigEditor.getDefaultPanelId();
- if (defaultPanelId == null) {
- mDefaultReference = null;
- return;
- }
-
- final int prefCount = getPreferenceCount();
-
- for (int i = 0; i < prefCount; i++) {
- final PanelsPreference pref = (PanelsPreference) getPreference(i);
-
- if (defaultPanelId.equals(pref.getKey())) {
- super.setDefault(pref);
- break;
- }
- }
- }
-
- @Override
- public void setDefault(CustomListPreference pref) {
- super.setDefault(pref);
-
- final String id = pref.getKey();
-
- final String defaultPanelId = mConfigEditor.getDefaultPanelId();
- if (defaultPanelId != null && defaultPanelId.equals(id)) {
- return;
- }
-
- updateVisibilityPrefsForPanel(id, true);
-
- mConfigEditor.setDefault(id);
- mConfigEditor.apply();
-
- Telemetry.sendUIEvent(TelemetryContract.Event.PANEL_SET_DEFAULT, Method.DIALOG, id);
- }
-
- @Override
- protected void onPrepareForRemoval() {
- super.onPrepareForRemoval();
- if (mLoadTask != null) {
- mLoadTask.cancel();
- }
- }
-
- @Override
- public void uninstall(CustomListPreference pref) {
- final String id = pref.getKey();
- mConfigEditor.uninstall(id);
- mConfigEditor.apply();
-
- Telemetry.sendUIEvent(TelemetryContract.Event.PANEL_REMOVE, Method.DIALOG, id);
-
- super.uninstall(pref);
- }
-
- public void moveUp(PanelsPreference pref) {
- final int panelIndex = pref.getIndex();
- if (panelIndex > 0) {
- final String panelKey = pref.getKey();
- mConfigEditor.moveTo(panelKey, panelIndex - 1);
- final State state = mConfigEditor.apply();
-
- Telemetry.sendUIEvent(TelemetryContract.Event.PANEL_MOVE, Method.DIALOG, panelKey);
-
- refresh(state, panelKey);
- }
- }
-
- public void moveDown(PanelsPreference pref) {
- final int panelIndex = pref.getIndex();
- if (panelIndex < getPreferenceCount() - 1) {
- final String panelKey = pref.getKey();
- mConfigEditor.moveTo(panelKey, panelIndex + 1);
- final State state = mConfigEditor.apply();
-
- Telemetry.sendUIEvent(TelemetryContract.Event.PANEL_MOVE, Method.DIALOG, panelKey);
-
- refresh(state, panelKey);
- }
- }
-
- /**
- * Update the hide/show state of the preference and save the HomeConfig
- * changes.
- *
- * @param pref Preference to update
- * @param toHide New hidden state of the preference
- */
- protected void setHidden(PanelsPreference pref, boolean toHide) {
- final String id = pref.getKey();
- mConfigEditor.setDisabled(id, toHide);
- mConfigEditor.apply();
-
- if (toHide) {
- Telemetry.sendUIEvent(TelemetryContract.Event.PANEL_HIDE, Method.DIALOG, id);
- } else {
- Telemetry.sendUIEvent(TelemetryContract.Event.PANEL_SHOW, Method.DIALOG, id);
- }
-
- updateVisibilityPrefsForPanel(id, !toHide);
-
- pref.setHidden(toHide);
- setDefaultFromConfig();
- }
-
- /**
- * When the default panel is removed or disabled, find an enabled panel
- * if possible and set it as mDefaultReference.
- */
- @Override
- protected void setFallbackDefault() {
- setDefaultFromConfig();
- }
-
- private void updateVisibilityPrefsForPanel(String panelId, boolean toShow) {
- if (HomeConfig.getIdForBuiltinPanelType(HomeConfig.PanelType.BOOKMARKS).equals(panelId)) {
- GeckoSharedPrefs.forProfile(getContext()).edit().putBoolean(HomeConfig.PREF_KEY_BOOKMARKS_PANEL_ENABLED, toShow).apply();
- }
-
- if (HomeConfig.getIdForBuiltinPanelType(HomeConfig.PanelType.COMBINED_HISTORY).equals(panelId)) {
- GeckoSharedPrefs.forProfile(getContext()).edit().putBoolean(HomeConfig.PREF_KEY_HISTORY_PANEL_ENABLED, toShow).apply();
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/PrivateDataPreference.java b/mobile/android/base/java/org/mozilla/gecko/preferences/PrivateDataPreference.java
deleted file mode 100644
index 61eff98e7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/PrivateDataPreference.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.icons.storage.DiskStorage;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Set;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.util.Log;
-
-class PrivateDataPreference extends MultiPrefMultiChoicePreference {
- private static final String LOGTAG = "GeckoPrivateDataPreference";
- private static final String PREF_KEY_PREFIX = "private.data.";
-
- public PrivateDataPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onDialogClosed(boolean positiveResult) {
- super.onDialogClosed(positiveResult);
-
- if (!positiveResult) {
- return;
- }
-
- Telemetry.sendUIEvent(TelemetryContract.Event.SANITIZE, TelemetryContract.Method.DIALOG, "settings");
-
- final Set<String> values = getValues();
- final JSONObject json = new JSONObject();
-
- for (String value : values) {
- // Privacy pref checkbox values are stored in Android prefs to
- // remember their check states. The key names are private.data.X,
- // where X is a string from Gecko sanitization. This prefix is
- // removed here so we can send the values to Gecko, which then does
- // the sanitization for each key.
- final String key = value.substring(PREF_KEY_PREFIX.length());
- try {
- json.put(key, true);
- } catch (JSONException e) {
- Log.e(LOGTAG, "JSON error", e);
- }
- }
-
- if (values.contains("private.data.offlineApps")) {
- // Remove all icons from storage if removing "Offline website data" was selected.
- DiskStorage.get(getContext()).evictAll();
- }
-
- // clear private data in gecko
- GeckoAppShell.notifyObservers("Sanitize:ClearData", json.toString());
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/SearchEnginePreference.java b/mobile/android/base/java/org/mozilla/gecko/preferences/SearchEnginePreference.java
deleted file mode 100644
index 3ba80b562..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/SearchEnginePreference.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.SnackbarBuilder;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconDescriptor;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.widget.FaviconView;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.support.design.widget.Snackbar;
-import android.text.SpannableString;
-import android.util.Log;
-import android.view.View;
-
-/**
- * Represents an element in the list of search engines on the preferences menu.
- */
-public class SearchEnginePreference extends CustomListPreference {
- protected String LOGTAG = "SearchEnginePreference";
-
- protected static final int INDEX_REMOVE_BUTTON = 1;
-
- // The icon to display in the prompt when clicked.
- private BitmapDrawable mPromptIcon;
-
- // The bitmap backing the drawable above - needed separately for the FaviconView.
- private Bitmap mIconBitmap;
- private final Object bitmapLock = new Object();
-
- private FaviconView mFaviconView;
-
- // Search engine identifier specified by the gecko search service. This will be "other"
- // for engines that are not shipped with the app.
- private String mIdentifier;
-
- public SearchEnginePreference(Context context, SearchPreferenceCategory parentCategory) {
- super(context, parentCategory);
- }
-
- /**
- * Called by Android when we're bound to the custom view. Allows us to set the custom properties
- * of our custom view elements as we desire (We can now use findViewById on them).
- *
- * @param view The view instance for this Preference object.
- */
- @Override
- protected void onBindView(View view) {
- super.onBindView(view);
-
- // We synchronise to avoid a race condition between this and the favicon loading callback in
- // setSearchEngineFromJSON.
- synchronized (bitmapLock) {
- // Set the icon in the FaviconView.
- mFaviconView = ((FaviconView) view.findViewById(R.id.search_engine_icon));
-
- if (mIconBitmap != null) {
- mFaviconView.updateAndScaleImage(IconResponse.create(mIconBitmap));
- }
- }
- }
-
- @Override
- protected int getPreferenceLayoutResource() {
- return R.layout.preference_search_engine;
- }
-
- /**
- * Returns the strings to be displayed in the dialog.
- */
- @Override
- protected String[] createDialogItems() {
- return new String[] { LABEL_SET_AS_DEFAULT,
- LABEL_REMOVE };
- }
-
- @Override
- public void showDialog() {
- // If this is the last engine, then we are the default, and none of the options
- // on this menu can do anything.
- if (mParentCategory.getPreferenceCount() == 1) {
- Activity activity = (Activity) getContext();
-
- SnackbarBuilder.builder(activity)
- .message(R.string.pref_search_last_toast)
- .duration(Snackbar.LENGTH_LONG)
- .buildAndShow();
-
- return;
- }
-
- super.showDialog();
- }
-
- @Override
- protected void configureDialogBuilder(AlertDialog.Builder builder) {
- // Copy the icon from this object to the prompt we produce. We lazily create the drawable,
- // as the user may not ever actually tap this object.
- if (mPromptIcon == null && mIconBitmap != null) {
- mPromptIcon = new BitmapDrawable(getContext().getResources(), mFaviconView.getBitmap());
- }
-
- builder.setIcon(mPromptIcon);
- }
-
- @Override
- protected void onDialogIndexClicked(int index) {
- switch (index) {
- case INDEX_SET_DEFAULT_BUTTON:
- mParentCategory.setDefault(this);
- break;
-
- case INDEX_REMOVE_BUTTON:
- mParentCategory.uninstall(this);
- break;
-
- default:
- Log.w(LOGTAG, "Selected index out of range.");
- break;
- }
- }
-
- /**
- * @return Identifier of built-in search engine, or "other" if engine is not built-in.
- */
- public String getIdentifier() {
- return mIdentifier;
- }
-
- /**
- * Configure this Preference object from the Gecko search engine JSON object.
- * @param geckoEngineJSON The Gecko-formatted JSON object representing the search engine.
- * @throws JSONException If the JSONObject is invalid.
- */
- public void setSearchEngineFromJSON(JSONObject geckoEngineJSON) throws JSONException {
- mIdentifier = geckoEngineJSON.getString("identifier");
-
- // A null JS value gets converted into a string.
- if (mIdentifier.equals("null")) {
- mIdentifier = "other";
- }
-
- final String engineName = geckoEngineJSON.getString("name");
- final SpannableString titleSpannable = new SpannableString(engineName);
-
- setTitle(titleSpannable);
-
- final String iconURI = geckoEngineJSON.getString("iconURI");
- // Keep a reference to the bitmap - we'll need it later in onBindView.
- try {
- Icons.with(getContext())
- .pageUrl(mIdentifier)
- .icon(IconDescriptor.createGenericIcon(iconURI))
- .privileged(true)
- .build()
- .execute(new IconCallback() {
- @Override
- public void onIconResponse(IconResponse response) {
- mIconBitmap = response.getBitmap();
-
- if (mFaviconView != null) {
- mFaviconView.updateAndScaleImage(response);
- }
- }
- });
- } catch (IllegalArgumentException e) {
- Log.e(LOGTAG, "IllegalArgumentException creating Bitmap. Most likely a zero-length bitmap.", e);
- } catch (NullPointerException e) {
- Log.e(LOGTAG, "NullPointerException creating Bitmap. Most likely a zero-length bitmap.", e);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/SearchPreferenceCategory.java b/mobile/android/base/java/org/mozilla/gecko/preferences/SearchPreferenceCategory.java
deleted file mode 100644
index 47db8b9b0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/SearchPreferenceCategory.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import android.content.Context;
-import android.preference.Preference;
-import android.util.AttributeSet;
-import android.util.Log;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.TelemetryContract.Method;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.ThreadUtils;
-
-public class SearchPreferenceCategory extends CustomListCategory implements GeckoEventListener {
- public static final String LOGTAG = "SearchPrefCategory";
-
- public SearchPreferenceCategory(Context context) {
- super(context);
- }
-
- public SearchPreferenceCategory(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public SearchPreferenceCategory(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- protected void onAttachedToActivity() {
- super.onAttachedToActivity();
-
- // Register for SearchEngines messages and request list of search engines from Gecko.
- GeckoApp.getEventDispatcher().registerGeckoThreadListener(this, "SearchEngines:Data");
- GeckoAppShell.notifyObservers("SearchEngines:GetVisible", null);
- }
-
- @Override
- protected void onPrepareForRemoval() {
- super.onPrepareForRemoval();
-
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this, "SearchEngines:Data");
- }
-
- @Override
- public void setDefault(CustomListPreference item) {
- super.setDefault(item);
-
- sendGeckoEngineEvent("SearchEngines:SetDefault", item.getTitle().toString());
-
- final String identifier = ((SearchEnginePreference) item).getIdentifier();
- Telemetry.sendUIEvent(TelemetryContract.Event.SEARCH_SET_DEFAULT, Method.DIALOG, identifier);
- }
-
- @Override
- public void uninstall(CustomListPreference item) {
- super.uninstall(item);
-
- sendGeckoEngineEvent("SearchEngines:Remove", item.getTitle().toString());
-
- final String identifier = ((SearchEnginePreference) item).getIdentifier();
- Telemetry.sendUIEvent(TelemetryContract.Event.SEARCH_REMOVE, Method.DIALOG, identifier);
- }
-
- @Override
- public void handleMessage(String event, final JSONObject data) {
- if (event.equals("SearchEngines:Data")) {
- // Parse engines array from JSON.
- JSONArray engines;
- try {
- engines = data.getJSONArray("searchEngines");
- } catch (JSONException e) {
- Log.e(LOGTAG, "Unable to decode search engine data from Gecko.", e);
- return;
- }
-
- // Clear the preferences category from this thread.
- this.removeAll();
-
- // Create an element in this PreferenceCategory for each engine.
- for (int i = 0; i < engines.length(); i++) {
- try {
- final JSONObject engineJSON = engines.getJSONObject(i);
-
- final SearchEnginePreference enginePreference = new SearchEnginePreference(getContext(), this);
- enginePreference.setSearchEngineFromJSON(engineJSON);
- enginePreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference preference) {
- SearchEnginePreference sPref = (SearchEnginePreference) preference;
- // Display the configuration dialog associated with the tapped engine.
- sPref.showDialog();
- return true;
- }
- });
-
- addPreference(enginePreference);
-
- // The first element in the array is the default engine.
- if (i == 0) {
- // We set this here, not in setSearchEngineFromJSON, because it allows us to
- // keep a reference to the default engine to use when the AlertDialog
- // callbacks are used.
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- enginePreference.setIsDefault(true);
- }
- });
- mDefaultReference = enginePreference;
- }
- } catch (JSONException e) {
- Log.e(LOGTAG, "JSONException parsing engine at index " + i, e);
- }
- }
- }
- }
-
- /**
- * Helper method to send a particular event string to Gecko with an associated engine name.
- * @param event The type of event to send.
- * @param engine The engine to which the event relates.
- */
- private void sendGeckoEngineEvent(String event, String engineName) {
- JSONObject json = new JSONObject();
- try {
- json.put("engine", engineName);
- } catch (JSONException e) {
- Log.e(LOGTAG, "JSONException creating search engine configuration change message for Gecko.", e);
- return;
- }
- GeckoAppShell.notifyObservers(event, json.toString());
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/SetHomepagePreference.java b/mobile/android/base/java/org/mozilla/gecko/preferences/SetHomepagePreference.java
deleted file mode 100644
index 55be702c4..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/SetHomepagePreference.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.preferences;
-
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.preference.DialogPreference;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-import android.widget.RadioButton;
-import android.widget.RadioGroup;
-
-public class SetHomepagePreference extends DialogPreference {
- private static final String DEFAULT_HOMEPAGE = AboutPages.HOME;
-
- private final SharedPreferences prefs;
-
- private RadioGroup homepageLayout;
- private RadioButton defaultRadio;
- private RadioButton userAddressRadio;
- private EditText homepageEditText;
-
- // This is the url that 1) was loaded from prefs or, 2) stored
- // when the user pressed the "default homepage" checkbox.
- private String storedUrl;
-
- public SetHomepagePreference(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- prefs = GeckoSharedPrefs.forProfile(context);
- }
-
- @Override
- protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
- // Without this GB devices have a black background to the dialog.
- builder.setInverseBackgroundForced(true);
- }
-
- @Override
- protected void onBindDialogView(final View view) {
- super.onBindDialogView(view);
-
- homepageLayout = (RadioGroup) view.findViewById(R.id.homepage_layout);
- defaultRadio = (RadioButton) view.findViewById(R.id.radio_default);
- userAddressRadio = (RadioButton) view.findViewById(R.id.radio_user_address);
- homepageEditText = (EditText) view.findViewById(R.id.edittext_user_address);
-
- storedUrl = prefs.getString(GeckoPreferences.PREFS_HOMEPAGE, DEFAULT_HOMEPAGE);
-
- homepageLayout.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(final RadioGroup radioGroup, final int checkedId) {
- if (checkedId == R.id.radio_user_address) {
- homepageEditText.setVisibility(View.VISIBLE);
- openKeyboardAndSelectAll(getContext(), homepageEditText);
- } else {
- homepageEditText.setVisibility(View.GONE);
- }
- }
- });
- setUIState(storedUrl);
- }
-
- private void setUIState(final String url) {
- if (isUrlDefaultHomepage(url)) {
- defaultRadio.setChecked(true);
- } else {
- userAddressRadio.setChecked(true);
- homepageEditText.setText(url);
- }
- }
-
- private boolean isUrlDefaultHomepage(final String url) {
- return TextUtils.isEmpty(url) || DEFAULT_HOMEPAGE.equals(url);
- }
-
- private static void openKeyboardAndSelectAll(final Context context, final View viewToFocus) {
- viewToFocus.requestFocus();
- viewToFocus.post(new Runnable() {
- @Override
- public void run() {
- InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.showSoftInput(viewToFocus, InputMethodManager.SHOW_IMPLICIT);
- // android:selectAllOnFocus doesn't work for the initial focus:
- // I'm not sure why. We manually selectAll instead.
- if (viewToFocus instanceof EditText) {
- ((EditText) viewToFocus).selectAll();
- }
- }
- });
- }
-
- @Override
- protected void onDialogClosed(final boolean positiveResult) {
- super.onDialogClosed(positiveResult);
- if (positiveResult) {
- final SharedPreferences.Editor editor = prefs.edit();
- final String homePageEditTextValue = homepageEditText.getText().toString();
- final String newPrefValue;
- if (homepageLayout.getCheckedRadioButtonId() == R.id.radio_default ||
- isUrlDefaultHomepage(homePageEditTextValue)) {
- newPrefValue = "";
- editor.remove(GeckoPreferences.PREFS_HOMEPAGE);
- } else {
- newPrefValue = homePageEditTextValue;
- editor.putString(GeckoPreferences.PREFS_HOMEPAGE, newPrefValue);
- }
- editor.apply();
-
- if (getOnPreferenceChangeListener() != null) {
- getOnPreferenceChangeListener().onPreferenceChange(this, newPrefValue);
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/preferences/SyncPreference.java b/mobile/android/base/java/org/mozilla/gecko/preferences/SyncPreference.java
deleted file mode 100644
index 350ac8fc0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/SyncPreference.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.preferences;
-
-import android.content.Context;
-import android.content.Intent;
-import android.preference.Preference;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-
-import com.squareup.picasso.Picasso;
-import com.squareup.picasso.Target;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.TelemetryContract.Method;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.activities.FxAccountWebFlowActivity;
-import org.mozilla.gecko.fxa.activities.PicassoPreferenceIconTarget;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.util.ThreadUtils;
-
-class SyncPreference extends Preference {
- private final Context mContext;
- private final Target profileAvatarTarget;
-
- public SyncPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- final float cornerRadius = mContext.getResources().getDimension(R.dimen.fxaccount_profile_image_width) / 2;
- profileAvatarTarget = new PicassoPreferenceIconTarget(mContext.getResources(), this, cornerRadius);
- }
-
- private void launchFxASetup() {
- final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
- intent.putExtra(FxAccountWebFlowActivity.EXTRA_ENDPOINT, FxAccountConstants.ENDPOINT_PREFERENCES);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
- mContext.startActivity(intent);
- }
-
- public void update(final AndroidFxAccount fxAccount) {
- if (fxAccount == null) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- setTitle(R.string.pref_sync);
- setSummary(R.string.pref_sync_summary);
- // Cancel any pending task.
- Picasso.with(mContext).cancelRequest(profileAvatarTarget);
- // Clear previously set icon.
- // Bug 1312719 - IconDrawable is prior to IconResId, drawable must be set null before setIcon(resId)
- // http://androidxref.com/5.1.1_r6/xref/frameworks/base/core/java/android/preference/Preference.java#562
- setIcon(null);
- setIcon(R.drawable.sync_avatar_default);
- }
- });
- return;
- }
-
- // Update title from account email.
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- setTitle(fxAccount.getEmail());
- setSummary("");
- }
- });
-
- final ExtendedJSONObject profileJSON = fxAccount.getProfileJSON();
- if (profileJSON == null) {
- return;
- }
-
- // Avatar URI empty, return early.
- final String avatarURI = profileJSON.getString(FxAccountConstants.KEY_PROFILE_JSON_AVATAR);
- if (TextUtils.isEmpty(avatarURI)) {
- return;
- }
-
- Picasso.with(mContext)
- .load(avatarURI)
- .centerInside()
- .resizeDimen(R.dimen.fxaccount_profile_image_width, R.dimen.fxaccount_profile_image_height)
- .placeholder(R.drawable.sync_avatar_default)
- .error(R.drawable.sync_avatar_default)
- .into(profileAvatarTarget);
- }
-
- @Override
- protected void onClick() {
- // Launch the FxA "Get started" activity, which will dispatch to the
- // right location.
- launchFxASetup();
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, Method.SETTINGS, "sync_setup");
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java b/mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java
deleted file mode 100644
index c1eeb6bd5..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/promotion/AddToHomeScreenPromotion.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.promotion;
-
-import android.app.Activity;
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.support.annotation.CallSuper;
-import android.util.Log;
-
-import com.keepsafe.switchboard.SwitchBoard;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.UrlAnnotations;
-import org.mozilla.gecko.delegates.TabsTrayVisibilityAwareDelegate;
-import org.mozilla.gecko.Experiments;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.lang.ref.WeakReference;
-
-import ch.boye.httpclientandroidlib.util.TextUtils;
-
-/**
- * Promote "Add to home screen" if user visits website often.
- */
-public class AddToHomeScreenPromotion extends TabsTrayVisibilityAwareDelegate implements Tabs.OnTabsChangedListener {
- private static class URLHistory {
- public final long visits;
- public final long lastVisit;
-
- private URLHistory(long visits, long lastVisit) {
- this.visits = visits;
- this.lastVisit = lastVisit;
- }
- }
-
- private static final String LOGTAG = "GeckoPromoteShortcut";
-
- private static final String EXPERIMENT_MINIMUM_TOTAL_VISITS = "minimumTotalVisits";
- private static final String EXPERIMENT_LAST_VISIT_MINIMUM_AGE = "lastVisitMinimumAgeMs";
- private static final String EXPERIMENT_LAST_VISIT_MAXIMUM_AGE = "lastVisitMaximumAgeMs";
-
- private WeakReference<Activity> activityReference;
- private boolean isEnabled;
- private int minimumVisits;
- private int lastVisitMinimumAgeMs;
- private int lastVisitMaximumAgeMs;
-
- @CallSuper
- @Override
- public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {
- super.onCreate(browserApp, savedInstanceState);
- activityReference = new WeakReference<Activity>(browserApp);
-
- initializeExperiment(browserApp);
- }
-
- @Override
- public void onResume(BrowserApp browserApp) {
- Tabs.registerOnTabsChangedListener(this);
- }
-
- @Override
- public void onPause(BrowserApp browserApp) {
- Tabs.unregisterOnTabsChangedListener(this);
- }
-
- private void initializeExperiment(Context context) {
- if (!SwitchBoard.isInExperiment(context, Experiments.PROMOTE_ADD_TO_HOMESCREEN)) {
- Log.v(LOGTAG, "Experiment not enabled");
- // Experiment is not enabled. No need to try to read values.
- return;
- }
-
- JSONObject values = SwitchBoard.getExperimentValuesFromJson(context, Experiments.PROMOTE_ADD_TO_HOMESCREEN);
- if (values == null) {
- // We didn't get any values for this experiment. Let's disable it instead of picking default
- // values that might be bad.
- return;
- }
-
- try {
- initializeWithValues(
- values.getInt(EXPERIMENT_MINIMUM_TOTAL_VISITS),
- values.getInt(EXPERIMENT_LAST_VISIT_MINIMUM_AGE),
- values.getInt(EXPERIMENT_LAST_VISIT_MAXIMUM_AGE));
- } catch (JSONException e) {
- Log.w(LOGTAG, "Could not read experiment values", e);
- }
- }
-
- private void initializeWithValues(int minimumVisits, int lastVisitMinimumAgeMs, int lastVisitMaximumAgeMs) {
- this.isEnabled = true;
-
- this.minimumVisits = minimumVisits;
- this.lastVisitMinimumAgeMs = lastVisitMinimumAgeMs;
- this.lastVisitMaximumAgeMs = lastVisitMaximumAgeMs;
- }
-
- @Override
- public void onTabChanged(final Tab tab, Tabs.TabEvents msg, String data) {
- if (tab == null) {
- return;
- }
-
- if (!Tabs.getInstance().isSelectedTab(tab)) {
- // We only ever want to show this promotion for the current tab.
- return;
- }
-
- if (Tabs.TabEvents.LOADED != msg) {
- return;
- }
-
- if (tab.isPrivate()) {
- // Never show the prompt for private browsing tabs.
- return;
- }
-
- if (isTabsTrayVisible()) {
- // We only want to show this prompt if this tab is in the foreground and not on top
- // of the tabs tray.
- return;
- }
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- maybeShowPromotionForUrl(tab.getURL(), tab.getTitle());
- }
- });
- }
-
- private void maybeShowPromotionForUrl(String url, String title) {
- if (!isEnabled) {
- return;
- }
-
- final Context context = activityReference.get();
- if (context == null) {
- return;
- }
-
- if (!shouldShowPromotion(context, url, title)) {
- return;
- }
-
- HomeScreenPrompt.show(context, url, title);
- }
-
- private boolean shouldShowPromotion(Context context, String url, String title) {
- if (TextUtils.isEmpty(url) || TextUtils.isEmpty(title)) {
- // We require an URL and a title for the shortcut.
- return false;
- }
-
- if (AboutPages.isAboutPage(url)) {
- // No promotion for our internal sites.
- return false;
- }
-
- if (!url.startsWith("https://")) {
- // Only promote websites that are served over HTTPS.
- return false;
- }
-
- URLHistory history = getHistoryForURL(context, url);
- if (history == null) {
- // There's no history for this URL yet or we can't read it right now. Just ignore.
- return false;
- }
-
- if (history.visits < minimumVisits) {
- // This URL has not been visited often enough.
- return false;
- }
-
- if (history.lastVisit > System.currentTimeMillis() - lastVisitMinimumAgeMs) {
- // The last visit is too new. Do not show promotion. This is mostly to avoid that the
- // promotion shows up for a quick refreshs and in the worst case the last visit could
- // be the current visit (race).
- return false;
- }
-
- if (history.lastVisit < System.currentTimeMillis() - lastVisitMaximumAgeMs) {
- // The last visit is to old. Do not show promotion.
- return false;
- }
-
- if (hasAcceptedOrDeclinedHomeScreenShortcut(context, url)) {
- // The user has already created a shortcut in the past or actively declined to create one.
- // Let's not ask again for this url - We do not want to be annoying.
- return false;
- }
-
- return true;
- }
-
- protected boolean hasAcceptedOrDeclinedHomeScreenShortcut(Context context, String url) {
- final UrlAnnotations urlAnnotations = BrowserDB.from(context).getUrlAnnotations();
- return urlAnnotations.hasAcceptedOrDeclinedHomeScreenShortcut(context.getContentResolver(), url);
- }
-
- protected URLHistory getHistoryForURL(Context context, String url) {
- final GeckoProfile profile = GeckoProfile.get(context);
- final BrowserDB browserDB = BrowserDB.from(profile);
-
- Cursor cursor = null;
- try {
- cursor = browserDB.getHistoryForURL(context.getContentResolver(), url);
-
- if (cursor.moveToFirst()) {
- return new URLHistory(
- cursor.getInt(cursor.getColumnIndex(BrowserContract.History.VISITS)),
- cursor.getLong(cursor.getColumnIndex(BrowserContract.History.DATE_LAST_VISITED)));
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- return null;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/promotion/HomeScreenPrompt.java b/mobile/android/base/java/org/mozilla/gecko/promotion/HomeScreenPrompt.java
deleted file mode 100644
index 0f2df8a2c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/promotion/HomeScreenPrompt.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.promotion;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.Locales;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.UrlAnnotations;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.Experiments;
-import org.mozilla.gecko.util.ActivityUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-
-/**
- * Prompt to promote adding the current website to the home screen.
- */
-public class HomeScreenPrompt extends Locales.LocaleAwareActivity implements IconCallback {
- private static final String EXTRA_TITLE = "title";
- private static final String EXTRA_URL = "url";
-
- private static final String TELEMETRY_EXTRA = "home_screen_promotion";
-
- private View containerView;
- private ImageView iconView;
- private String title;
- private String url;
- private boolean isAnimating;
- private boolean hasAccepted;
- private boolean hasDeclined;
-
- public static void show(Context context, String url, String title) {
- Intent intent = new Intent(context, HomeScreenPrompt.class);
- intent.putExtra(EXTRA_TITLE, title);
- intent.putExtra(EXTRA_URL, url);
- context.startActivity(intent);
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- fetchDataFromIntent();
- setupViews();
- loadShortcutIcon();
-
- slideIn();
-
- Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.PROMOTE_ADD_TO_HOMESCREEN);
-
- // Technically this isn't triggered by a "service". But it's also triggered by a background task and without
- // actual user interaction.
- Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.SERVICE, TELEMETRY_EXTRA);
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
-
- Telemetry.stopUISession(TelemetryContract.Session.EXPERIMENT, Experiments.PROMOTE_ADD_TO_HOMESCREEN);
- }
-
- private void fetchDataFromIntent() {
- final Bundle extras = getIntent().getExtras();
-
- title = extras.getString(EXTRA_TITLE);
- url = extras.getString(EXTRA_URL);
- }
-
- private void setupViews() {
- setContentView(R.layout.homescreen_prompt);
-
- ((TextView) findViewById(R.id.title)).setText(title);
-
- Uri uri = Uri.parse(url);
- ((TextView) findViewById(R.id.host)).setText(uri.getHost());
-
- containerView = findViewById(R.id.container);
- iconView = (ImageView) findViewById(R.id.icon);
-
- findViewById(R.id.add).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- hasAccepted = true;
-
- addToHomeScreen();
- }
- });
-
- findViewById(R.id.close).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- onDecline();
- }
- });
- }
-
- private void addToHomeScreen() {
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- GeckoAppShell.createShortcut(title, url);
-
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, TELEMETRY_EXTRA);
-
- ActivityUtils.goToHomeScreen(HomeScreenPrompt.this);
-
- finish();
- }
- });
- }
-
-
-
- private void loadShortcutIcon() {
- Icons.with(this)
- .pageUrl(url)
- .skipNetwork()
- .skipMemory()
- .forLauncherIcon()
- .build()
- .execute(this);
- }
-
- private void slideIn() {
- containerView.setTranslationY(500);
- containerView.setAlpha(0);
-
- final Animator translateAnimator = ObjectAnimator.ofFloat(containerView, "translationY", 0);
- translateAnimator.setDuration(400);
-
- final Animator alphaAnimator = ObjectAnimator.ofFloat(containerView, "alpha", 1);
- alphaAnimator.setStartDelay(200);
- alphaAnimator.setDuration(600);
-
- final AnimatorSet set = new AnimatorSet();
- set.playTogether(alphaAnimator, translateAnimator);
- set.setStartDelay(400);
-
- set.start();
- }
-
- /**
- * Remember that the user rejected creating a home screen shortcut for this URL.
- */
- private void rememberRejection() {
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- final UrlAnnotations urlAnnotations = BrowserDB.from(HomeScreenPrompt.this).getUrlAnnotations();
- urlAnnotations.insertHomeScreenShortcut(getContentResolver(), url, false);
- }
- });
- }
-
- private void slideOut() {
- if (isAnimating) {
- return;
- }
-
- isAnimating = true;
-
- ObjectAnimator animator = ObjectAnimator.ofFloat(containerView, "translationY", containerView.getHeight());
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- finish();
- }
-
- });
- animator.start();
- }
-
- @Override
- public void finish() {
- super.finish();
-
- // Don't perform an activity-dismiss animation.
- overridePendingTransition(0, 0);
- }
-
- @Override
- public void onBackPressed() {
- onDecline();
- }
-
- private void onDecline() {
- if (hasDeclined || hasAccepted) {
- return;
- }
-
- rememberRejection();
- slideOut();
-
- // Technically not always an action triggered by the "back" button but with the same effect: Finishing this
- // activity and going back to the previous one.
- Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, TelemetryContract.Method.BACK, TELEMETRY_EXTRA);
-
- hasDeclined = true;
- }
-
- /**
- * User clicked outside of the prompt.
- */
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- onDecline();
-
- return true;
- }
-
- @Override
- public void onIconResponse(IconResponse response) {
- iconView.setImageBitmap(response.getBitmap());
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/promotion/ReaderViewBookmarkPromotion.java b/mobile/android/base/java/org/mozilla/gecko/promotion/ReaderViewBookmarkPromotion.java
deleted file mode 100644
index db5a531c6..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/promotion/ReaderViewBookmarkPromotion.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.promotion;
-
-import android.content.Intent;
-import android.content.SharedPreferences;
-
-import com.keepsafe.switchboard.SwitchBoard;
-
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.delegates.BrowserAppDelegateWithReference;
-import org.mozilla.gecko.reader.ReaderModeUtils;
-import org.mozilla.gecko.Experiments;
-
-public class ReaderViewBookmarkPromotion extends BrowserAppDelegateWithReference implements Tabs.OnTabsChangedListener {
- private static final String PREF_FIRST_RV_HINT_SHOWN = "first_reader_view_hint_shown";
- private static final String FIRST_READERVIEW_OPEN_TELEMETRYEXTRA = "first_readerview_open_prompt";
-
- private boolean hasEnteredReaderMode = false;
-
- @Override
- public void onResume(BrowserApp browserApp) {
- Tabs.registerOnTabsChangedListener(this);
- }
-
- @Override
- public void onPause(BrowserApp browserApp) {
- Tabs.unregisterOnTabsChangedListener(this);
- }
-
- @Override
- public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
- switch (msg) {
- case LOCATION_CHANGE:
- // old url: data
- // new url: tab.getURL()
- final boolean enteringReaderMode = ReaderModeUtils.isEnteringReaderMode(data, tab.getURL());
-
- if (!hasEnteredReaderMode && enteringReaderMode) {
- hasEnteredReaderMode = true;
- promoteBookmarking();
- }
-
- break;
- }
- }
-
- @Override
- public void onActivityResult(BrowserApp browserApp, int requestCode, int resultCode, Intent data) {
- switch (requestCode) {
- case BrowserApp.ACTIVITY_REQUEST_TRIPLE_READERVIEW:
- if (resultCode == BrowserApp.ACTIVITY_RESULT_TRIPLE_READERVIEW_ADD_BOOKMARK) {
- final Tab tab = Tabs.getInstance().getSelectedTab();
- if (tab != null) {
- tab.addBookmark();
- }
- } else if (resultCode == BrowserApp.ACTIVITY_RESULT_TRIPLE_READERVIEW_IGNORE) {
- // Nothing to do: we won't show this promotion again either way.
- }
- break;
- }
- }
-
- private void promoteBookmarking() {
- final BrowserApp browserApp = getBrowserApp();
- if (browserApp == null) {
- return;
- }
-
- final SharedPreferences prefs = GeckoSharedPrefs.forProfile(browserApp);
- final boolean isEnabled = SwitchBoard.isInExperiment(browserApp, Experiments.TRIPLE_READERVIEW_BOOKMARK_PROMPT);
-
- // We reuse the same preference as for the first offline reader view bookmark
- // as we only want to show one of the two UIs (they both explain the same
- // functionality).
- if (!isEnabled || prefs.getBoolean(PREF_FIRST_RV_HINT_SHOWN, false)) {
- return;
- }
-
- SimpleHelperUI.show(browserApp,
- FIRST_READERVIEW_OPEN_TELEMETRYEXTRA,
- BrowserApp.ACTIVITY_REQUEST_TRIPLE_READERVIEW,
- R.string.helper_triple_readerview_open_title,
- R.string.helper_triple_readerview_open_message,
- R.drawable.helper_readerview_bookmark, // We share the icon with the usual helper UI
- R.string.helper_triple_readerview_open_button,
- BrowserApp.ACTIVITY_RESULT_TRIPLE_READERVIEW_ADD_BOOKMARK,
- BrowserApp.ACTIVITY_RESULT_TRIPLE_READERVIEW_IGNORE);
-
- prefs
- .edit()
- .putBoolean(PREF_FIRST_RV_HINT_SHOWN, true)
- .apply();
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/promotion/SimpleHelperUI.java b/mobile/android/base/java/org/mozilla/gecko/promotion/SimpleHelperUI.java
deleted file mode 100644
index b6b857fb9..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/promotion/SimpleHelperUI.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.promotion;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.annotation.DrawableRes;
-import android.support.annotation.StringRes;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import org.mozilla.gecko.Locales;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-
-/**
- * Generic HelperUI (prompt) that can be populated with an image, title, message and action button.
- * See show() for usage. This is run as an Activity, results must be handled in the parent Activities
- * onActivityResult().
- */
-public class SimpleHelperUI extends Locales.LocaleAwareActivity {
- public static final String PREF_FIRST_RVBP_SHOWN = "first_reader_view_bookmark_prompt_shown";
- public static final String FIRST_RVBP_SHOWN_TELEMETRYEXTRA = "first_readerview_bookmark_prompt";
-
- private View containerView;
-
- private boolean isAnimating;
-
- private String mTelemetryExtra;
-
- private static final String EXTRA_TELEMETRYEXTRA = "telemetryextra";
- private static final String EXTRA_TITLE = "title";
- private static final String EXTRA_MESSAGE = "message";
- private static final String EXTRA_IMAGE = "image";
- private static final String EXTRA_BUTTON = "button";
- private static final String EXTRA_RESULTCODE_POSITIVE = "positive";
- private static final String EXTRA_RESULTCODE_NEGATIVE = "negative";
-
-
- /**
- * Show a generic helper UI/prompt.
- *
- * @param owner The owning Activity, the result of this prompt will be delivered to its
- * onActivityResult().
- * @param requestCode The request code for the Activity that will be created, this is passed to
- * onActivityResult() to identify the prompt.
- *
- * @param positiveResultCode The result code passed to onActivityResult() when the button has
- * been pressed.
- * @param negativeResultCode The result code passed to onActivityResult() when the prompt was
- * dismissed, either by pressing outside the prompt or by pressing the
- * device back button.
- */
- public static void show(Activity owner, String telemetryExtra,
- int requestCode,
- @StringRes int title, @StringRes int message,
- @DrawableRes int image, @StringRes int buttonText,
- int positiveResultCode, int negativeResultCode) {
- Intent intent = new Intent(owner, SimpleHelperUI.class);
-
- intent.putExtra(EXTRA_TELEMETRYEXTRA, telemetryExtra);
-
- intent.putExtra(EXTRA_TITLE, title);
- intent.putExtra(EXTRA_MESSAGE, message);
-
- intent.putExtra(EXTRA_IMAGE, image);
- intent.putExtra(EXTRA_BUTTON, buttonText);
-
- intent.putExtra(EXTRA_RESULTCODE_POSITIVE, positiveResultCode);
- intent.putExtra(EXTRA_RESULTCODE_NEGATIVE, negativeResultCode);
-
- owner.startActivityForResult(intent, requestCode);
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mTelemetryExtra = getIntent().getStringExtra(EXTRA_TELEMETRYEXTRA);
-
- setupViews();
-
- slideIn();
- }
-
- private void setupViews() {
- final Intent i = getIntent();
-
- setContentView(R.layout.simple_helper_ui);
-
- containerView = findViewById(R.id.container);
-
- ((ImageView) findViewById(R.id.image)).setImageResource(i.getIntExtra(EXTRA_IMAGE, 0));
-
- ((TextView) findViewById(R.id.title)).setText(i.getIntExtra(EXTRA_TITLE, 0));
-
- ((TextView) findViewById(R.id.message)).setText(i.getIntExtra(EXTRA_MESSAGE, 0));
-
- final Button button = ((Button) findViewById(R.id.button));
- button.setText(i.getIntExtra(EXTRA_BUTTON, 0));
- button.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- slideOut();
-
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, mTelemetryExtra);
-
- setResult(i.getIntExtra(EXTRA_RESULTCODE_POSITIVE, -1));
- }
- });
- }
-
- private void slideIn() {
- containerView.setTranslationY(500);
- containerView.setAlpha(0);
-
- final Animator translateAnimator = ObjectAnimator.ofFloat(containerView, "translationY", 0);
- translateAnimator.setDuration(400);
-
- final Animator alphaAnimator = ObjectAnimator.ofFloat(containerView, "alpha", 1);
- alphaAnimator.setStartDelay(200);
- alphaAnimator.setDuration(600);
-
- final AnimatorSet set = new AnimatorSet();
- set.playTogether(alphaAnimator, translateAnimator);
- set.setStartDelay(400);
-
- set.start();
- }
-
- private void slideOut() {
- if (isAnimating) {
- return;
- }
-
- isAnimating = true;
-
- ObjectAnimator animator = ObjectAnimator.ofFloat(containerView, "translationY", containerView.getHeight());
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- finish();
- }
-
- });
- animator.start();
- }
-
- @Override
- public void finish() {
- super.finish();
-
- // Don't perform an activity-dismiss animation.
- overridePendingTransition(0, 0);
- }
-
- @Override
- public void onBackPressed() {
- slideOut();
-
- Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, TelemetryContract.Method.BACK, mTelemetryExtra);
-
- setResult(getIntent().getIntExtra(EXTRA_RESULTCODE_NEGATIVE, -1));
-
- }
-
- /**
- * User clicked outside of the prompt.
- */
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- slideOut();
-
- // Not really an action triggered by the "back" button but with the same effect: Finishing this
- // activity and going back to the previous one.
- Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL, TelemetryContract.Method.BACK, mTelemetryExtra);
-
- setResult(getIntent().getIntExtra(EXTRA_RESULTCODE_NEGATIVE, -1));
-
- return true;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/prompts/ColorPickerInput.java b/mobile/android/base/java/org/mozilla/gecko/prompts/ColorPickerInput.java
deleted file mode 100644
index 3d66eeea8..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/ColorPickerInput.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.prompts;
-
-import org.json.JSONObject;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.widget.BasicColorPicker;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.view.LayoutInflater;
-import android.view.View;
-
-public class ColorPickerInput extends PromptInput {
- public static final String INPUT_TYPE = "color";
- public static final String LOGTAG = "GeckoColorPickerInput";
-
- private final boolean mShowAdvancedButton = true;
- private final int mInitialColor;
-
- public ColorPickerInput(JSONObject obj) {
- super(obj);
- String init = obj.optString("value");
- mInitialColor = Color.rgb(Integer.parseInt(init.substring(1, 3), 16),
- Integer.parseInt(init.substring(3, 5), 16),
- Integer.parseInt(init.substring(5, 7), 16));
- }
-
- @Override
- public View getView(Context context) throws UnsupportedOperationException {
- LayoutInflater inflater = LayoutInflater.from(context);
- mView = inflater.inflate(R.layout.basic_color_picker_dialog, null);
-
- BasicColorPicker cp = (BasicColorPicker) mView.findViewById(R.id.colorpicker);
- cp.setColor(mInitialColor);
-
- return mView;
- }
-
- @Override
- public Object getValue() {
- BasicColorPicker cp = (BasicColorPicker) mView.findViewById(R.id.colorpicker);
- int color = cp.getColor();
- return "#" + Integer.toHexString(color).substring(2);
- }
-
- @Override
- public boolean getScrollable() {
- return true;
- }
-
- @Override
- public boolean canApplyInputStyle() {
- return false;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/prompts/IconGridInput.java b/mobile/android/base/java/org/mozilla/gecko/prompts/IconGridInput.java
deleted file mode 100644
index bc7d7ac20..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/IconGridInput.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.prompts;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.ResourceDrawableUtils;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
-import android.view.Display;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ArrayAdapter;
-import android.widget.GridView;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-public class IconGridInput extends PromptInput implements OnItemClickListener {
- public static final String INPUT_TYPE = "icongrid";
- public static final String LOGTAG = "GeckoIconGridInput";
-
- private ArrayAdapter<IconGridItem> mAdapter; // An adapter holding a list of items to show in the grid
-
- private static int mColumnWidth = -1; // The maximum width of columns
- private static int mMaxColumns = -1; // The maximum number of columns to show
- private static int mIconSize = -1; // Size of icons in the grid
- private int mSelected; // Current selection
- private final JSONArray mArray;
-
- public IconGridInput(JSONObject obj) {
- super(obj);
- mArray = obj.optJSONArray("items");
- }
-
- @Override
- public View getView(Context context) throws UnsupportedOperationException {
- if (mColumnWidth < 0) {
- // getColumnWidth isn't available on pre-ICS, so we pull it out and assign it here
- mColumnWidth = context.getResources().getDimensionPixelSize(R.dimen.icongrid_columnwidth);
- }
-
- if (mIconSize < 0) {
- mIconSize = GeckoAppShell.getPreferredIconSize();
- }
-
- if (mMaxColumns < 0) {
- mMaxColumns = context.getResources().getInteger(R.integer.max_icon_grid_columns);
- }
-
- // TODO: Dynamically handle size changes
- final WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- final Display display = wm.getDefaultDisplay();
- final int screenWidth = display.getWidth();
- int maxColumns = Math.min(mMaxColumns, screenWidth / mColumnWidth);
-
- final GridView view = (GridView) LayoutInflater.from(context).inflate(R.layout.icon_grid, null, false);
- view.setColumnWidth(mColumnWidth);
-
- final ArrayList<IconGridItem> items = new ArrayList<IconGridItem>(mArray.length());
- for (int i = 0; i < mArray.length(); i++) {
- IconGridItem item = new IconGridItem(context, mArray.optJSONObject(i));
- items.add(item);
- if (item.selected) {
- mSelected = i;
- }
- }
-
- view.setNumColumns(Math.min(items.size(), maxColumns));
- view.setOnItemClickListener(this);
- // Despite what the docs say, setItemChecked was not moved into the AbsListView class until sometime between
- // Android 2.3.7 and Android 4.0.3. For other versions the item won't be visually highlighted, BUT we really only
- // mSelected will still be set so that we default to its behavior.
- if (mSelected > -1) {
- view.setItemChecked(mSelected, true);
- }
-
- mAdapter = new IconGridAdapter(context, -1, items);
- view.setAdapter(mAdapter);
- mView = view;
- return mView;
- }
-
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- mSelected = position;
- notifyListeners(Integer.toString(position));
- }
-
- @Override
- public Object getValue() {
- return mSelected;
- }
-
- @Override
- public boolean getScrollable() {
- return true;
- }
-
- private class IconGridAdapter extends ArrayAdapter<IconGridItem> {
- public IconGridAdapter(Context context, int resource, List<IconGridItem> items) {
- super(context, resource, items);
- }
-
- @Override
- public View getView(int position, View convert, ViewGroup parent) {
- final Context context = parent.getContext();
- if (convert == null) {
- convert = LayoutInflater.from(context).inflate(R.layout.icon_grid_item, parent, false);
- }
- bindView(convert, context, position);
- return convert;
- }
-
- private void bindView(View v, Context c, int position) {
- final IconGridItem item = getItem(position);
- final TextView text1 = (TextView) v.findViewById(android.R.id.text1);
- text1.setText(item.label);
-
- final TextView text2 = (TextView) v.findViewById(android.R.id.text2);
- if (TextUtils.isEmpty(item.description)) {
- text2.setVisibility(View.GONE);
- } else {
- text2.setVisibility(View.VISIBLE);
- text2.setText(item.description);
- }
-
- final ImageView icon = (ImageView) v.findViewById(R.id.icon);
- icon.setImageDrawable(item.icon);
- ViewGroup.LayoutParams lp = icon.getLayoutParams();
- lp.width = lp.height = mIconSize;
- }
- }
-
- private class IconGridItem {
- final String label;
- final String description;
- final boolean selected;
- Drawable icon;
-
- public IconGridItem(final Context context, final JSONObject obj) {
- label = obj.optString("name");
- final String iconUrl = obj.optString("iconUri");
- description = obj.optString("description");
- selected = obj.optBoolean("selected");
-
- ResourceDrawableUtils.getDrawable(context, iconUrl, new ResourceDrawableUtils.BitmapLoader() {
- @Override
- public void onBitmapFound(Drawable d) {
- icon = d;
- if (mAdapter != null) {
- mAdapter.notifyDataSetChanged();
- }
- }
- });
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/prompts/IntentChooserPrompt.java b/mobile/android/base/java/org/mozilla/gecko/prompts/IntentChooserPrompt.java
deleted file mode 100644
index 502f1156d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/IntentChooserPrompt.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.prompts;
-
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.widget.GeckoActionProvider;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.widget.ListView;
-import android.util.Log;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Shows a prompt letting the user pick from a list of intent handlers for a set of Intents or
- * for a GeckoActionProvider. Basic usage:
- * IntentChooserPrompt prompt = new IntentChooserPrompt(context, new Intent[] {
- * ... // some intents
- * });
- * prompt.show("Title", context, new IntentHandler() {
- * public void onIntentSelected(Intent intent, int position) { }
- * public void onCancelled() { }
- * });
- **/
-public class IntentChooserPrompt {
- private static final String LOGTAG = "GeckoIntentChooser";
-
- private final ArrayList<PromptListItem> mItems;
-
- public IntentChooserPrompt(Context context, Intent[] intents) {
- mItems = getItems(context, intents);
- }
-
- public IntentChooserPrompt(Context context, GeckoActionProvider provider) {
- mItems = getItems(context, provider);
- }
-
- /* If an IntentHandler is passed in, will asynchronously call the handler when the dialog is closed
- * Otherwise, will return the Intent that was chosen by the user. Must be called on the UI thread.
- */
- public void show(final String title, final Context context, final IntentHandler handler) {
- ThreadUtils.assertOnUiThread();
-
- if (mItems.isEmpty()) {
- Log.i(LOGTAG, "No activities for the intent chooser!");
- handler.onCancelled();
- return;
- }
-
- // If there's only one item in the intent list, just return it
- if (mItems.size() == 1) {
- handler.onIntentSelected(mItems.get(0).getIntent(), 0);
- return;
- }
-
- final Prompt prompt = new Prompt(context, new Prompt.PromptCallback() {
- @Override
- public void onPromptFinished(String promptServiceResult) {
- if (handler == null) {
- return;
- }
-
- int itemId = -1;
- try {
- itemId = new JSONObject(promptServiceResult).getInt("button");
- } catch (JSONException e) {
- Log.e(LOGTAG, "result from promptservice was invalid: ", e);
- }
-
- if (itemId == -1) {
- handler.onCancelled();
- } else {
- handler.onIntentSelected(mItems.get(itemId).getIntent(), itemId);
- }
- }
- });
-
- PromptListItem[] arrays = new PromptListItem[mItems.size()];
- mItems.toArray(arrays);
- prompt.show(title, "", arrays, ListView.CHOICE_MODE_NONE);
-
- return;
- }
-
- // Whether or not any activities were found. Useful for checking if you should try a different Intent set
- public boolean hasActivities(Context context) {
- return mItems.isEmpty();
- }
-
- // Gets a list of PromptListItems for an Intent array
- private ArrayList<PromptListItem> getItems(final Context context, Intent[] intents) {
- final ArrayList<PromptListItem> items = new ArrayList<PromptListItem>();
-
- // If we have intents, use them to build the initial list
- for (final Intent intent : intents) {
- items.addAll(getItemsForIntent(context, intent));
- }
-
- return items;
- }
-
- // Gets a list of PromptListItems for a GeckoActionProvider
- private ArrayList<PromptListItem> getItems(final Context context, final GeckoActionProvider provider) {
- final ArrayList<PromptListItem> items = new ArrayList<PromptListItem>();
-
- // Add any intents from the provider.
- final PackageManager packageManager = context.getPackageManager();
- final ArrayList<ResolveInfo> infos = provider.getSortedActivities();
-
- for (final ResolveInfo info : infos) {
- items.add(getItemForResolveInfo(info, packageManager, provider.getIntent()));
- }
-
- return items;
- }
-
- private PromptListItem getItemForResolveInfo(ResolveInfo info, PackageManager pm, Intent intent) {
- PromptListItem item = new PromptListItem(info.loadLabel(pm).toString());
- item.setIcon(info.loadIcon(pm));
-
- Intent i = new Intent(intent);
- // These intents should be implicit.
- i.setComponent(new ComponentName(info.activityInfo.applicationInfo.packageName,
- info.activityInfo.name));
- item.setIntent(new Intent(i));
-
- return item;
- }
-
- private ArrayList<PromptListItem> getItemsForIntent(Context context, Intent intent) {
- ArrayList<PromptListItem> items = new ArrayList<PromptListItem>();
- PackageManager pm = context.getPackageManager();
- List<ResolveInfo> lri = pm.queryIntentActivityOptions(GeckoAppShell.getGeckoInterface().getActivity().getComponentName(), null, intent, 0);
-
- // If we didn't find any activities, just return the empty list
- if (lri == null) {
- return items;
- }
-
- // Otherwise, convert the ResolveInfo. Note we don't currently check for duplicates here.
- for (ResolveInfo ri : lri) {
- items.add(getItemForResolveInfo(ri, pm, intent));
- }
-
- return items;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/prompts/IntentHandler.java b/mobile/android/base/java/org/mozilla/gecko/prompts/IntentHandler.java
deleted file mode 100644
index 1509ab626..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/IntentHandler.java
+++ /dev/null
@@ -1,12 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.prompts;
-
-import android.content.Intent;
-
-public interface IntentHandler {
- public void onIntentSelected(Intent intent, int position);
- public void onCancelled();
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/prompts/Prompt.java b/mobile/android/base/java/org/mozilla/gecko/prompts/Prompt.java
deleted file mode 100644
index 11121b2cc..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/Prompt.java
+++ /dev/null
@@ -1,586 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.prompts;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.gfx.LayerView;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
-import android.content.DialogInterface.OnClickListener;
-import android.content.res.Resources;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.ScrollView;
-
-import java.util.ArrayList;
-
-public class Prompt implements OnClickListener, OnCancelListener, OnItemClickListener,
- PromptInput.OnChangeListener, Tabs.OnTabsChangedListener {
- private static final String LOGTAG = "GeckoPromptService";
-
- private String[] mButtons;
- private PromptInput[] mInputs;
- private AlertDialog mDialog;
- private int mDoubleTapButtonType;
-
- private final LayoutInflater mInflater;
- private final Context mContext;
- private PromptCallback mCallback;
- private String mGuid;
- private PromptListAdapter mAdapter;
-
- private static boolean mInitialized;
- private static int mInputPaddingSize;
-
- private int mTabId = Tabs.INVALID_TAB_ID;
- private Object mPreviousInputValue = null;
-
- public Prompt(Context context, PromptCallback callback) {
- this(context);
- mCallback = callback;
- }
-
- private Prompt(Context context) {
- mContext = context;
- mInflater = LayoutInflater.from(mContext);
-
- if (!mInitialized) {
- Resources res = mContext.getResources();
- mInputPaddingSize = (int) (res.getDimension(R.dimen.prompt_service_inputs_padding));
- mInitialized = true;
- }
- }
-
- private View applyInputStyle(View view, PromptInput input) {
- // Don't add padding to color picker views
- if (input.canApplyInputStyle()) {
- view.setPadding(mInputPaddingSize, 0, mInputPaddingSize, 0);
- }
- return view;
- }
-
- public void show(JSONObject message) {
- String title = message.optString("title");
- String text = message.optString("text");
- mGuid = message.optString("guid");
-
- mButtons = getStringArray(message, "buttons");
- final int buttonCount = mButtons == null ? 0 : mButtons.length;
- mDoubleTapButtonType = convertIndexToButtonType(message.optInt("doubleTapButton", -1), buttonCount);
- mPreviousInputValue = null;
-
- JSONArray inputs = getSafeArray(message, "inputs");
- mInputs = new PromptInput[inputs.length()];
- for (int i = 0; i < mInputs.length; i++) {
- try {
- mInputs[i] = PromptInput.getInput(inputs.getJSONObject(i));
- mInputs[i].setListener(this);
- } catch (Exception ex) { }
- }
-
- PromptListItem[] menuitems = PromptListItem.getArray(message.optJSONArray("listitems"));
- String selected = message.optString("choiceMode");
-
- int choiceMode = ListView.CHOICE_MODE_NONE;
- if ("single".equals(selected)) {
- choiceMode = ListView.CHOICE_MODE_SINGLE;
- } else if ("multiple".equals(selected)) {
- choiceMode = ListView.CHOICE_MODE_MULTIPLE;
- }
-
- if (message.has("tabId")) {
- mTabId = message.optInt("tabId", Tabs.INVALID_TAB_ID);
- }
-
- show(title, text, menuitems, choiceMode);
- }
-
- private int convertIndexToButtonType(final int buttonIndex, final int buttonCount) {
- if (buttonIndex < 0 || buttonIndex >= buttonCount) {
- // All valid DialogInterface button values are < 0,
- // so we return 0 as an invalid value.
- return 0;
- }
-
- switch (buttonIndex) {
- case 0:
- return DialogInterface.BUTTON_POSITIVE;
- case 1:
- return DialogInterface.BUTTON_NEUTRAL;
- case 2:
- return DialogInterface.BUTTON_NEGATIVE;
- default:
- return 0;
- }
- }
-
- public void show(String title, String text, PromptListItem[] listItems, int choiceMode) {
- ThreadUtils.assertOnUiThread();
-
- try {
- create(title, text, listItems, choiceMode);
- } catch (IllegalStateException ex) {
- Log.i(LOGTAG, "Error building dialog", ex);
- return;
- }
-
- if (mTabId != Tabs.INVALID_TAB_ID) {
- Tabs.registerOnTabsChangedListener(this);
-
- final Tab tab = Tabs.getInstance().getTab(mTabId);
- if (Tabs.getInstance().getSelectedTab() == tab) {
- mDialog.show();
- }
- } else {
- mDialog.show();
- }
- }
-
- @Override
- public void onTabChanged(final Tab tab, final Tabs.TabEvents msg, final String data) {
- if (tab != Tabs.getInstance().getTab(mTabId)) {
- return;
- }
-
- switch (msg) {
- case SELECTED:
- Log.i(LOGTAG, "Selected");
- mDialog.show();
- break;
- case UNSELECTED:
- Log.i(LOGTAG, "Unselected");
- mDialog.hide();
- break;
- case LOCATION_CHANGE:
- Log.i(LOGTAG, "Location change");
- mDialog.cancel();
- break;
- }
- }
-
- private void create(String title, String text, PromptListItem[] listItems, int choiceMode)
- throws IllegalStateException {
-
- AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
- if (!TextUtils.isEmpty(title)) {
- // Long strings can delay showing the dialog, so we cap the number of characters shown to 256.
- builder.setTitle(title.substring(0, Math.min(title.length(), 256)));
- }
-
- if (!TextUtils.isEmpty(text)) {
- builder.setMessage(text);
- }
-
- // Because lists are currently added through the normal Android AlertBuilder interface, they're
- // incompatible with also adding additional input elements to a dialog.
- if (listItems != null && listItems.length > 0) {
- addListItems(builder, listItems, choiceMode);
- } else if (!addInputs(builder)) {
- throw new IllegalStateException("Could not add inputs to dialog");
- }
-
- int length = mButtons == null ? 0 : mButtons.length;
- if (length > 0) {
- builder.setPositiveButton(mButtons[0], this);
- if (length > 1) {
- builder.setNeutralButton(mButtons[1], this);
- if (length > 2) {
- builder.setNegativeButton(mButtons[2], this);
- }
- }
- }
-
- mDialog = builder.create();
- mDialog.setOnCancelListener(Prompt.this);
- }
-
- public void setButtons(String[] buttons) {
- mButtons = buttons;
- }
-
- public void setInputs(PromptInput[] inputs) {
- mInputs = inputs;
- }
-
- /* Adds to a result value from the lists that can be shown in dialogs.
- * Will set the selected value(s) to the button attribute of the
- * object that's passed in. If this is a multi-select dialog, sets a
- * selected attribute to an array of booleans.
- */
- private void addListResult(final JSONObject result, int which) {
- if (mAdapter == null) {
- return;
- }
-
- try {
- JSONArray selected = new JSONArray();
-
- // If the button has already been filled in
- ArrayList<Integer> selectedItems = mAdapter.getSelected();
- for (Integer item : selectedItems) {
- selected.put(item);
- }
-
- // If we haven't assigned a button yet, or we assigned it to -1, assign the which
- // parameter to both selected and the button.
- if (!result.has("button") || result.optInt("button") == -1) {
- if (!selectedItems.contains(which)) {
- selected.put(which);
- }
-
- result.put("button", which);
- }
-
- result.put("list", selected);
- } catch (JSONException ex) { }
- }
-
- /* Adds to a result value from the inputs that can be shown in dialogs.
- * Each input will set its own value in the result.
- */
- private void addInputValues(final JSONObject result) {
- try {
- if (mInputs != null) {
- for (int i = 0; i < mInputs.length; i++) {
- if (mInputs[i] != null) {
- result.put(mInputs[i].getId(), mInputs[i].getValue());
- }
- }
- }
- } catch (JSONException ex) { }
- }
-
- /* Adds the selected button to a result. This should only be called if there
- * are no lists shown on the dialog, since they also write their results to the button
- * attribute.
- */
- private void addButtonResult(final JSONObject result, int which) {
- int button = -1;
- switch (which) {
- case DialogInterface.BUTTON_POSITIVE : button = 0; break;
- case DialogInterface.BUTTON_NEUTRAL : button = 1; break;
- case DialogInterface.BUTTON_NEGATIVE : button = 2; break;
- }
- try {
- result.put("button", button);
- } catch (JSONException ex) { }
- }
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- ThreadUtils.assertOnUiThread();
- closeDialog(which);
- }
-
- /* Adds a set of list items to the prompt. This can be used for either context menu type dialogs, checked lists,
- * or multiple selection lists.
- *
- * @param builder
- * The alert builder currently building this dialog.
- * @param listItems
- * The items to add.
- * @param choiceMode
- * One of the ListView.CHOICE_MODE constants to designate whether this list shows checkmarks, radios buttons, or nothing.
- */
- private void addListItems(AlertDialog.Builder builder, PromptListItem[] listItems, int choiceMode) {
- switch (choiceMode) {
- case ListView.CHOICE_MODE_MULTIPLE_MODAL:
- case ListView.CHOICE_MODE_MULTIPLE:
- addMultiSelectList(builder, listItems);
- break;
- case ListView.CHOICE_MODE_SINGLE:
- addSingleSelectList(builder, listItems);
- break;
- case ListView.CHOICE_MODE_NONE:
- default:
- addMenuList(builder, listItems);
- }
- }
-
- /* Shows a multi-select list with checkmarks on the side. Android doesn't support using an adapter for
- * multi-choice lists by default so instead we insert our own custom list so that we can do fancy things
- * to the rows like disabling/indenting them.
- *
- * @param builder
- * The alert builder currently building this dialog.
- * @param listItems
- * The items to add.
- */
- private void addMultiSelectList(AlertDialog.Builder builder, PromptListItem[] listItems) {
- ListView listView = (ListView) mInflater.inflate(R.layout.select_dialog_list, null);
- listView.setOnItemClickListener(this);
- listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
-
- mAdapter = new PromptListAdapter(mContext, R.layout.select_dialog_multichoice, listItems);
- listView.setAdapter(mAdapter);
- builder.setView(listView);
- }
-
- /* Shows a single-select list with radio boxes on the side.
- *
- * @param builder
- * the alert builder currently building this dialog.
- * @param listItems
- * The items to add.
- */
- private void addSingleSelectList(AlertDialog.Builder builder, PromptListItem[] listItems) {
- mAdapter = new PromptListAdapter(mContext, R.layout.select_dialog_singlechoice, listItems);
- builder.setSingleChoiceItems(mAdapter, mAdapter.getSelectedIndex(), new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // The adapter isn't aware of single vs. multi choice lists, so manually
- // clear any other selected items first.
- ArrayList<Integer> selected = mAdapter.getSelected();
- for (Integer sel : selected) {
- mAdapter.toggleSelected(sel);
- }
-
- // Now select this item.
- mAdapter.toggleSelected(which);
- closeIfNoButtons(which);
- }
- });
- }
-
- /* Shows a single-select list.
- *
- * @param builder
- * the alert builder currently building this dialog.
- * @param listItems
- * The items to add.
- */
- private void addMenuList(AlertDialog.Builder builder, PromptListItem[] listItems) {
- mAdapter = new PromptListAdapter(mContext, android.R.layout.simple_list_item_1, listItems);
- builder.setAdapter(mAdapter, this);
- }
-
- /* Wraps an input in a linearlayout. We do this so that we can set padding that appears outside the background
- * drawable for the view.
- */
- private View wrapInput(final PromptInput input) {
- final LinearLayout linearLayout = new LinearLayout(mContext);
- linearLayout.setOrientation(LinearLayout.VERTICAL);
- applyInputStyle(linearLayout, input);
-
- linearLayout.addView(input.getView(mContext));
-
- return linearLayout;
- }
-
- /* Add the requested input elements to the dialog.
- *
- * @param builder
- * the alert builder currently building this dialog.
- * @return
- * return true if the inputs were added successfully. This may fail
- * if the requested input is compatible with this Android version.
- */
- private boolean addInputs(AlertDialog.Builder builder) {
- int length = mInputs == null ? 0 : mInputs.length;
- if (length == 0) {
- return true;
- }
-
- try {
- View root = null;
- boolean scrollable = false; // If any of the inputs are scrollable, we won't wrap this in a ScrollView
-
- if (length == 1) {
- root = wrapInput(mInputs[0]);
- scrollable |= mInputs[0].getScrollable();
- } else if (length > 1) {
- LinearLayout linearLayout = new LinearLayout(mContext);
- linearLayout.setOrientation(LinearLayout.VERTICAL);
- for (int i = 0; i < length; i++) {
- View content = wrapInput(mInputs[i]);
- linearLayout.addView(content);
- scrollable |= mInputs[i].getScrollable();
- }
- root = linearLayout;
- }
-
- if (scrollable) {
- // If we're showing some sort of scrollable list, force an inverse background.
- builder.setInverseBackgroundForced(true);
- builder.setView(root);
- } else {
- ScrollView view = new ScrollView(mContext);
- view.addView(root);
- builder.setView(view);
- }
- } catch (Exception ex) {
- Log.e(LOGTAG, "Error showing prompt inputs", ex);
- // We cannot display these input widgets with this sdk version,
- // do not display any dialog and finish the prompt now.
- cancelDialog();
- return false;
- }
-
- return true;
- }
-
- /* AdapterView.OnItemClickListener
- * Called when a list item is clicked
- */
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- ThreadUtils.assertOnUiThread();
- mAdapter.toggleSelected(position);
-
- // If there are no buttons on this dialog, then we take selecting an item as a sign to close
- // the dialog. Note that means it will be hard to select multiple things in this list, but
- // given there is no way to confirm+close the dialog, it seems reasonable.
- closeIfNoButtons(position);
- }
-
- private boolean closeIfNoButtons(int selected) {
- ThreadUtils.assertOnUiThread();
- if (mButtons == null || mButtons.length == 0) {
- closeDialog(selected);
- return true;
- }
- return false;
- }
-
- /* @DialogInterface.OnCancelListener
- * Called when the user hits back to cancel a dialog. The dialog will close itself when this
- * ends. Setup the correct return values here.
- *
- * @param aDialog
- * A dialog interface for the dialog that's being closed.
- */
- @Override
- public void onCancel(DialogInterface aDialog) {
- ThreadUtils.assertOnUiThread();
- cancelDialog();
- }
-
- /* Called in situations where we want to cancel the dialog . This can happen if the user hits back,
- * or if the dialog can't be created because of invalid JSON.
- */
- private void cancelDialog() {
- JSONObject ret = new JSONObject();
- try {
- ret.put("button", -1);
- } catch (Exception ex) { }
- addInputValues(ret);
- notifyClosing(ret);
- }
-
- /* Called any time we're closing the dialog to cleanup and notify listeners that the dialog
- * is closing.
- */
- private void closeDialog(int which) {
- JSONObject ret = new JSONObject();
- mDialog.dismiss();
-
- addButtonResult(ret, which);
- addListResult(ret, which);
- addInputValues(ret);
-
- notifyClosing(ret);
- }
-
- /* Called any time we're closing the dialog to cleanup and notify listeners that the dialog
- * is closing.
- */
- private void notifyClosing(JSONObject aReturn) {
- try {
- aReturn.put("guid", mGuid);
- } catch (JSONException ex) { }
-
- if (mTabId != Tabs.INVALID_TAB_ID) {
- Tabs.unregisterOnTabsChangedListener(this);
- }
-
- if (mCallback != null) {
- mCallback.onPromptFinished(aReturn.toString());
- }
- }
-
- // Called when the prompt inputs on the dialog change
- @Override
- public void onChange(PromptInput input) {
- // If there are no buttons on this dialog, assuming that "changing" an input
- // means something was selected and we can close. This provides a way to tap
- // on a list item and close the dialog automatically.
- if (!closeIfNoButtons(-1)) {
- // Alternatively, if a default button has been specified for double tapping,
- // we want to close the dialog if the same input value has been transmitted
- // twice in a row.
- closeIfDoubleTapEnabled(input.getValue());
- }
- }
-
- private boolean closeIfDoubleTapEnabled(Object inputValue) {
- if (mDoubleTapButtonType != 0 && inputValue == mPreviousInputValue) {
- closeDialog(mDoubleTapButtonType);
- return true;
- }
- mPreviousInputValue = inputValue;
- return false;
- }
-
- private static JSONArray getSafeArray(JSONObject json, String key) {
- try {
- return json.getJSONArray(key);
- } catch (Exception e) {
- return new JSONArray();
- }
- }
-
- public static String[] getStringArray(JSONObject aObject, String aName) {
- JSONArray items = getSafeArray(aObject, aName);
- int length = items.length();
- String[] list = new String[length];
- for (int i = 0; i < length; i++) {
- try {
- list[i] = items.getString(i);
- } catch (Exception ex) { }
- }
- return list;
- }
-
- private static boolean[] getBooleanArray(JSONObject aObject, String aName) {
- JSONArray items = new JSONArray();
- try {
- items = aObject.getJSONArray(aName);
- } catch (Exception ex) { return null; }
- int length = items.length();
- boolean[] list = new boolean[length];
- for (int i = 0; i < length; i++) {
- try {
- list[i] = items.getBoolean(i);
- } catch (Exception ex) { }
- }
- return list;
- }
-
- public interface PromptCallback {
-
- /**
- * Called when the Prompt has been completed (i.e. when the user has selected an item or action in the Prompt).
- * This callback is run on the UI thread.
- */
- public void onPromptFinished(String jsonResult);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/prompts/PromptInput.java b/mobile/android/base/java/org/mozilla/gecko/prompts/PromptInput.java
deleted file mode 100644
index 752f5c24c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/PromptInput.java
+++ /dev/null
@@ -1,398 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.prompts;
-
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-
-import org.json.JSONObject;
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.widget.AllCapsTextView;
-import org.mozilla.gecko.widget.DateTimePicker;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.support.design.widget.TextInputLayout;
-import android.support.v7.widget.AppCompatCheckBox;
-import android.text.Html;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.text.format.DateFormat;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.ArrayAdapter;
-import android.widget.CheckBox;
-import android.widget.DatePicker;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.Spinner;
-import android.widget.TextView;
-import android.widget.TimePicker;
-
-public abstract class PromptInput {
- protected final String mLabel;
- protected final String mType;
- protected final String mId;
- protected final String mValue;
- protected final String mMinValue;
- protected final String mMaxValue;
- protected OnChangeListener mListener;
- protected View mView;
- public static final String LOGTAG = "GeckoPromptInput";
-
- public interface OnChangeListener {
- void onChange(PromptInput input);
- }
-
- public void setListener(OnChangeListener listener) {
- mListener = listener;
- }
-
- public static class EditInput extends PromptInput {
- protected final String mHint;
- protected final boolean mAutofocus;
- public static final String INPUT_TYPE = "textbox";
-
- public EditInput(JSONObject object) {
- super(object);
- mHint = object.optString("hint");
- mAutofocus = object.optBoolean("autofocus");
- }
-
- @Override
- public View getView(final Context context) throws UnsupportedOperationException {
- EditText input = new EditText(context);
- input.setInputType(InputType.TYPE_CLASS_TEXT);
- input.setText(mValue);
-
- if (!TextUtils.isEmpty(mHint)) {
- input.setHint(mHint);
- }
-
- if (mAutofocus) {
- input.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus) {
- ((InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE)).showSoftInput(v, 0);
- }
- }
- });
- input.requestFocus();
- }
-
- TextInputLayout inputLayout = new TextInputLayout(context);
- inputLayout.addView(input);
-
- mView = (View) inputLayout;
- return mView;
- }
-
- @Override
- public Object getValue() {
- final TextInputLayout inputLayout = (TextInputLayout) mView;
- return inputLayout.getEditText().getText();
- }
- }
-
- public static class NumberInput extends EditInput {
- public static final String INPUT_TYPE = "number";
- public NumberInput(JSONObject obj) {
- super(obj);
- }
-
- @Override
- public View getView(final Context context) throws UnsupportedOperationException {
- final TextInputLayout inputLayout = (TextInputLayout) super.getView(context);
- final EditText input = inputLayout.getEditText();
- input.setRawInputType(Configuration.KEYBOARD_12KEY);
- input.setInputType(InputType.TYPE_CLASS_NUMBER |
- InputType.TYPE_NUMBER_FLAG_SIGNED);
- return input;
- }
- }
-
- public static class PasswordInput extends EditInput {
- public static final String INPUT_TYPE = "password";
- public PasswordInput(JSONObject obj) {
- super(obj);
- }
-
- @Override
- public View getView(Context context) throws UnsupportedOperationException {
- final TextInputLayout inputLayout = (TextInputLayout) super.getView(context);
- inputLayout.getEditText().setInputType(InputType.TYPE_CLASS_TEXT |
- InputType.TYPE_TEXT_VARIATION_PASSWORD |
- InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
- return inputLayout;
- }
- }
-
- public static class CheckboxInput extends PromptInput {
- public static final String INPUT_TYPE = "checkbox";
- private final boolean mChecked;
-
- public CheckboxInput(JSONObject obj) {
- super(obj);
- mChecked = obj.optBoolean("checked");
- }
-
- @Override
- public View getView(Context context) throws UnsupportedOperationException {
- final CheckBox checkbox = new AppCompatCheckBox(context);
- checkbox.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
- checkbox.setText(mLabel);
- checkbox.setChecked(mChecked);
- mView = (View)checkbox;
- return mView;
- }
-
- @Override
- public Object getValue() {
- CheckBox checkbox = (CheckBox)mView;
- return checkbox.isChecked() ? Boolean.TRUE : Boolean.FALSE;
- }
- }
-
- public static class DateTimeInput extends PromptInput {
- public static final String[] INPUT_TYPES = new String[] {
- "date",
- "week",
- "time",
- "datetime-local",
- "datetime",
- "month"
- };
-
- public DateTimeInput(JSONObject obj) {
- super(obj);
- }
-
- @Override
- public View getView(Context context) throws UnsupportedOperationException {
- if (mType.equals("date")) {
- try {
- DateTimePicker input = new DateTimePicker(context, "yyyy-MM-dd", mValue,
- DateTimePicker.PickersState.DATE, mMinValue, mMaxValue);
- input.toggleCalendar(true);
- mView = (View)input;
- } catch (UnsupportedOperationException ex) {
- // We can't use our custom version of the DatePicker widget because the sdk is too old.
- // But we can fallback on the native one.
- DatePicker input = new DatePicker(context);
- try {
- if (!TextUtils.isEmpty(mValue)) {
- GregorianCalendar calendar = new GregorianCalendar();
- calendar.setTime(new SimpleDateFormat("yyyy-MM-dd").parse(mValue));
- input.updateDate(calendar.get(Calendar.YEAR),
- calendar.get(Calendar.MONTH),
- calendar.get(Calendar.DAY_OF_MONTH));
- }
- } catch (Exception e) {
- Log.e(LOGTAG, "error parsing format string: " + e);
- }
- mView = (View)input;
- }
- } else if (mType.equals("week")) {
- DateTimePicker input = new DateTimePicker(context, "yyyy-'W'ww", mValue,
- DateTimePicker.PickersState.WEEK, mMinValue, mMaxValue);
- mView = (View)input;
- } else if (mType.equals("time")) {
- TimePicker input = new TimePicker(context);
- input.setIs24HourView(DateFormat.is24HourFormat(context));
-
- GregorianCalendar calendar = new GregorianCalendar();
- if (!TextUtils.isEmpty(mValue)) {
- try {
- calendar.setTime(new SimpleDateFormat("HH:mm").parse(mValue));
- } catch (Exception e) { }
- }
- input.setCurrentHour(calendar.get(GregorianCalendar.HOUR_OF_DAY));
- input.setCurrentMinute(calendar.get(GregorianCalendar.MINUTE));
- mView = (View)input;
- } else if (mType.equals("datetime-local") || mType.equals("datetime")) {
- DateTimePicker input = new DateTimePicker(context, "yyyy-MM-dd HH:mm", mValue.replace("T", " ").replace("Z", ""),
- DateTimePicker.PickersState.DATETIME,
- mMinValue.replace("T", " ").replace("Z", ""), mMaxValue.replace("T", " ").replace("Z", ""));
- input.toggleCalendar(true);
- mView = (View)input;
- } else if (mType.equals("month")) {
- DateTimePicker input = new DateTimePicker(context, "yyyy-MM", mValue,
- DateTimePicker.PickersState.MONTH, mMinValue, mMaxValue);
- mView = (View)input;
- }
- return mView;
- }
-
- private static String formatDateString(String dateFormat, Calendar calendar) {
- return new SimpleDateFormat(dateFormat).format(calendar.getTime());
- }
-
- @Override
- public Object getValue() {
- if (mType.equals("time")) {
- TimePicker tp = (TimePicker)mView;
- GregorianCalendar calendar =
- new GregorianCalendar(0, 0, 0, tp.getCurrentHour(), tp.getCurrentMinute());
- return formatDateString("HH:mm", calendar);
- } else {
- DateTimePicker dp = (DateTimePicker)mView;
- GregorianCalendar calendar = new GregorianCalendar();
- calendar.setTimeInMillis(dp.getTimeInMillis());
- if (mType.equals("date")) {
- return formatDateString("yyyy-MM-dd", calendar);
- } else if (mType.equals("week")) {
- return formatDateString("yyyy-'W'ww", calendar);
- } else if (mType.equals("datetime-local")) {
- return formatDateString("yyyy-MM-dd'T'HH:mm", calendar);
- } else if (mType.equals("datetime")) {
- calendar.set(GregorianCalendar.ZONE_OFFSET, 0);
- calendar.setTimeInMillis(dp.getTimeInMillis());
- return formatDateString("yyyy-MM-dd'T'HH:mm'Z'", calendar);
- } else if (mType.equals("month")) {
- return formatDateString("yyyy-MM", calendar);
- }
- }
- return super.getValue();
- }
- }
-
- public static class MenulistInput extends PromptInput {
- public static final String INPUT_TYPE = "menulist";
- private static String[] mListitems;
- private static int mSelected;
-
- public Spinner spinner;
- public AllCapsTextView textView;
-
- public MenulistInput(JSONObject obj) {
- super(obj);
- mListitems = Prompt.getStringArray(obj, "values");
- mSelected = obj.optInt("selected");
- }
-
- @Override
- public View getView(final Context context) throws UnsupportedOperationException {
- spinner = new Spinner(context, Spinner.MODE_DIALOG);
- try {
- if (mListitems.length > 0) {
- ArrayAdapter<String> adapter = new ArrayAdapter<String>(context, android.R.layout.simple_spinner_item, mListitems);
- adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-
- spinner.setAdapter(adapter);
- spinner.setSelection(mSelected);
- }
- } catch (Exception ex) {
- }
-
- if (!TextUtils.isEmpty(mLabel)) {
- LinearLayout container = new LinearLayout(context);
- container.setOrientation(LinearLayout.VERTICAL);
-
- textView = new AllCapsTextView(context, null);
- textView.setText(mLabel);
- container.addView(textView);
-
- container.addView(spinner);
- return container;
- }
-
- return spinner;
- }
-
- @Override
- public Object getValue() {
- return spinner.getSelectedItemPosition();
- }
- }
-
- public static class LabelInput extends PromptInput {
- public static final String INPUT_TYPE = "label";
- public LabelInput(JSONObject obj) {
- super(obj);
- }
-
- @Override
- public View getView(Context context) throws UnsupportedOperationException {
- // not really an input, but a way to add labels and such to the dialog
- TextView view = new TextView(context);
- view.setText(Html.fromHtml(mLabel));
- mView = view;
- return mView;
- }
- }
-
- public PromptInput(JSONObject obj) {
- mLabel = obj.optString("label");
- mType = obj.optString("type");
- String id = obj.optString("id");
- mId = TextUtils.isEmpty(id) ? mType : id;
- mValue = obj.optString("value");
- mMaxValue = obj.optString("max");
- mMinValue = obj.optString("min");
- }
-
- public static PromptInput getInput(JSONObject obj) {
- String type = obj.optString("type");
- switch (type) {
- case EditInput.INPUT_TYPE:
- return new EditInput(obj);
- case NumberInput.INPUT_TYPE:
- return new NumberInput(obj);
- case PasswordInput.INPUT_TYPE:
- return new PasswordInput(obj);
- case CheckboxInput.INPUT_TYPE:
- return new CheckboxInput(obj);
- case MenulistInput.INPUT_TYPE:
- return new MenulistInput(obj);
- case LabelInput.INPUT_TYPE:
- return new LabelInput(obj);
- case IconGridInput.INPUT_TYPE:
- return new IconGridInput(obj);
- case ColorPickerInput.INPUT_TYPE:
- return new ColorPickerInput(obj);
- case TabInput.INPUT_TYPE:
- return new TabInput(obj);
- default:
- for (String dtType : DateTimeInput.INPUT_TYPES) {
- if (dtType.equals(type)) {
- return new DateTimeInput(obj);
- }
- }
-
- break;
- }
-
- return null;
- }
-
- public abstract View getView(Context context) throws UnsupportedOperationException;
-
- public String getId() {
- return mId;
- }
-
- public Object getValue() {
- return null;
- }
-
- public boolean getScrollable() {
- return false;
- }
-
- public boolean canApplyInputStyle() {
- return true;
- }
-
- protected void notifyListeners(String val) {
- if (mListener != null) {
- mListener.onChange(this);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/prompts/PromptListAdapter.java b/mobile/android/base/java/org/mozilla/gecko/prompts/PromptListAdapter.java
deleted file mode 100644
index 720086c92..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/PromptListAdapter.java
+++ /dev/null
@@ -1,281 +0,0 @@
-package org.mozilla.gecko.prompts;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.menu.MenuItemSwitcherLayout;
-import org.mozilla.gecko.widget.GeckoActionProvider;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CheckedTextView;
-import android.widget.TextView;
-import android.widget.ListView;
-import android.widget.ArrayAdapter;
-import android.util.TypedValue;
-
-import java.util.ArrayList;
-
-public class PromptListAdapter extends ArrayAdapter<PromptListItem> {
- private static final int VIEW_TYPE_ITEM = 0;
- private static final int VIEW_TYPE_GROUP = 1;
- private static final int VIEW_TYPE_ACTIONS = 2;
- private static final int VIEW_TYPE_COUNT = 3;
-
- private static final String LOGTAG = "GeckoPromptListAdapter";
-
- private final int mResourceId;
- private Drawable mBlankDrawable;
- private Drawable mMoreDrawable;
- private static int mGroupPaddingSize;
- private static int mLeftRightTextWithIconPadding;
- private static int mTopBottomTextWithIconPadding;
- private static int mIconSize;
- private static int mMinRowSize;
- private static int mIconTextPadding;
- private static float mTextSize;
- private static boolean mInitialized;
-
- PromptListAdapter(Context context, int textViewResourceId, PromptListItem[] objects) {
- super(context, textViewResourceId, objects);
- mResourceId = textViewResourceId;
- init();
- }
-
- private void init() {
- if (!mInitialized) {
- Resources res = getContext().getResources();
- mGroupPaddingSize = (int) (res.getDimension(R.dimen.prompt_service_group_padding_size));
- mLeftRightTextWithIconPadding = (int) (res.getDimension(R.dimen.prompt_service_left_right_text_with_icon_padding));
- mTopBottomTextWithIconPadding = (int) (res.getDimension(R.dimen.prompt_service_top_bottom_text_with_icon_padding));
- mIconTextPadding = (int) (res.getDimension(R.dimen.prompt_service_icon_text_padding));
- mIconSize = (int) (res.getDimension(R.dimen.prompt_service_icon_size));
- mMinRowSize = (int) (res.getDimension(R.dimen.menu_item_row_height));
- mTextSize = res.getDimension(R.dimen.menu_item_textsize);
-
- mInitialized = true;
- }
- }
-
- @Override
- public int getItemViewType(int position) {
- PromptListItem item = getItem(position);
- if (item.isGroup) {
- return VIEW_TYPE_GROUP;
- } else if (item.showAsActions) {
- return VIEW_TYPE_ACTIONS;
- } else {
- return VIEW_TYPE_ITEM;
- }
- }
-
- @Override
- public int getViewTypeCount() {
- return VIEW_TYPE_COUNT;
- }
-
- private Drawable getMoreDrawable(Resources res) {
- if (mMoreDrawable == null) {
- mMoreDrawable = res.getDrawable(R.drawable.menu_item_more);
- }
- return mMoreDrawable;
- }
-
- private Drawable getBlankDrawable(Resources res) {
- if (mBlankDrawable == null) {
- mBlankDrawable = res.getDrawable(R.drawable.blank);
- }
- return mBlankDrawable;
- }
-
- public void toggleSelected(int position) {
- PromptListItem item = getItem(position);
- item.setSelected(!item.getSelected());
- }
-
- private void maybeUpdateIcon(PromptListItem item, TextView t) {
- if (item.getIcon() == null && !item.inGroup && !item.isParent) {
- t.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
- return;
- }
-
- Drawable d = null;
- Resources res = getContext().getResources();
- // Set the padding between the icon and the text.
- t.setCompoundDrawablePadding(mIconTextPadding);
- if (item.getIcon() != null) {
- // We want the icon to be of a specific size. Some do not
- // follow this rule so we have to resize them.
- Bitmap bitmap = ((BitmapDrawable) item.getIcon()).getBitmap();
- d = new BitmapDrawable(res, Bitmap.createScaledBitmap(bitmap, mIconSize, mIconSize, true));
- } else if (item.inGroup) {
- // We don't currently support "indenting" items with icons
- d = getBlankDrawable(res);
- }
-
- Drawable moreDrawable = null;
- if (item.isParent) {
- moreDrawable = getMoreDrawable(res);
- }
-
- if (d != null || moreDrawable != null) {
- t.setCompoundDrawablesWithIntrinsicBounds(d, null, moreDrawable, null);
- }
- }
-
- private void maybeUpdateCheckedState(ListView list, int position, PromptListItem item, ViewHolder viewHolder) {
- viewHolder.textView.setEnabled(!item.disabled && !item.isGroup);
- viewHolder.textView.setClickable(item.isGroup || item.disabled);
- if (viewHolder.textView instanceof CheckedTextView) {
- // Apparently just using ct.setChecked(true) doesn't work, so this
- // is stolen from the android source code as a way to set the checked
- // state of these items
- list.setItemChecked(position, item.getSelected());
- }
- }
-
- boolean isSelected(int position) {
- return getItem(position).getSelected();
- }
-
- ArrayList<Integer> getSelected() {
- int length = getCount();
-
- ArrayList<Integer> selected = new ArrayList<Integer>();
- for (int i = 0; i < length; i++) {
- if (isSelected(i)) {
- selected.add(i);
- }
- }
-
- return selected;
- }
-
- int getSelectedIndex() {
- int length = getCount();
- for (int i = 0; i < length; i++) {
- if (isSelected(i)) {
- return i;
- }
- }
- return -1;
- }
-
- private View getActionView(PromptListItem item, final ListView list, final int position) {
- final GeckoActionProvider provider = GeckoActionProvider.getForType(item.getIntent().getType(), getContext());
- provider.setIntent(item.getIntent());
-
- final MenuItemSwitcherLayout view = (MenuItemSwitcherLayout) provider.onCreateActionView(
- GeckoActionProvider.ActionViewType.CONTEXT_MENU);
- // If a quickshare button is clicked, we need to close the dialog.
- view.addActionButtonClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- ListView.OnItemClickListener listener = list.getOnItemClickListener();
- if (listener != null) {
- listener.onItemClick(list, view, position, position);
- }
- }
- });
-
- return view;
- }
-
- private void updateActionView(final PromptListItem item, final MenuItemSwitcherLayout view, final ListView list, final int position) {
- view.setTitle(item.label);
- view.setIcon(item.getIcon());
- view.setSubMenuIndicator(item.isParent);
-
- // If the share button is clicked, we need to close the dialog and then show an intent chooser
- view.setMenuItemClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- ListView.OnItemClickListener listener = list.getOnItemClickListener();
- if (listener != null) {
- listener.onItemClick(list, view, position, position);
- }
-
- final GeckoActionProvider provider = GeckoActionProvider.getForType(item.getIntent().getType(), getContext());
- IntentChooserPrompt prompt = new IntentChooserPrompt(getContext(), provider);
- prompt.show(item.label, getContext(), new IntentHandler() {
- @Override
- public void onIntentSelected(final Intent intent, final int p) {
- provider.chooseActivity(p);
-
- // Context: Sharing via content contextmenu list (no explicit session is active)
- Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "promptlist");
- }
-
- @Override
- public void onCancelled() {
- // do nothing
- }
- });
- }
- });
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- PromptListItem item = getItem(position);
- int type = getItemViewType(position);
- ViewHolder viewHolder = null;
-
- if (convertView == null) {
- if (type == VIEW_TYPE_ACTIONS) {
- convertView = getActionView(item, (ListView) parent, position);
- } else {
- int resourceId = mResourceId;
- if (item.isGroup) {
- resourceId = R.layout.list_item_header;
- }
-
- LayoutInflater mInflater = LayoutInflater.from(getContext());
- convertView = mInflater.inflate(resourceId, null);
- convertView.setMinimumHeight(mMinRowSize);
-
- TextView tv = (TextView) convertView.findViewById(android.R.id.text1);
- tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
- viewHolder = new ViewHolder(tv, tv.getPaddingLeft(), tv.getPaddingRight(),
- tv.getPaddingTop(), tv.getPaddingBottom());
-
- convertView.setTag(viewHolder);
- }
- } else {
- viewHolder = (ViewHolder) convertView.getTag();
- }
-
- if (type == VIEW_TYPE_ACTIONS) {
- updateActionView(item, (MenuItemSwitcherLayout) convertView, (ListView) parent, position);
- } else {
- viewHolder.textView.setText(item.label);
- maybeUpdateCheckedState((ListView) parent, position, item, viewHolder);
- maybeUpdateIcon(item, viewHolder.textView);
- }
-
- return convertView;
- }
-
- private static class ViewHolder {
- public final TextView textView;
- public final int paddingLeft;
- public final int paddingRight;
- public final int paddingTop;
- public final int paddingBottom;
-
- ViewHolder(TextView aTextView, int aLeft, int aRight, int aTop, int aBottom) {
- textView = aTextView;
- paddingLeft = aLeft;
- paddingRight = aRight;
- paddingTop = aTop;
- paddingBottom = aBottom;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/prompts/PromptListItem.java b/mobile/android/base/java/org/mozilla/gecko/prompts/PromptListItem.java
deleted file mode 100644
index 48ace735c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/PromptListItem.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package org.mozilla.gecko.prompts;
-
-import org.json.JSONException;
-import org.mozilla.gecko.IntentHelper;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.ThumbnailHelper;
-import org.mozilla.gecko.util.ResourceDrawableUtils;
-import org.mozilla.gecko.widget.GeckoActionProvider;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-
-import java.util.List;
-import java.util.ArrayList;
-
-// This class should die and be replaced with normal menu items
-public class PromptListItem {
- private static final String LOGTAG = "GeckoPromptListItem";
- public final String label;
- public final boolean isGroup;
- public final boolean inGroup;
- public final boolean disabled;
- public final int id;
- public final boolean showAsActions;
- public final boolean isParent;
-
- public Intent mIntent;
- public boolean mSelected;
- public Drawable mIcon;
-
- PromptListItem(JSONObject aObject) {
- Context context = GeckoAppShell.getContext();
- label = aObject.isNull("label") ? "" : aObject.optString("label");
- isGroup = aObject.optBoolean("isGroup");
- inGroup = aObject.optBoolean("inGroup");
- disabled = aObject.optBoolean("disabled");
- id = aObject.optInt("id");
- mSelected = aObject.optBoolean("selected");
-
- JSONObject obj = aObject.optJSONObject("showAsActions");
- if (obj != null) {
- showAsActions = true;
- String uri = obj.isNull("uri") ? "" : obj.optString("uri");
- String type = obj.isNull("type") ? GeckoActionProvider.DEFAULT_MIME_TYPE :
- obj.optString("type", GeckoActionProvider.DEFAULT_MIME_TYPE);
-
- mIntent = IntentHelper.getShareIntent(context, uri, type, "");
- isParent = true;
- } else {
- mIntent = null;
- showAsActions = false;
- // Support both "isParent" (backwards compat for older consumers), and "menu" for the new Tabbed prompt ui.
- isParent = aObject.optBoolean("isParent") || aObject.optBoolean("menu");
- }
-
- final String iconStr = aObject.optString("icon");
- if (iconStr != null) {
- final ResourceDrawableUtils.BitmapLoader loader = new ResourceDrawableUtils.BitmapLoader() {
- @Override
- public void onBitmapFound(Drawable d) {
- mIcon = d;
- }
- };
-
- if (iconStr.startsWith("thumbnail:")) {
- final int id = Integer.parseInt(iconStr.substring(10), 10);
- ThumbnailHelper.getInstance().getAndProcessThumbnailFor(id, loader);
- } else {
- ResourceDrawableUtils.getDrawable(context, iconStr, loader);
- }
- }
- }
-
- public void setIntent(Intent i) {
- mIntent = i;
- }
-
- public Intent getIntent() {
- return mIntent;
- }
-
- public void setIcon(Drawable icon) {
- mIcon = icon;
- }
-
- public Drawable getIcon() {
- return mIcon;
- }
-
- public void setSelected(boolean selected) {
- mSelected = selected;
- }
-
- public boolean getSelected() {
- return mSelected;
- }
-
- public PromptListItem(String aLabel) {
- label = aLabel;
- isGroup = false;
- inGroup = false;
- isParent = false;
- disabled = false;
- id = 0;
- showAsActions = false;
- }
-
- static PromptListItem[] getArray(JSONArray items) {
- if (items == null) {
- return new PromptListItem[0];
- }
-
- int length = items.length();
- List<PromptListItem> list = new ArrayList<>(length);
- for (int i = 0; i < length; i++) {
- try {
- PromptListItem item = new PromptListItem(items.getJSONObject(i));
- list.add(item);
- } catch (JSONException ex) { }
- }
-
- return list.toArray(new PromptListItem[length]);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/prompts/PromptService.java b/mobile/android/base/java/org/mozilla/gecko/prompts/PromptService.java
deleted file mode 100644
index 8155cc1c6..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/PromptService.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.prompts;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.content.Context;
-import android.util.Log;
-
-public class PromptService implements GeckoEventListener {
- private static final String LOGTAG = "GeckoPromptService";
-
- private final Context mContext;
-
- public PromptService(Context context) {
- GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
- "Prompt:Show",
- "Prompt:ShowTop");
- mContext = context;
- }
-
- public void destroy() {
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
- "Prompt:Show",
- "Prompt:ShowTop");
- }
-
- public void show(final String aTitle, final String aText, final PromptListItem[] aMenuList,
- final int aChoiceMode, final Prompt.PromptCallback callback) {
- // The dialog must be created on the UI thread.
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- Prompt p;
- p = new Prompt(mContext, callback);
- p.show(aTitle, aText, aMenuList, aChoiceMode);
- }
- });
- }
-
- // GeckoEventListener implementation
- @Override
- public void handleMessage(String event, final JSONObject message) {
- // The dialog must be created on the UI thread.
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- Prompt p;
- p = new Prompt(mContext, new Prompt.PromptCallback() {
- @Override
- public void onPromptFinished(String jsonResult) {
- try {
- EventDispatcher.sendResponse(message, new JSONObject(jsonResult));
- } catch (JSONException ex) {
- Log.i(LOGTAG, "Error building json response", ex);
- }
- }
- });
- p.show(message);
- }
- });
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/prompts/TabInput.java b/mobile/android/base/java/org/mozilla/gecko/prompts/TabInput.java
deleted file mode 100644
index ab490e79c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/TabInput.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.prompts;
-
-import java.util.LinkedHashMap;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.content.Context;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.ListView;
-import android.widget.TabHost;
-import android.widget.TextView;
-
-public class TabInput extends PromptInput implements AdapterView.OnItemClickListener {
- public static final String INPUT_TYPE = "tabs";
- public static final String LOGTAG = "GeckoTabInput";
-
- /* Keeping the order of this in sync with the JSON is important. */
- final private LinkedHashMap<String, PromptListItem[]> mTabs;
-
- private TabHost mHost;
- private int mPosition;
-
- public TabInput(JSONObject obj) {
- super(obj);
- mTabs = new LinkedHashMap<String, PromptListItem[]>();
- try {
- JSONArray tabs = obj.getJSONArray("items");
- for (int i = 0; i < tabs.length(); i++) {
- JSONObject tab = tabs.getJSONObject(i);
- String title = tab.getString("label");
- JSONArray items = tab.getJSONArray("items");
- mTabs.put(title, PromptListItem.getArray(items));
- }
- } catch (JSONException ex) {
- Log.e(LOGTAG, "Exception", ex);
- }
- }
-
- @Override
- public View getView(final Context context) throws UnsupportedOperationException {
- final LayoutInflater inflater = LayoutInflater.from(context);
- mHost = (TabHost) inflater.inflate(R.layout.tab_prompt_input, null);
- mHost.setup();
-
- for (String title : mTabs.keySet()) {
- final TabHost.TabSpec spec = mHost.newTabSpec(title);
- spec.setContent(new TabHost.TabContentFactory() {
- @Override
- public View createTabContent(final String tag) {
- PromptListAdapter adapter = new PromptListAdapter(context, android.R.layout.simple_list_item_1, mTabs.get(tag));
- ListView listView = new ListView(context);
- listView.setCacheColorHint(0);
- listView.setOnItemClickListener(TabInput.this);
- listView.setAdapter(adapter);
- return listView;
- }
- });
-
- spec.setIndicator(title);
- mHost.addTab(spec);
- }
- mView = mHost;
- return mHost;
- }
-
- @Override
- public Object getValue() {
- JSONObject obj = new JSONObject();
- try {
- obj.put("tab", mHost.getCurrentTab());
- obj.put("item", mPosition);
- } catch (JSONException ex) { }
-
- return obj;
- }
-
- @Override
- public boolean getScrollable() {
- return true;
- }
-
- @Override
- public boolean canApplyInputStyle() {
- return false;
- }
-
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- ThreadUtils.assertOnUiThread();
- mPosition = position;
- notifyListeners(Integer.toString(position));
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/push/Fetched.java b/mobile/android/base/java/org/mozilla/gecko/push/Fetched.java
deleted file mode 100644
index 42a7c6a90..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/push/Fetched.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.push;
-
-import android.support.annotation.NonNull;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * Pair a (String) value with a timestamp. The timestamp is usually when the
- * value was fetched from a remote service or when the value was locally
- * generated.
- *
- * It's awkward to serialize generic values to JSON -- that requires lots of
- * factory classes -- so we specialize to String instances.
- */
-public class Fetched {
- public final String value;
- public final long timestamp;
-
- public Fetched(String value, long timestamp) {
- this.value = value;
- this.timestamp = timestamp;
- }
-
- public static Fetched now(String value) {
- return new Fetched(value, System.currentTimeMillis());
- }
-
- public static @NonNull Fetched fromJSONObject(@NonNull JSONObject json) {
- final String value = json.optString("value", null);
- final String timestampString = json.optString("timestamp", null);
- final long timestamp = timestampString != null ? Long.valueOf(timestampString) : 0L;
- return new Fetched(value, timestamp);
- }
-
- public JSONObject toJSONObject() throws JSONException {
- final JSONObject jsonObject = new JSONObject();
- if (value != null) {
- jsonObject.put("value", value);
- } else {
- jsonObject.remove("value");
- }
- jsonObject.put("timestamp", Long.toString(timestamp));
- return jsonObject;
- }
-
- @Override
- public boolean equals(Object o) {
- // Auto-generated.
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- Fetched fetched = (Fetched) o;
-
- if (timestamp != fetched.timestamp) return false;
- return !(value != null ? !value.equals(fetched.value) : fetched.value != null);
-
- }
-
- @Override
- public int hashCode() {
- // Auto-generated.
- int result = value != null ? value.hashCode() : 0;
- result = 31 * result + (int) (timestamp ^ (timestamp >>> 32));
- return result;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/push/PushClient.java b/mobile/android/base/java/org/mozilla/gecko/push/PushClient.java
deleted file mode 100644
index 9c1fab5f9..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/push/PushClient.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.push;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import org.mozilla.gecko.push.RegisterUserAgentResponse;
-import org.mozilla.gecko.push.SubscribeChannelResponse;
-import org.mozilla.gecko.push.autopush.AutopushClient;
-import org.mozilla.gecko.push.autopush.AutopushClientException;
-import org.mozilla.gecko.sync.Utils;
-
-import java.util.concurrent.Executor;
-
-/**
- * This class bridges the autopush client, which is written in callback style, with the Fennec
- * push implementation, which is written in a linear style. It handles returning results and
- * re-throwing exceptions passed as messages.
- * <p/>
- * TODO: fold this into the autopush client directly.
- */
-public class PushClient {
- public static class LocalException extends Exception {
- private static final long serialVersionUID = 2387554736L;
-
- public LocalException(Throwable throwable) {
- super(throwable);
- }
- }
-
- private final AutopushClient autopushClient;
-
- public PushClient(String serverURI) {
- this.autopushClient = new AutopushClient(serverURI, Utils.newSynchronousExecutor());
- }
-
- /**
- * Each instance is <b>single-use</b>! Exactly one delegate method should be invoked once,
- * but we take care to handle multiple invocations (favoring the earliest), just to be safe.
- */
- protected static class Delegate<T> implements AutopushClient.RequestDelegate<T> {
- Object result; // Oh, for an algebraic data type when you need one!
-
- @SuppressWarnings("unchecked")
- public T responseOrThrow() throws LocalException, AutopushClientException {
- if (result instanceof LocalException) {
- throw (LocalException) result;
- }
- if (result instanceof AutopushClientException) {
- throw (AutopushClientException) result;
- }
- return (T) result;
- }
-
- @Override
- public void handleError(Exception e) {
- if (result == null) {
- result = new LocalException(e);
- }
- }
-
- @Override
- public void handleFailure(AutopushClientException e) {
- if (result == null) {
- result = e;
- }
- }
-
- @Override
- public void handleSuccess(T response) {
- if (result == null) {
- result = response;
- }
- }
- }
-
- public RegisterUserAgentResponse registerUserAgent(@NonNull String token) throws LocalException, AutopushClientException {
- final Delegate<RegisterUserAgentResponse> delegate = new Delegate<>();
- autopushClient.registerUserAgent(token, delegate);
- return delegate.responseOrThrow();
- }
-
- public void reregisterUserAgent(@NonNull String uaid, @NonNull String secret, @NonNull String token) throws LocalException, AutopushClientException {
- final Delegate<Void> delegate = new Delegate<>();
- autopushClient.reregisterUserAgent(uaid, secret, token, delegate);
- delegate.responseOrThrow(); // For side-effects only.
- }
-
- public void unregisterUserAgent(@NonNull String uaid, @NonNull String secret) throws LocalException, AutopushClientException {
- final Delegate<Void> delegate = new Delegate<>();
- autopushClient.unregisterUserAgent(uaid, secret, delegate);
- delegate.responseOrThrow(); // For side-effects only.
- }
-
- public SubscribeChannelResponse subscribeChannel(@NonNull String uaid, @NonNull String secret, @Nullable String appServerKey) throws LocalException, AutopushClientException {
- final Delegate<SubscribeChannelResponse> delegate = new Delegate<>();
- autopushClient.subscribeChannel(uaid, secret, appServerKey, delegate);
- return delegate.responseOrThrow();
- }
-
- public void unsubscribeChannel(@NonNull String uaid, @NonNull String secret, @NonNull String chid) throws LocalException, AutopushClientException {
- final Delegate<Void> delegate = new Delegate<>();
- autopushClient.unsubscribeChannel(uaid, secret, chid, delegate);
- delegate.responseOrThrow(); // For side-effects only.
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/push/PushManager.java b/mobile/android/base/java/org/mozilla/gecko/push/PushManager.java
deleted file mode 100644
index 42ef60b61..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/push/PushManager.java
+++ /dev/null
@@ -1,354 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.push;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.util.Log;
-
-import org.json.JSONObject;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.gcm.GcmTokenClient;
-import org.mozilla.gecko.push.autopush.AutopushClientException;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * The push manager advances push registrations, ensuring that the upstream autopush endpoint has
- * a fresh GCM token. It brokers channel subscription requests to the upstream and maintains
- * local state.
- * <p/>
- * This class is not thread safe. An individual instance should be accessed on a single
- * (background) thread.
- */
-public class PushManager {
- public static final long TIME_BETWEEN_AUTOPUSH_UAID_REGISTRATION_IN_MILLIS = 7 * 24 * 60 * 60 * 1000L; // One week.
-
- public static class ProfileNeedsConfigurationException extends Exception {
- private static final long serialVersionUID = 3326738888L;
-
- public ProfileNeedsConfigurationException() {
- super();
- }
- }
-
- private static final String LOG_TAG = "GeckoPushManager";
-
- protected final @NonNull PushState state;
- protected final @NonNull GcmTokenClient gcmClient;
- protected final @NonNull PushClientFactory pushClientFactory;
-
- // For testing only.
- public interface PushClientFactory {
- PushClient getPushClient(String autopushEndpoint, boolean debug);
- }
-
- public PushManager(@NonNull PushState state, @NonNull GcmTokenClient gcmClient, @NonNull PushClientFactory pushClientFactory) {
- this.state = state;
- this.gcmClient = gcmClient;
- this.pushClientFactory = pushClientFactory;
- }
-
- public PushRegistration registrationForSubscription(String chid) {
- // chids are globally unique, so we're not concerned about finding a chid associated to
- // any particular profile.
- for (Map.Entry<String, PushRegistration> entry : state.getRegistrations().entrySet()) {
- final PushSubscription subscription = entry.getValue().getSubscription(chid);
- if (subscription != null) {
- return entry.getValue();
- }
- }
- return null;
- }
-
- public Map<String, PushSubscription> allSubscriptionsForProfile(String profileName) {
- final PushRegistration registration = state.getRegistration(profileName);
- if (registration == null) {
- return Collections.emptyMap();
- }
- return Collections.unmodifiableMap(registration.subscriptions);
- }
-
- public PushRegistration registerUserAgent(final @NonNull String profileName, final long now) throws ProfileNeedsConfigurationException, AutopushClientException, PushClient.LocalException, GcmTokenClient.NeedsGooglePlayServicesException, IOException {
- Log.i(LOG_TAG, "Registering user agent for profile named: " + profileName);
- return advanceRegistration(profileName, now);
- }
-
- public PushRegistration unregisterUserAgent(final @NonNull String profileName, final long now) throws ProfileNeedsConfigurationException {
- Log.i(LOG_TAG, "Unregistering user agent for profile named: " + profileName);
-
- final PushRegistration registration = state.getRegistration(profileName);
- if (registration == null) {
- Log.w(LOG_TAG, "Cannot find registration corresponding to subscription; not unregistering remote uaid for profileName: " + profileName);
- return null;
- }
-
- final String uaid = registration.uaid.value;
- final String secret = registration.secret;
- if (uaid == null || secret == null) {
- Log.e(LOG_TAG, "Cannot unregisterUserAgent with null registration uaid or secret!");
- return null;
- }
-
- unregisterUserAgentOnBackgroundThread(registration);
- return registration;
- }
-
- public PushSubscription subscribeChannel(final @NonNull String profileName, final @NonNull String service, final @NonNull JSONObject serviceData, @Nullable String appServerKey, final long now) throws ProfileNeedsConfigurationException, AutopushClientException, PushClient.LocalException, GcmTokenClient.NeedsGooglePlayServicesException, IOException {
- Log.i(LOG_TAG, "Subscribing to channel for service: " + service + "; for profile named: " + profileName);
- final PushRegistration registration = advanceRegistration(profileName, now);
- final PushSubscription subscription = subscribeChannel(registration, profileName, service, serviceData, appServerKey, System.currentTimeMillis());
- return subscription;
- }
-
- protected PushSubscription subscribeChannel(final @NonNull PushRegistration registration, final @NonNull String profileName, final @NonNull String service, final @NonNull JSONObject serviceData, @Nullable String appServerKey, final long now) throws AutopushClientException, PushClient.LocalException {
- final String uaid = registration.uaid.value;
- final String secret = registration.secret;
- if (uaid == null || secret == null) {
- throw new IllegalStateException("Cannot subscribeChannel with null uaid or secret!");
- }
-
- // Verify endpoint is not null?
- final PushClient pushClient = pushClientFactory.getPushClient(registration.autopushEndpoint, registration.debug);
-
- final SubscribeChannelResponse result = pushClient.subscribeChannel(uaid, secret, appServerKey);
- if (registration.debug) {
- Log.i(LOG_TAG, "Got chid: " + result.channelID + " and endpoint: " + result.endpoint);
- } else {
- Log.i(LOG_TAG, "Got chid and endpoint.");
- }
-
- final PushSubscription subscription = new PushSubscription(result.channelID, profileName, result.endpoint, service, serviceData);
- registration.putSubscription(result.channelID, subscription);
- state.checkpoint();
-
- return subscription;
- }
-
- public PushSubscription unsubscribeChannel(final @NonNull String chid) {
- Log.i(LOG_TAG, "Unsubscribing from channel with chid: " + chid);
-
- final PushRegistration registration = registrationForSubscription(chid);
- if (registration == null) {
- Log.w(LOG_TAG, "Cannot find registration corresponding to subscription; not unregistering remote subscription: " + chid);
- return null;
- }
-
- // We remove the local subscription before the remote subscription: without the local
- // subscription we'll ignoring incoming messages, and after some amount of time the
- // server will expire the channel due to non-activity. This is also Desktop's approach.
- final PushSubscription subscription = registration.removeSubscription(chid);
- state.checkpoint();
-
- if (subscription == null) {
- // This should never happen.
- Log.e(LOG_TAG, "Subscription did not exist: " + chid);
- return null;
- }
-
- final String uaid = registration.uaid.value;
- final String secret = registration.secret;
- if (uaid == null || secret == null) {
- Log.e(LOG_TAG, "Cannot unsubscribeChannel with null registration uaid or secret!");
- return null;
- }
-
- final PushClient pushClient = pushClientFactory.getPushClient(registration.autopushEndpoint, registration.debug);
- // Fire and forget.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- try {
- pushClient.unsubscribeChannel(registration.uaid.value, registration.secret, chid);
- Log.i(LOG_TAG, "Unsubscribed from channel with chid: " + chid);
- } catch (PushClient.LocalException | AutopushClientException e) {
- Log.w(LOG_TAG, "Failed to unsubscribe from channel with chid; ignoring: " + chid, e);
- }
- }
- });
-
- return subscription;
- }
-
- public PushRegistration configure(final @NonNull String profileName, final @NonNull String endpoint, final boolean debug, final long now) {
- Log.i(LOG_TAG, "Updating configuration.");
- final PushRegistration registration = state.getRegistration(profileName);
- final PushRegistration newRegistration;
- if (registration != null) {
- if (!endpoint.equals(registration.autopushEndpoint)) {
- if (debug) {
- Log.i(LOG_TAG, "Push configuration autopushEndpoint changed! Was: " + registration.autopushEndpoint + "; now: " + endpoint);
- } else {
- Log.i(LOG_TAG, "Push configuration autopushEndpoint changed!");
- }
-
- newRegistration = new PushRegistration(endpoint, debug, Fetched.now(null), null);
-
- if (registration.uaid.value != null) {
- // New endpoint! All registrations and subscriptions have been dropped, and
- // should be removed remotely.
- unregisterUserAgentOnBackgroundThread(registration);
- }
- } else if (debug != registration.debug) {
- Log.i(LOG_TAG, "Push configuration debug changed: " + debug);
- newRegistration = registration.withDebug(debug);
- } else {
- newRegistration = registration;
- }
- } else {
- if (debug) {
- Log.i(LOG_TAG, "Push configuration set: " + endpoint + "; debug: " + debug);
- } else {
- Log.i(LOG_TAG, "Push configuration set!");
- }
- newRegistration = new PushRegistration(endpoint, debug, new Fetched(null, now), null);
- }
-
- if (newRegistration != registration) {
- state.putRegistration(profileName, newRegistration);
- state.checkpoint();
- }
-
- return newRegistration;
- }
-
- private void unregisterUserAgentOnBackgroundThread(final PushRegistration registration) {
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- try {
- pushClientFactory.getPushClient(registration.autopushEndpoint, registration.debug).unregisterUserAgent(registration.uaid.value, registration.secret);
- Log.i(LOG_TAG, "Unregistered user agent with uaid: " + registration.uaid.value);
- } catch (PushClient.LocalException | AutopushClientException e) {
- Log.w(LOG_TAG, "Failed to unregister user agent with uaid; ignoring: " + registration.uaid.value, e);
- }
- }
- });
- }
-
- protected @NonNull PushRegistration advanceRegistration(final @NonNull String profileName, final long now) throws ProfileNeedsConfigurationException, AutopushClientException, PushClient.LocalException, GcmTokenClient.NeedsGooglePlayServicesException, IOException {
- final PushRegistration registration = state.getRegistration(profileName);
- if (registration == null || registration.autopushEndpoint == null) {
- Log.i(LOG_TAG, "Cannot advance to registered: registration needs configuration.");
- throw new ProfileNeedsConfigurationException();
- }
- return advanceRegistration(registration, profileName, now);
- }
-
- protected @NonNull PushRegistration advanceRegistration(final PushRegistration registration, final @NonNull String profileName, final long now) throws AutopushClientException, PushClient.LocalException, GcmTokenClient.NeedsGooglePlayServicesException, IOException {
- final Fetched gcmToken = gcmClient.getToken(AppConstants.MOZ_ANDROID_GCM_SENDERID, registration.debug);
-
- final PushClient pushClient = pushClientFactory.getPushClient(registration.autopushEndpoint, registration.debug);
-
- if (registration.uaid.value == null) {
- if (registration.debug) {
- Log.i(LOG_TAG, "No uaid; requesting from autopush endpoint: " + registration.autopushEndpoint);
- } else {
- Log.i(LOG_TAG, "No uaid: requesting from autopush endpoint.");
- }
- final RegisterUserAgentResponse result = pushClient.registerUserAgent(gcmToken.value);
- if (registration.debug) {
- Log.i(LOG_TAG, "Got uaid: " + result.uaid + " and secret: " + result.secret);
- } else {
- Log.i(LOG_TAG, "Got uaid and secret.");
- }
- final long nextNow = System.currentTimeMillis();
- final PushRegistration nextRegistration = registration.withUserAgentID(result.uaid, result.secret, nextNow);
- state.putRegistration(profileName, nextRegistration);
- state.checkpoint();
- return advanceRegistration(nextRegistration, profileName, nextNow);
- }
-
- if (registration.uaid.timestamp + TIME_BETWEEN_AUTOPUSH_UAID_REGISTRATION_IN_MILLIS < now
- || registration.uaid.timestamp < gcmToken.timestamp) {
- if (registration.debug) {
- Log.i(LOG_TAG, "Stale uaid; re-registering with autopush endpoint: " + registration.autopushEndpoint);
- } else {
- Log.i(LOG_TAG, "Stale uaid: re-registering with autopush endpoint.");
- }
-
- pushClient.reregisterUserAgent(registration.uaid.value, registration.secret, gcmToken.value);
-
- Log.i(LOG_TAG, "Re-registered uaid and secret.");
- final long nextNow = System.currentTimeMillis();
- final PushRegistration nextRegistration = registration.withUserAgentID(registration.uaid.value, registration.secret, nextNow);
- state.putRegistration(profileName, nextRegistration);
- state.checkpoint();
- return advanceRegistration(nextRegistration, profileName, nextNow);
- }
-
- Log.d(LOG_TAG, "Existing uaid is fresh; no need to request from autopush endpoint.");
- return registration;
- }
-
- public void invalidateGcmToken() {
- gcmClient.invalidateToken();
- }
-
- public void startup(long now) {
- try {
- Log.i(LOG_TAG, "Startup: requesting GCM token.");
- gcmClient.getToken(AppConstants.MOZ_ANDROID_GCM_SENDERID, false); // For side-effects.
- } catch (GcmTokenClient.NeedsGooglePlayServicesException e) {
- // Requires user intervention. At App startup, we don't want to address this. In
- // response to user activity, we do want to try to have the user address this.
- Log.w(LOG_TAG, "Startup: needs Google Play Services. Ignoring until GCM is requested in response to user activity.");
- return;
- } catch (IOException e) {
- // We're temporarily unable to get a GCM token. There's nothing to be done; we'll
- // try to advance the App's state in response to user activity or at next startup.
- Log.w(LOG_TAG, "Startup: Google Play Services is available, but we can't get a token; ignoring.", e);
- return;
- }
-
- Log.i(LOG_TAG, "Startup: advancing all registrations.");
- final Map<String, PushRegistration> registrations = state.getRegistrations();
-
- // Now advance all registrations.
- try {
- final Iterator<Map.Entry<String, PushRegistration>> it = registrations.entrySet().iterator();
- while (it.hasNext()) {
- final Map.Entry<String, PushRegistration> entry = it.next();
- final String profileName = entry.getKey();
- final PushRegistration registration = entry.getValue();
- if (registration.subscriptions.isEmpty()) {
- Log.i(LOG_TAG, "Startup: no subscriptions for profileName; not advancing registration: " + profileName);
- continue;
- }
-
- try {
- advanceRegistration(profileName, now); // For side-effects.
- Log.i(LOG_TAG, "Startup: advanced registration for profileName: " + profileName);
- } catch (ProfileNeedsConfigurationException e) {
- Log.i(LOG_TAG, "Startup: cannot advance registration for profileName: " + profileName + "; profile needs configuration from Gecko.");
- } catch (AutopushClientException e) {
- if (e.isTransientError()) {
- Log.w(LOG_TAG, "Startup: cannot advance registration for profileName: " + profileName + "; got transient autopush error. Ignoring; will advance on demand.", e);
- } else {
- Log.w(LOG_TAG, "Startup: cannot advance registration for profileName: " + profileName + "; got permanent autopush error. Removing registration entirely.", e);
- it.remove();
- }
- } catch (PushClient.LocalException e) {
- Log.w(LOG_TAG, "Startup: cannot advance registration for profileName: " + profileName + "; got local exception. Ignoring; will advance on demand.", e);
- }
- }
- } catch (GcmTokenClient.NeedsGooglePlayServicesException e) {
- Log.w(LOG_TAG, "Startup: cannot advance any registrations; need Google Play Services!", e);
- return;
- } catch (IOException e) {
- Log.w(LOG_TAG, "Startup: cannot advance any registrations; intermittent Google Play Services exception; ignoring, will advance on demand.", e);
- return;
- }
-
- // We may have removed registrations above. Checkpoint just to be safe!
- state.checkpoint();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/push/PushRegistration.java b/mobile/android/base/java/org/mozilla/gecko/push/PushRegistration.java
deleted file mode 100644
index a991774ff..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/push/PushRegistration.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.push;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * Represent an autopush User Agent registration.
- * <p/>
- * Such a registration associates an endpoint, optional debug flag, some Google
- * Cloud Messaging data, and the returned uaid and secret.
- * <p/>
- * Each registration is associated to a single Gecko profile, although we don't
- * enforce that here. This class is immutable, so it is by definition
- * thread-safe.
- */
-public class PushRegistration {
- public final String autopushEndpoint;
- public final boolean debug;
- // TODO: fold (timestamp, {uaid, secret}) into this class.
- public final @NonNull Fetched uaid;
- public final String secret;
-
- protected final @NonNull Map<String, PushSubscription> subscriptions;
-
- public PushRegistration(String autopushEndpoint, boolean debug, @NonNull Fetched uaid, @Nullable String secret, @NonNull Map<String, PushSubscription> subscriptions) {
- this.autopushEndpoint = autopushEndpoint;
- this.debug = debug;
- this.uaid = uaid;
- this.secret = secret;
- this.subscriptions = subscriptions;
- }
-
- public PushRegistration(String autopushEndpoint, boolean debug, @NonNull Fetched uaid, @Nullable String secret) {
- this(autopushEndpoint, debug, uaid, secret, new HashMap<String, PushSubscription>());
- }
-
- public JSONObject toJSONObject() throws JSONException {
- final JSONObject subscriptions = new JSONObject();
- for (Map.Entry<String, PushSubscription> entry : this.subscriptions.entrySet()) {
- subscriptions.put(entry.getKey(), entry.getValue().toJSONObject());
- }
-
- final JSONObject jsonObject = new JSONObject();
- jsonObject.put("autopushEndpoint", autopushEndpoint);
- jsonObject.put("debug", debug);
- jsonObject.put("uaid", uaid.toJSONObject());
- jsonObject.put("secret", secret);
- jsonObject.put("subscriptions", subscriptions);
- return jsonObject;
- }
-
- public static PushRegistration fromJSONObject(@NonNull JSONObject registration) throws JSONException {
- final String endpoint = registration.optString("autopushEndpoint", null);
- final boolean debug = registration.getBoolean("debug");
- final Fetched uaid = Fetched.fromJSONObject(registration.getJSONObject("uaid"));
- final String secret = registration.optString("secret", null);
-
- final JSONObject subscriptionsObject = registration.getJSONObject("subscriptions");
- final Map<String, PushSubscription> subscriptions = new HashMap<>();
- final Iterator<String> it = subscriptionsObject.keys();
- while (it.hasNext()) {
- final String chid = it.next();
- subscriptions.put(chid, PushSubscription.fromJSONObject(subscriptionsObject.getJSONObject(chid)));
- }
-
- return new PushRegistration(endpoint, debug, uaid, secret, subscriptions);
- }
-
- @Override
- public boolean equals(Object o) {
- // Auto-generated.
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- PushRegistration that = (PushRegistration) o;
-
- if (autopushEndpoint != null ? !autopushEndpoint.equals(that.autopushEndpoint) : that.autopushEndpoint != null)
- return false;
- if (!uaid.equals(that.uaid)) return false;
- if (secret != null ? !secret.equals(that.secret) : that.secret != null) return false;
- if (subscriptions != null ? !subscriptions.equals(that.subscriptions) : that.subscriptions != null) return false;
- return (debug == that.debug);
- }
-
- @Override
- public int hashCode() {
- // Auto-generated.
- int result = autopushEndpoint != null ? autopushEndpoint.hashCode() : 0;
- result = 31 * result + (debug ? 1 : 0);
- result = 31 * result + uaid.hashCode();
- result = 31 * result + (secret != null ? secret.hashCode() : 0);
- result = 31 * result + (subscriptions != null ? subscriptions.hashCode() : 0);
- return result;
- }
-
- public PushRegistration withDebug(boolean debug) {
- return new PushRegistration(this.autopushEndpoint, debug, this.uaid, this.secret, this.subscriptions);
- }
-
- public PushRegistration withUserAgentID(String uaid, String secret, long nextNow) {
- return new PushRegistration(this.autopushEndpoint, this.debug, new Fetched(uaid, nextNow), secret, this.subscriptions);
- }
-
- public PushSubscription getSubscription(@NonNull String chid) {
- return subscriptions.get(chid);
- }
-
- public PushSubscription putSubscription(@NonNull String chid, @NonNull PushSubscription subscription) {
- return subscriptions.put(chid, subscription);
- }
-
- public PushSubscription removeSubscription(@NonNull String chid) {
- return subscriptions.remove(chid);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/push/PushService.java b/mobile/android/base/java/org/mozilla/gecko/push/PushService.java
deleted file mode 100644
index 7c3a9434f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/push/PushService.java
+++ /dev/null
@@ -1,440 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.push;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.util.Log;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.GeckoService;
-import org.mozilla.gecko.GeckoThread;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.annotation.ReflectionTarget;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.gcm.GcmTokenClient;
-import org.mozilla.gecko.push.autopush.AutopushClientException;
-import org.mozilla.gecko.util.BundleEventListener;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Class that handles messages used in the Google Cloud Messaging and DOM push API integration.
- * <p/>
- * This singleton services Gecko messages from dom/push/PushServiceAndroidGCM.jsm and Google Cloud
- * Messaging requests.
- * <p/>
- * It is expected that Gecko is started (if not already running) soon after receiving GCM messages
- * otherwise there is a greater risk that pending messages that have not been handle by Gecko will
- * be lost if this service is killed.
- * <p/>
- * It's worth noting that we allow the DOM push API in restricted profiles.
- */
-@ReflectionTarget
-public class PushService implements BundleEventListener {
- private static final String LOG_TAG = "GeckoPushService";
-
- public static final String SERVICE_WEBPUSH = "webpush";
- public static final String SERVICE_FXA = "fxa";
-
- private static PushService sInstance;
-
- private static final String[] GECKO_EVENTS = new String[] {
- "PushServiceAndroidGCM:Configure",
- "PushServiceAndroidGCM:DumpRegistration",
- "PushServiceAndroidGCM:DumpSubscriptions",
- "PushServiceAndroidGCM:Initialized",
- "PushServiceAndroidGCM:Uninitialized",
- "PushServiceAndroidGCM:RegisterUserAgent",
- "PushServiceAndroidGCM:UnregisterUserAgent",
- "PushServiceAndroidGCM:SubscribeChannel",
- "PushServiceAndroidGCM:UnsubscribeChannel",
- "History:GetPrePathLastVisitedTimeMilliseconds",
- };
-
- private enum GeckoComponent {
- PushServiceAndroidGCM
- }
-
- public static synchronized PushService getInstance(Context context) {
- if (sInstance == null) {
- onCreate(context);
- }
- return sInstance;
- }
-
- @ReflectionTarget
- public static synchronized void onCreate(Context context) {
- if (sInstance != null) {
- return;
- }
- sInstance = new PushService(context);
-
- sInstance.registerGeckoEventListener();
- sInstance.onStartup();
- }
-
- protected final PushManager pushManager;
-
- // NB: These are not thread-safe, we're depending on these being access from the same background thread.
- private boolean isReadyPushServiceAndroidGCM = false;
- private final List<JSONObject> pendingPushMessages;
-
- public PushService(Context context) {
- pushManager = new PushManager(new PushState(context, "GeckoPushState.json"), new GcmTokenClient(context), new PushManager.PushClientFactory() {
- @Override
- public PushClient getPushClient(String autopushEndpoint, boolean debug) {
- return new PushClient(autopushEndpoint);
- }
- });
-
- pendingPushMessages = new LinkedList<>();
- }
-
- public void onStartup() {
- Log.i(LOG_TAG, "Starting up.");
- ThreadUtils.assertOnBackgroundThread();
-
- try {
- pushManager.startup(System.currentTimeMillis());
- } catch (Exception e) {
- Log.e(LOG_TAG, "Got exception during startup; ignoring.", e);
- return;
- }
- }
-
- public void onRefresh() {
- Log.i(LOG_TAG, "Google Play Services requested GCM token refresh; invalidating GCM token and running startup again.");
- ThreadUtils.assertOnBackgroundThread();
-
- pushManager.invalidateGcmToken();
- try {
- pushManager.startup(System.currentTimeMillis());
- } catch (Exception e) {
- Log.e(LOG_TAG, "Got exception during refresh; ignoring.", e);
- return;
- }
- }
-
- public void onMessageReceived(final @NonNull Context context, final @NonNull Bundle bundle) {
- Log.i(LOG_TAG, "Google Play Services GCM message received; delivering.");
- ThreadUtils.assertOnBackgroundThread();
-
- final String chid = bundle.getString("chid");
- if (chid == null) {
- Log.w(LOG_TAG, "No chid found; ignoring message.");
- return;
- }
-
- final PushRegistration registration = pushManager.registrationForSubscription(chid);
- if (registration == null) {
- Log.w(LOG_TAG, "Cannot find registration corresponding to subscription for chid: " + chid + "; ignoring message.");
- return;
- }
-
- final PushSubscription subscription = registration.getSubscription(chid);
- if (subscription == null) {
- // This should never happen. There's not much to be done; in the future, perhaps we
- // could try to drop the remote subscription?
- Log.e(LOG_TAG, "No subscription found for chid: " + chid + "; ignoring message.");
- return;
- }
-
- boolean isWebPush = SERVICE_WEBPUSH.equals(subscription.service);
- boolean isFxAPush = SERVICE_FXA.equals(subscription.service);
- if (!isWebPush && !isFxAPush) {
- Log.e(LOG_TAG, "Message directed to unknown service; dropping: " + subscription.service);
- return;
- }
-
- Log.i(LOG_TAG, "Message directed to service: " + subscription.service);
-
- if (subscription.serviceData == null) {
- Log.e(LOG_TAG, "No serviceData found for chid: " + chid + "; ignoring dom/push message.");
- return;
- }
-
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.SERVICE, "dom-push-api");
-
- final String profileName = subscription.serviceData.optString("profileName", null);
- final String profilePath = subscription.serviceData.optString("profilePath", null);
- if (profileName == null || profilePath == null) {
- Log.e(LOG_TAG, "Corrupt serviceData found for chid: " + chid + "; ignoring dom/push message.");
- return;
- }
-
- if (canSendPushMessagesToGecko()) {
- if (!GeckoThread.canUseProfile(profileName, new File(profilePath))) {
- Log.e(LOG_TAG, "Mismatched profile for chid: " + chid + "; ignoring dom/push message.");
- return;
- }
- } else {
- final Intent intent = GeckoService.getIntentToCreateServices(context, "android-push-service");
- GeckoService.setIntentProfile(intent, profileName, profilePath);
- context.startService(intent);
- }
-
- final JSONObject data = new JSONObject();
- try {
- data.put("channelID", chid);
- data.put("con", bundle.getString("con"));
- data.put("enc", bundle.getString("enc"));
- // Only one of cryptokey (newer) and enckey (deprecated) should be set, but the
- // Gecko handler will verify this.
- data.put("cryptokey", bundle.getString("cryptokey"));
- data.put("enckey", bundle.getString("enckey"));
- data.put("message", bundle.getString("body"));
-
- if (!canSendPushMessagesToGecko()) {
- data.put("profileName", profileName);
- data.put("profilePath", profilePath);
- data.put("service", subscription.service);
- }
- } catch (JSONException e) {
- Log.e(LOG_TAG, "Got exception delivering dom/push message to Gecko!", e);
- return;
- }
-
- if (!canSendPushMessagesToGecko()) {
- Log.i(LOG_TAG, "Required service not initialized, adding message to queue.");
- pendingPushMessages.add(data);
- return;
- }
-
- if (isWebPush) {
- sendMessageToGeckoService(data);
- } else {
- sendMessageToDecodeToGeckoService(data);
- }
- }
-
- protected static void sendMessageToGeckoService(final @NonNull JSONObject message) {
- Log.i(LOG_TAG, "Delivering dom/push message to Gecko!");
- GeckoAppShell.notifyObservers("PushServiceAndroidGCM:ReceivedPushMessage",
- message.toString(),
- GeckoThread.State.PROFILE_READY);
- }
-
- protected static void sendMessageToDecodeToGeckoService(final @NonNull JSONObject message) {
- Log.i(LOG_TAG, "Delivering dom/push message to decode to Gecko!");
- }
-
- protected void registerGeckoEventListener() {
- Log.d(LOG_TAG, "Registered Gecko event listener.");
- EventDispatcher.getInstance().registerBackgroundThreadListener(this, GECKO_EVENTS);
- }
-
- protected void unregisterGeckoEventListener() {
- Log.d(LOG_TAG, "Unregistered Gecko event listener.");
- EventDispatcher.getInstance().unregisterBackgroundThreadListener(this, GECKO_EVENTS);
- }
-
- @Override
- public void handleMessage(final String event, final Bundle message, final EventCallback callback) {
- Log.i(LOG_TAG, "Handling event: " + event);
- ThreadUtils.assertOnBackgroundThread();
-
- final Context context = GeckoAppShell.getApplicationContext();
- // We're invoked in response to a Gecko message on a background thread. We should always
- // be able to safely retrieve the current Gecko profile.
- final GeckoProfile geckoProfile = GeckoProfile.get(context);
-
- if (callback == null) {
- Log.e(LOG_TAG, "callback must not be null in " + event);
- return;
- }
-
- try {
- if ("PushServiceAndroidGCM:Initialized".equals(event)) {
- processComponentState(GeckoComponent.PushServiceAndroidGCM, true);
- callback.sendSuccess(null);
- return;
- }
- if ("PushServiceAndroidGCM:Uninitialized".equals(event)) {
- processComponentState(GeckoComponent.PushServiceAndroidGCM, false);
- callback.sendSuccess(null);
- return;
- }
- if ("PushServiceAndroidGCM:Configure".equals(event)) {
- final String endpoint = message.getString("endpoint");
- if (endpoint == null) {
- callback.sendError("endpoint must not be null in " + event);
- return;
- }
- final boolean debug = message.getBoolean("debug", false);
- pushManager.configure(geckoProfile.getName(), endpoint, debug, System.currentTimeMillis()); // For side effects.
- callback.sendSuccess(null);
- return;
- }
- if ("PushServiceAndroidGCM:DumpRegistration".equals(event)) {
- // In the future, this might be used to interrogate the Java Push Manager
- // registration state from JavaScript.
- callback.sendError("Not yet implemented!");
- return;
- }
- if ("PushServiceAndroidGCM:DumpSubscriptions".equals(event)) {
- try {
- final Map<String, PushSubscription> result = pushManager.allSubscriptionsForProfile(geckoProfile.getName());
-
- final JSONObject json = new JSONObject();
- for (Map.Entry<String, PushSubscription> entry : result.entrySet()) {
- json.put(entry.getKey(), entry.getValue().toJSONObject());
- }
- callback.sendSuccess(json);
- } catch (JSONException e) {
- callback.sendError("Got exception handling message [" + event + "]: " + e.toString());
- }
- return;
- }
- if ("PushServiceAndroidGCM:RegisterUserAgent".equals(event)) {
- try {
- pushManager.registerUserAgent(geckoProfile.getName(), System.currentTimeMillis()); // For side-effects.
- callback.sendSuccess(null);
- } catch (PushManager.ProfileNeedsConfigurationException | AutopushClientException | PushClient.LocalException | IOException e) {
- Log.e(LOG_TAG, "Got exception in " + event, e);
- callback.sendError("Got exception handling message [" + event + "]: " + e.toString());
- }
- return;
- }
- if ("PushServiceAndroidGCM:UnregisterUserAgent".equals(event)) {
- // In the future, this might be used to tell the Java Push Manager to unregister
- // a User Agent entirely from JavaScript. Right now, however, everything is
- // subscription based; there's no concept of unregistering all subscriptions
- // simultaneously.
- callback.sendError("Not yet implemented!");
- return;
- }
- if ("PushServiceAndroidGCM:SubscribeChannel".equals(event)) {
- final String service = SERVICE_FXA.equals(message.getString("service")) ?
- SERVICE_FXA :
- SERVICE_WEBPUSH;
- final JSONObject serviceData;
- final String appServerKey = message.getString("appServerKey");
- try {
- serviceData = new JSONObject();
- serviceData.put("profileName", geckoProfile.getName());
- serviceData.put("profilePath", geckoProfile.getDir().getAbsolutePath());
- } catch (JSONException e) {
- Log.e(LOG_TAG, "Got exception in " + event, e);
- callback.sendError("Got exception handling message [" + event + "]: " + e.toString());
- return;
- }
-
- final PushSubscription subscription;
- try {
- subscription = pushManager.subscribeChannel(geckoProfile.getName(), service, serviceData, appServerKey, System.currentTimeMillis());
- } catch (PushManager.ProfileNeedsConfigurationException | AutopushClientException | PushClient.LocalException | IOException e) {
- Log.e(LOG_TAG, "Got exception in " + event, e);
- callback.sendError("Got exception handling message [" + event + "]: " + e.toString());
- return;
- }
-
- final JSONObject json = new JSONObject();
- try {
- json.put("channelID", subscription.chid);
- json.put("endpoint", subscription.webpushEndpoint);
- } catch (JSONException e) {
- Log.e(LOG_TAG, "Got exception in " + event, e);
- callback.sendError("Got exception handling message [" + event + "]: " + e.toString());
- return;
- }
-
- Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.SERVICE, "dom-push-api");
- callback.sendSuccess(json);
- return;
- }
- if ("PushServiceAndroidGCM:UnsubscribeChannel".equals(event)) {
- final String channelID = message.getString("channelID");
- if (channelID == null) {
- callback.sendError("channelID must not be null in " + event);
- return;
- }
-
- // Fire and forget. See comments in the function itself.
- final PushSubscription pushSubscription = pushManager.unsubscribeChannel(channelID);
- if (pushSubscription != null) {
- Telemetry.sendUIEvent(TelemetryContract.Event.UNSAVE, TelemetryContract.Method.SERVICE, "dom-push-api");
- callback.sendSuccess(null);
- return;
- }
-
- callback.sendError("Could not unsubscribe from channel: " + channelID);
- return;
- }
- if ("History:GetPrePathLastVisitedTimeMilliseconds".equals(event)) {
- if (callback == null) {
- Log.e(LOG_TAG, "callback must not be null in " + event);
- return;
- }
- final String prePath = message.getString("prePath");
- if (prePath == null) {
- callback.sendError("prePath must not be null in " + event);
- return;
- }
- // We're on a background thread, so we can be synchronous.
- final long millis = BrowserDB.from(geckoProfile).getPrePathLastVisitedTimeMilliseconds(
- context.getContentResolver(), prePath);
- callback.sendSuccess(millis);
- return;
- }
- } catch (GcmTokenClient.NeedsGooglePlayServicesException e) {
- // TODO: improve this. Can we find a point where the user is *definitely* interacting
- // with the WebPush? Perhaps we can show a dialog when interacting with the Push
- // permissions, and then be more aggressive showing this notification when we have
- // registrations and subscriptions that can't be advanced.
- callback.sendError("To handle event [" + event + "], user interaction is needed to enable Google Play Services.");
- }
- }
-
- private void processComponentState(@NonNull GeckoComponent component, boolean isReady) {
- if (component == GeckoComponent.PushServiceAndroidGCM) {
- isReadyPushServiceAndroidGCM = isReady;
- }
-
- // Send all pending messages to Gecko.
- if (canSendPushMessagesToGecko()) {
- sendPushMessagesToGecko(pendingPushMessages);
- pendingPushMessages.clear();
- }
- }
-
- private boolean canSendPushMessagesToGecko() {
- return isReadyPushServiceAndroidGCM;
- }
-
- private static void sendPushMessagesToGecko(@NonNull List<JSONObject> messages) {
- for (JSONObject pushMessage : messages) {
- final String profileName = pushMessage.optString("profileName", null);
- final String profilePath = pushMessage.optString("profilePath", null);
- final String service = pushMessage.optString("service", null);
- if (profileName == null || profilePath == null ||
- !GeckoThread.canUseProfile(profileName, new File(profilePath))) {
- Log.e(LOG_TAG, "Mismatched profile for chid: " +
- pushMessage.optString("channelID") +
- "; ignoring dom/push message.");
- continue;
- }
- if (SERVICE_WEBPUSH.equals(service)) {
- sendMessageToGeckoService(pushMessage);
- } else if (SERVICE_FXA.equals(service)) {
- sendMessageToDecodeToGeckoService(pushMessage);
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/push/PushState.java b/mobile/android/base/java/org/mozilla/gecko/push/PushState.java
deleted file mode 100644
index 686bf5a0d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/push/PushState.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.push;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.WorkerThread;
-import android.support.v4.util.AtomicFile;
-import android.util.Log;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * Firefox for Android maintains an App-wide mapping associating
- * profile names to push registrations. Each push registration in turn associates channels to
- * push subscriptions.
- * <p/>
- * We use a simple storage model of JSON backed by an atomic file. It is assumed that instances
- * of this class will reference distinct files on disk; and that all accesses will be happen on a
- * single (worker thread).
- */
-public class PushState {
- private static final String LOG_TAG = "GeckoPushState";
-
- private static final long VERSION = 1L;
-
- protected final @NonNull AtomicFile file;
-
- protected final @NonNull Map<String, PushRegistration> registrations;
-
- public PushState(Context context, @NonNull String fileName) {
- this.registrations = new HashMap<>();
-
- file = new AtomicFile(new File(context.getApplicationInfo().dataDir, fileName));
- synchronized (file) {
- try {
- final String s = new String(file.readFully(), "UTF-8");
- final JSONObject temp = new JSONObject(s);
- if (temp.optLong("version", 0L) != VERSION) {
- throw new JSONException("Unknown version!");
- }
-
- final JSONObject registrationsObject = temp.getJSONObject("registrations");
- final Iterator<String> it = registrationsObject.keys();
- while (it.hasNext()) {
- final String profileName = it.next();
- final PushRegistration registration = PushRegistration.fromJSONObject(registrationsObject.getJSONObject(profileName));
- this.registrations.put(profileName, registration);
- }
- } catch (FileNotFoundException e) {
- Log.i(LOG_TAG, "No storage found; starting fresh.");
- this.registrations.clear();
- } catch (IOException | JSONException e) {
- Log.w(LOG_TAG, "Got exception reading storage; dropping storage and starting fresh.", e);
- this.registrations.clear();
- }
- }
- }
-
- public JSONObject toJSONObject() throws JSONException {
- final JSONObject registrations = new JSONObject();
- for (Map.Entry<String, PushRegistration> entry : this.registrations.entrySet()) {
- registrations.put(entry.getKey(), entry.getValue().toJSONObject());
- }
-
- final JSONObject jsonObject = new JSONObject();
- jsonObject.put("version", 1L);
- jsonObject.put("registrations", registrations);
- return jsonObject;
- }
-
- /**
- * Synchronously persist the cache to disk.
- * @return whether the cache was persisted successfully.
- */
- @WorkerThread
- public boolean checkpoint() {
- synchronized (file) {
- FileOutputStream fileOutputStream = null;
- try {
- fileOutputStream = file.startWrite();
- fileOutputStream.write(toJSONObject().toString().getBytes("UTF-8"));
- file.finishWrite(fileOutputStream);
- return true;
- } catch (JSONException | IOException e) {
- Log.e(LOG_TAG, "Got exception writing JSON storage; ignoring.", e);
- if (fileOutputStream != null) {
- file.failWrite(fileOutputStream);
- }
- return false;
- }
- }
- }
-
- public PushRegistration putRegistration(@NonNull String profileName, @NonNull PushRegistration registration) {
- return registrations.put(profileName, registration);
- }
-
- /**
- * Return the existing push registration for the given profile name.
- * @return the push registration, if one is registered; null otherwise.
- */
- public PushRegistration getRegistration(@NonNull String profileName) {
- return registrations.get(profileName);
- }
-
- /**
- * Return all push registrations, keyed by profile names.
- * @return a map of all push registrations. <b>The map is intentionally mutable - be careful!</b>
- */
- public @NonNull Map<String, PushRegistration> getRegistrations() {
- return registrations;
- }
-
- /**
- * Remove any existing push registration for the given profile name.
- * </p>
- * Most registration removals are during iteration, which should use an iterator that is
- * aware of removals.
- * @return the removed push registration, if one was removed; null otherwise.
- */
- public PushRegistration removeRegistration(@NonNull String profileName) {
- return registrations.remove(profileName);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/push/PushSubscription.java b/mobile/android/base/java/org/mozilla/gecko/push/PushSubscription.java
deleted file mode 100644
index ecf752591..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/push/PushSubscription.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.push;
-
-import android.support.annotation.NonNull;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * Represent an autopush Channel subscription.
- * <p/>
- * Such a subscription associates a user agent and autopush data with a channel
- * ID, a WebPush endpoint, and some service-specific data.
- * <p/>
- * Cloud Messaging data, and the returned uaid and secret.
- * <p/>
- * Each registration is associated to a single Gecko profile, although we don't
- * enforce that here. This class is immutable, so it is by definition
- * thread-safe.
- */
-public class PushSubscription {
- public final @NonNull String chid;
- public final @NonNull String profileName;
- public final @NonNull String webpushEndpoint;
- public final @NonNull String service;
- public final JSONObject serviceData;
-
- public PushSubscription(@NonNull String chid, @NonNull String profileName, @NonNull String webpushEndpoint, @NonNull String service, JSONObject serviceData) {
- this.chid = chid;
- this.profileName = profileName;
- this.webpushEndpoint = webpushEndpoint;
- this.service = service;
- this.serviceData = serviceData;
- }
-
- public JSONObject toJSONObject() throws JSONException {
- final JSONObject jsonObject = new JSONObject();
- jsonObject.put("chid", chid);
- jsonObject.put("profileName", profileName);
- jsonObject.put("webpushEndpoint", webpushEndpoint);
- jsonObject.put("service", service);
- jsonObject.put("serviceData", serviceData);
- return jsonObject;
- }
-
- public static PushSubscription fromJSONObject(@NonNull JSONObject subscription) throws JSONException {
- final String chid = subscription.getString("chid");
- final String profileName = subscription.getString("profileName");
- final String webpushEndpoint = subscription.getString("webpushEndpoint");
- final String service = subscription.getString("service");
- final JSONObject serviceData = subscription.optJSONObject("serviceData");
- return new PushSubscription(chid, profileName, webpushEndpoint, service, serviceData);
- }
-
- @Override
- public boolean equals(Object o) {
- // Auto-generated.
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- PushSubscription that = (PushSubscription) o;
-
- if (!chid.equals(that.chid)) return false;
- if (!profileName.equals(that.profileName)) return false;
- if (!webpushEndpoint.equals(that.webpushEndpoint)) return false;
- return service.equals(that.service);
- }
-
- @Override
- public int hashCode() {
- // Auto-generated.
- int result = profileName.hashCode();
- result = 31 * result + chid.hashCode();
- result = 31 * result + webpushEndpoint.hashCode();
- result = 31 * result + service.hashCode();
- return result;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/reader/ReaderModeUtils.java b/mobile/android/base/java/org/mozilla/gecko/reader/ReaderModeUtils.java
deleted file mode 100644
index e70aac5b5..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/reader/ReaderModeUtils.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.reader;
-
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.util.StringUtils;
-
-import android.net.Uri;
-
-public class ReaderModeUtils {
- private static final String LOGTAG = "ReaderModeUtils";
-
- /**
- * Extract the URL from a valid about:reader URL. You may want to use stripAboutReaderUrl
- * instead to always obtain a valid String.
- *
- * @see #stripAboutReaderUrl(String) for a safer version that returns the original URL for malformed/invalid
- * URLs.
- * @return <code>null</code> if the URL is malformed or doesn't contain a URL parameter.
- */
- private static String getUrlFromAboutReader(String aboutReaderUrl) {
- return StringUtils.getQueryParameter(aboutReaderUrl, "url");
- }
-
- public static boolean isEnteringReaderMode(String oldURL, String newURL) {
- if (oldURL == null || newURL == null) {
- return false;
- }
-
- if (!AboutPages.isAboutReader(newURL)) {
- return false;
- }
-
- String urlFromAboutReader = getUrlFromAboutReader(newURL);
- if (urlFromAboutReader == null) {
- return false;
- }
-
- return urlFromAboutReader.equals(oldURL);
- }
-
- public static String getAboutReaderForUrl(String url) {
- return getAboutReaderForUrl(url, -1);
- }
-
- /**
- * Obtain the underlying URL from an about:reader URL.
- * This will return the input URL if either of the following is true:
- * 1. the input URL is a non about:reader URL
- * 2. the input URL is an invalid/unparseable about:reader URL
- */
- public static String stripAboutReaderUrl(String url) {
- if (!AboutPages.isAboutReader(url)) {
- return url;
- }
-
- final String strippedUrl = getUrlFromAboutReader(url);
- return strippedUrl != null ? strippedUrl : url;
- }
-
- public static String getAboutReaderForUrl(String url, int tabId) {
- String aboutReaderUrl = AboutPages.READER + "?url=" + Uri.encode(url);
-
- if (tabId >= 0) {
- aboutReaderUrl += "&tabId=" + tabId;
- }
-
- return aboutReaderUrl;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/reader/ReadingListHelper.java b/mobile/android/base/java/org/mozilla/gecko/reader/ReadingListHelper.java
deleted file mode 100644
index e01ff79ac..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/reader/ReadingListHelper.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.reader;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.icons.IconRequest;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.NativeEventListener;
-import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.UIAsyncTask;
-
-import android.content.Context;
-import android.util.Log;
-
-import java.util.concurrent.ExecutionException;
-
-public final class ReadingListHelper implements NativeEventListener {
- private static final String LOGTAG = "GeckoReadingListHelper";
-
- protected final Context context;
- private final BrowserDB db;
-
- public ReadingListHelper(Context context, GeckoProfile profile) {
- this.context = context;
- this.db = BrowserDB.from(profile);
-
- GeckoApp.getEventDispatcher().registerGeckoThreadListener((NativeEventListener) this,
- "Reader:FaviconRequest", "Reader:AddedToCache");
- }
-
- public void uninit() {
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener((NativeEventListener) this,
- "Reader:FaviconRequest", "Reader:AddedToCache");
- }
-
- @Override
- public void handleMessage(final String event, final NativeJSObject message,
- final EventCallback callback) {
- switch (event) {
- case "Reader:FaviconRequest": {
- handleReaderModeFaviconRequest(callback, message.getString("url"));
- break;
- }
- case "Reader:AddedToCache": {
- // AddedToCache is a one way message: callback will be null, and we therefore shouldn't
- // attempt to handle it.
- handleAddedToCache(message.getString("url"), message.getString("path"), message.getInt("size"));
- break;
- }
- }
- }
-
- /**
- * Gecko (ReaderMode) requests the page favicon to append to the
- * document head for display.
- */
- private void handleReaderModeFaviconRequest(final EventCallback callback, final String url) {
- (new UIAsyncTask.WithoutParams<String>(ThreadUtils.getBackgroundHandler()) {
- @Override
- public String doInBackground() {
- // This is a bit ridiculous if you look at the bigger picture: Reader mode extracts
- // the article content. We insert the content into a new document (about:reader).
- // Some events are exchanged to lookup the icon URL for the actual website. This
- // URL is then added to the markup which will then trigger our icon loading code in
- // the Tab class.
- //
- // The Tab class could just lookup and load the icon itself. All it needs to do is
- // to strip the about:reader URL and perform a normal icon load from cache.
- //
- // A more global solution (looking at desktop and iOS) would be to copy the <link>
- // markup from the original page to the about:reader page and then rely on our normal
- // icon loading code. This would work even if we do not have anything in the cache
- // for some kind of reason.
-
- final IconRequest request = Icons.with(context)
- .pageUrl(url)
- .prepareOnly()
- .build();
-
- try {
- request.execute(null).get();
- if (request.getIconCount() > 0) {
- return request.getBestIcon().getUrl();
- }
- } catch (InterruptedException | ExecutionException e) {
- // Ignore
- }
-
- return null;
- }
-
- @Override
- public void onPostExecute(String faviconUrl) {
- JSONObject args = new JSONObject();
- if (faviconUrl != null) {
- try {
- args.put("url", url);
- args.put("faviconUrl", faviconUrl);
- } catch (JSONException e) {
- Log.w(LOGTAG, "Error building JSON favicon arguments.", e);
- }
- }
- callback.sendSuccess(args.toString());
- }
- }).execute();
- }
-
- private void handleAddedToCache(final String url, final String path, final int size) {
- final SavedReaderViewHelper rch = SavedReaderViewHelper.getSavedReaderViewHelper(context);
-
- rch.put(url, path, size);
- }
-
- public static void cacheReaderItem(final String url, final int tabID, Context context) {
- if (AboutPages.isAboutReader(url)) {
- throw new IllegalArgumentException("Page url must be original (not about:reader) url");
- }
-
- SavedReaderViewHelper rch = SavedReaderViewHelper.getSavedReaderViewHelper(context);
-
- if (!rch.isURLCached(url)) {
- GeckoAppShell.notifyObservers("Reader:AddToCache", Integer.toString(tabID));
- }
- }
-
- public static void removeCachedReaderItem(final String url, Context context) {
- if (AboutPages.isAboutReader(url)) {
- throw new IllegalArgumentException("Page url must be original (not about:reader) url");
- }
-
- SavedReaderViewHelper rch = SavedReaderViewHelper.getSavedReaderViewHelper(context);
-
- if (rch.isURLCached(url)) {
- GeckoAppShell.notifyObservers("Reader:RemoveFromCache", url);
- }
-
- // When removing items from the cache we can probably spare ourselves the async callback
- // that we use when adding cached items. We know the cached item will be gone, hence
- // we no longer need to track it in the SavedReaderViewHelper
- rch.remove(url);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/reader/SavedReaderViewHelper.java b/mobile/android/base/java/org/mozilla/gecko/reader/SavedReaderViewHelper.java
deleted file mode 100644
index e60abac71..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/reader/SavedReaderViewHelper.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.reader;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.util.Log;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.UrlAnnotations;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Iterator;
-
-/**
- * Helper to keep track of items that are stored in the reader view cache. This is an in-memory list
- * of the reader view items that are cached on disk. It is intended to allow quickly determining whether
- * a given URL is in the cache, and also how many cached items there are.
- *
- * Currently we have 1:1 correspondence of reader view items (in the URL-annotations table)
- * to cached items. This is _not_ a true cache, we never purge/cleanup items here - we only remove
- * items when we un-reader-view/bookmark them. This is an acceptable model while we can guarantee the
- * 1:1 correspondence.
- *
- * It isn't strictly necessary to mirror cached items in SQL at this stage, however it seems sensible
- * to maintain URL anotations to avoid additional DB migrations in future.
- * It is also simpler to implement the reading list smart-folder using the annotations (even if we do
- * all other decoration from our in-memory cache record), as that is what we will need when
- * we move away from the 1:1 correspondence.
- *
- * Bookmarks can be in one of two states - plain bookmark, or reader view bookmark that is also saved
- * offline. We're hoping to introduce real cache management / cleanup in future, in which case a
- * third user-visible state (reader view bookmark without a cache entry) will be added. However that logic is
- * much more complicated and requires substantial changes in how we decorate reader view bookmarks.
- * With the current 1:1 correspondence we can use this in-memory helper to quickly decorate
- * bookmarks (in all the various lists and panels that are used), whereas supporting
- * the third state requires significant changes in order to allow joining with the
- * URL-annotations table wherever bookmarks might be retrieved (i.e. multiple homepanels, each with
- * their own loaders and adapter).
- *
- * If/when cache cleanup and sync are implemented, URL annotations will be the canonical record of
- * user intent, and the cache will no longer represent all reader view bookmarks. We will have (A)
- * cached items that are not a bookmark, or bookmarks without the reader view annotation (both of
- * these would need purging), and (B) bookmarks with a reader view annotation, but not stored in
- * the cache (which we might want to download in the background). Supporting (B) is currently difficult,
- * see previous paragraph.
- */
-public class SavedReaderViewHelper {
- private static final String LOG_TAG = "SavedReaderViewHelper";
-
- private static final String PATH = "path";
- private static final String SIZE = "size";
-
- private static final String DIRECTORY = "readercache";
- private static final String FILE_NAME = "items.json";
- private static final String FILE_PATH = DIRECTORY + "/" + FILE_NAME;
-
- // We use null to indicate that the cache hasn't yet been loaded. Loading has to be explicitly
- // requested by client code, and must happen on the background thread. Attempting to access
- // items (which happens mainly on the UI thread) before explicitly loading them is not permitted.
- private JSONObject mItems = null;
-
- private final Context mContext;
-
- private static SavedReaderViewHelper instance = null;
-
- private SavedReaderViewHelper(Context context) {
- mContext = context;
- }
-
- public static synchronized SavedReaderViewHelper getSavedReaderViewHelper(final Context context) {
- if (instance == null) {
- instance = new SavedReaderViewHelper(context);
- }
-
- return instance;
- }
-
- /**
- * Load the reader view cache list from our JSON file.
- *
- * Must not be run on the UI thread due to file access.
- */
- public synchronized void loadItems() {
- // TODO bug 1264489
- // This is a band aid fix for Bug 1264134. We need to figure out the root cause and reenable this
- // assertion.
- // ThreadUtils.assertNotOnUiThread();
-
- if (mItems != null) {
- return;
- }
-
- try {
- mItems = GeckoProfile.get(mContext).readJSONObjectFromFile(FILE_PATH);
- } catch (IOException e) {
- mItems = new JSONObject();
- }
- }
-
- private synchronized void assertItemsLoaded() {
- if (mItems == null) {
- throw new IllegalStateException("SavedReaderView items must be explicitly loaded using loadItems() before access.");
- }
- }
-
- private JSONObject makeItem(@NonNull String path, long size) throws JSONException {
- final JSONObject item = new JSONObject();
-
- item.put(PATH, path);
- item.put(SIZE, size);
-
- return item;
- }
-
- public synchronized boolean isURLCached(@NonNull final String URL) {
- assertItemsLoaded();
- return mItems.has(URL);
- }
-
- /**
- * Insert an item into the list of cached items.
- *
- * This may be called from any thread.
- */
- public synchronized void put(@NonNull final String pageURL, @NonNull final String path, final long size) {
- assertItemsLoaded();
-
- try {
- mItems.put(pageURL, makeItem(path, size));
- } catch (JSONException e) {
- Log.w(LOG_TAG, "Item insertion failed:", e);
- // This should never happen, absent any errors in our own implementation
- throw new IllegalStateException("Failure inserting into SavedReaderViewHelper json");
- }
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- UrlAnnotations annotations = BrowserDB.from(mContext).getUrlAnnotations();
- annotations.insertReaderViewUrl(mContext.getContentResolver(), pageURL);
-
- commit();
- }
- });
- }
-
- protected synchronized void remove(@NonNull final String pageURL) {
- assertItemsLoaded();
-
- mItems.remove(pageURL);
-
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- UrlAnnotations annotations = BrowserDB.from(mContext).getUrlAnnotations();
- annotations.deleteReaderViewUrl(mContext.getContentResolver(), pageURL);
-
- commit();
- }
- });
- }
-
- @RobocopTarget
- public synchronized int size() {
- assertItemsLoaded();
- return mItems.length();
- }
-
- private synchronized void commit() {
- ThreadUtils.assertOnBackgroundThread();
-
- GeckoProfile profile = GeckoProfile.get(mContext);
- File cacheDir = new File(profile.getDir(), DIRECTORY);
-
- if (!cacheDir.exists()) {
- Log.i(LOG_TAG, "No preexisting cache directory, creating now");
-
- boolean cacheDirCreated = cacheDir.mkdir();
- if (!cacheDirCreated) {
- throw new IllegalStateException("Couldn't create cache directory, unable to track reader view cache");
- }
- }
-
- profile.writeFile(FILE_PATH, mItems.toString());
- }
-
- /**
- * Return the Reader View URL for a given URL if it is contained in the cache. Returns the
- * plain URL if the page is not cached.
- */
- public static String getReaderURLIfCached(final Context context, @NonNull final String pageURL) {
- SavedReaderViewHelper rvh = getSavedReaderViewHelper(context);
-
- if (rvh.isURLCached(pageURL)) {
- return ReaderModeUtils.getAboutReaderForUrl(pageURL);
- } else {
- return pageURL;
- }
- }
-
- /**
- * Obtain the total disk space used for saved reader view items, in KB.
- *
- * @return Total disk space used (KB), or Integer.MAX_VALUE on overflow.
- */
- public synchronized int getDiskSpacedUsedKB() {
- // JSONObject is not thread safe - we need to be synchronized to avoid issues (most likely to
- // occur if items are removed during iteration).
- final Iterator<String> keys = mItems.keys();
- long bytes = 0;
-
- while (keys.hasNext()) {
- final String pageURL = keys.next();
- try {
- final JSONObject item = mItems.getJSONObject(pageURL);
- bytes += item.getLong(SIZE);
-
- // Overflow is highly unlikely (we will hit device storage limits before we hit integer limits),
- // but we should still handle this for correctness.
- // We definitely can't store our output in an int if we overflow the long here.
- if (bytes < 0) {
- return Integer.MAX_VALUE;
- }
- } catch (JSONException e) {
- // This shouldn't ever happen:
- throw new IllegalStateException("Must be able to access items in saved reader view list", e);
- }
- }
-
- long kb = bytes / 1024;
- if (kb > Integer.MAX_VALUE) {
- return Integer.MAX_VALUE;
- } else {
- return (int) kb;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/restrictions/DefaultConfiguration.java b/mobile/android/base/java/org/mozilla/gecko/restrictions/DefaultConfiguration.java
deleted file mode 100644
index 480078a98..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/restrictions/DefaultConfiguration.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.restrictions;
-
-/**
- * Default implementation of RestrictionConfiguration interface. Used whenever no restrictions are enforced for the
- * current profile.
- */
-public class DefaultConfiguration implements RestrictionConfiguration {
- @Override
- public boolean isAllowed(Restrictable restrictable) {
- if (restrictable == Restrictable.BLOCK_LIST) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public boolean canLoadUrl(String url) {
- return true;
- }
-
- @Override
- public boolean isRestricted() {
- return false;
- }
-
- @Override
- public void update() {}
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/restrictions/GuestProfileConfiguration.java b/mobile/android/base/java/org/mozilla/gecko/restrictions/GuestProfileConfiguration.java
deleted file mode 100644
index f9663ccf7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/restrictions/GuestProfileConfiguration.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.restrictions;
-
-import android.net.Uri;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * RestrictionConfiguration implementation for guest profiles.
- */
-public class GuestProfileConfiguration implements RestrictionConfiguration {
- static List<Restrictable> DISABLED_FEATURES = Arrays.asList(
- Restrictable.DOWNLOAD,
- Restrictable.INSTALL_EXTENSION,
- Restrictable.INSTALL_APPS,
- Restrictable.BROWSE,
- Restrictable.SHARE,
- Restrictable.BOOKMARK,
- Restrictable.ADD_CONTACT,
- Restrictable.SET_IMAGE,
- Restrictable.MODIFY_ACCOUNTS,
- Restrictable.REMOTE_DEBUGGING,
- Restrictable.IMPORT_SETTINGS,
- Restrictable.BLOCK_LIST,
- Restrictable.DATA_CHOICES,
- Restrictable.DEFAULT_THEME
- );
-
- @SuppressWarnings("serial")
- private static final List<String> BANNED_SCHEMES = Arrays.asList(
- "file",
- "chrome",
- "resource",
- "jar",
- "wyciwyg"
- );
-
- private static final List<String> BANNED_URLS = Arrays.asList(
- "about:config",
- "about:addons"
- );
-
- @Override
- public boolean isAllowed(Restrictable restrictable) {
- return !DISABLED_FEATURES.contains(restrictable);
- }
-
- @Override
- public boolean canLoadUrl(String url) {
- // Null URLs are always permitted.
- if (url == null) {
- return true;
- }
-
- final Uri u = Uri.parse(url);
- final String scheme = u.getScheme();
- if (BANNED_SCHEMES.contains(scheme)) {
- return false;
- }
-
- url = url.toLowerCase();
- for (String banned : BANNED_URLS) {
- if (url.startsWith(banned)) {
- return false;
- }
- }
-
- return true;
- }
-
- @Override
- public boolean isRestricted() {
- return true;
- }
-
- @Override
- public void update() {}
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/restrictions/Restrictable.java b/mobile/android/base/java/org/mozilla/gecko/restrictions/Restrictable.java
deleted file mode 100644
index f794c5782..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/restrictions/Restrictable.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.restrictions;
-
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.support.annotation.StringRes;
-
-/**
- * This is a list of things we can restrict you from doing. Some of these are reflected in Android UserManager constants.
- * Others are specific to us.
- * These constants should be in sync with the ones from toolkit/components/parentalcontrols/nsIParentalControlsService.idl
- */
-public enum Restrictable {
- DOWNLOAD(1, "downloads", 0, 0),
-
- INSTALL_EXTENSION(
- 2, "no_install_extensions",
- R.string.restrictable_feature_addons_installation,
- R.string.restrictable_feature_addons_installation_description),
-
- // UserManager.DISALLOW_INSTALL_APPS
- INSTALL_APPS(3, "no_install_apps", 0 , 0),
-
- BROWSE(4, "browse", 0, 0),
-
- SHARE(5, "share", 0, 0),
-
- BOOKMARK(6, "bookmark", 0, 0),
-
- ADD_CONTACT(7, "add_contact", 0, 0),
-
- SET_IMAGE(8, "set_image", 0, 0),
-
- // UserManager.DISALLOW_MODIFY_ACCOUNTS
- MODIFY_ACCOUNTS(9, "no_modify_accounts", 0, 0),
-
- REMOTE_DEBUGGING(10, "remote_debugging", 0, 0),
-
- IMPORT_SETTINGS(11, "import_settings", 0, 0),
-
- PRIVATE_BROWSING(
- 12, "private_browsing",
- R.string.restrictable_feature_private_browsing,
- R.string.restrictable_feature_private_browsing_description),
-
- DATA_CHOICES(13, "data_coices", 0, 0),
-
- CLEAR_HISTORY(14, "clear_history",
- R.string.restrictable_feature_clear_history,
- R.string.restrictable_feature_clear_history_description),
-
- MASTER_PASSWORD(15, "master_password", 0, 0),
-
- GUEST_BROWSING(16, "guest_browsing", 0, 0),
-
- ADVANCED_SETTINGS(17, "advanced_settings",
- R.string.restrictable_feature_advanced_settings,
- R.string.restrictable_feature_advanced_settings_description),
-
- CAMERA_MICROPHONE(18, "camera_microphone",
- R.string.restrictable_feature_camera_microphone,
- R.string.restrictable_feature_camera_microphone_description),
-
- BLOCK_LIST(19, "block_list",
- R.string.restrictable_feature_block_list,
- R.string.restrictable_feature_block_list_description),
-
- TELEMETRY(20, "telemetry",
- R.string.datareporting_telemetry_title,
- R.string.datareporting_telemetry_summary),
-
- HEALTH_REPORT(21, "health_report",
- R.string.datareporting_fhr_title,
- R.string.datareporting_fhr_summary2),
-
- DEFAULT_THEME(22, "default_theme", 0, 0);
-
- public final int id;
- public final String name;
-
- @StringRes
- public final int title;
-
- @StringRes
- public final int description;
-
- Restrictable(final int id, final String name, @StringRes int title, @StringRes int description) {
- this.id = id;
- this.name = name;
- this.title = title;
- this.description = description;
- }
-
- public String getTitle(Context context) {
- if (title == 0) {
- return toString();
- }
- return context.getResources().getString(title);
- }
-
- public String getDescription(Context context) {
- if (description == 0) {
- return null;
- }
- return context.getResources().getString(description);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/restrictions/RestrictedProfileConfiguration.java b/mobile/android/base/java/org/mozilla/gecko/restrictions/RestrictedProfileConfiguration.java
deleted file mode 100644
index 15a0b97f4..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/restrictions/RestrictedProfileConfiguration.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.restrictions;
-
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.StrictMode;
-import android.os.UserManager;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
-public class RestrictedProfileConfiguration implements RestrictionConfiguration {
- // Mapping from restrictable feature to default state (on/off)
- private static Map<Restrictable, Boolean> configuration = new LinkedHashMap<>();
- static {
- configuration.put(Restrictable.INSTALL_EXTENSION, false);
- configuration.put(Restrictable.PRIVATE_BROWSING, false);
- configuration.put(Restrictable.CLEAR_HISTORY, false);
- configuration.put(Restrictable.MASTER_PASSWORD, false);
- configuration.put(Restrictable.GUEST_BROWSING, false);
- configuration.put(Restrictable.ADVANCED_SETTINGS, false);
- configuration.put(Restrictable.CAMERA_MICROPHONE, false);
- configuration.put(Restrictable.DATA_CHOICES, false);
- configuration.put(Restrictable.BLOCK_LIST, false);
- configuration.put(Restrictable.TELEMETRY, false);
- configuration.put(Restrictable.HEALTH_REPORT, true);
- configuration.put(Restrictable.DEFAULT_THEME, true);
- }
-
- /**
- * These restrictions are hidden from the admin configuration UI.
- */
- private static List<Restrictable> hiddenRestrictions = new ArrayList<>();
- static {
- hiddenRestrictions.add(Restrictable.MASTER_PASSWORD);
- hiddenRestrictions.add(Restrictable.GUEST_BROWSING);
- hiddenRestrictions.add(Restrictable.DATA_CHOICES);
- hiddenRestrictions.add(Restrictable.DEFAULT_THEME);
-
- // Hold behind Nightly flag until we have an actual block list deployed.
- if (!AppConstants.NIGHTLY_BUILD) {
- hiddenRestrictions.add(Restrictable.BLOCK_LIST);
- }
- }
-
- /* package-private */ static boolean shouldHide(Restrictable restrictable) {
- return hiddenRestrictions.contains(restrictable);
- }
-
- /* package-private */ static Map<Restrictable, Boolean> getConfiguration() {
- return configuration;
- }
-
- private Context context;
-
- public RestrictedProfileConfiguration(Context context) {
- this.context = context.getApplicationContext();
- }
-
- @Override
- public synchronized boolean isAllowed(Restrictable restrictable) {
- // Special casing system/user restrictions
- if (restrictable == Restrictable.INSTALL_APPS || restrictable == Restrictable.MODIFY_ACCOUNTS) {
- return RestrictionCache.getUserRestriction(context, restrictable.name);
- }
-
- if (!RestrictionCache.hasApplicationRestriction(context, restrictable.name) && !configuration.containsKey(restrictable)) {
- // Always allow features that are not in the configuration
- return true;
- }
-
- return RestrictionCache.getApplicationRestriction(context, restrictable.name, configuration.get(restrictable));
- }
-
- @Override
- public boolean canLoadUrl(String url) {
- if (!isAllowed(Restrictable.INSTALL_EXTENSION) && AboutPages.isAboutAddons(url)) {
- return false;
- }
-
- if (!isAllowed(Restrictable.PRIVATE_BROWSING) && AboutPages.isAboutPrivateBrowsing(url)) {
- return false;
- }
-
- if (AboutPages.isAboutConfig(url)) {
- // Always block access to about:config to prevent circumventing restrictions (Bug 1189233)
- return false;
- }
-
- return true;
- }
-
- @Override
- public boolean isRestricted() {
- return true;
- }
-
- @Override
- public synchronized void update() {
- RestrictionCache.invalidate();
- }
-
- public static List<Restrictable> getVisibleRestrictions() {
- final List<Restrictable> visibleList = new ArrayList<>();
-
- for (Restrictable restrictable : configuration.keySet()) {
- if (hiddenRestrictions.contains(restrictable)) {
- continue;
- }
- visibleList.add(restrictable);
- }
-
- return visibleList;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/restrictions/RestrictionCache.java b/mobile/android/base/java/org/mozilla/gecko/restrictions/RestrictionCache.java
deleted file mode 100644
index 523cc113b..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/restrictions/RestrictionCache.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.restrictions;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.StrictMode;
-import android.os.UserManager;
-
-import org.mozilla.gecko.util.ThreadUtils;
-
-/**
- * Cache for user and application restrictions.
- */
-public class RestrictionCache {
- private static Bundle cachedAppRestrictions;
- private static Bundle cachedUserRestrictions;
- private static boolean isCacheInvalid = true;
-
- private RestrictionCache() {}
-
- public static synchronized boolean getUserRestriction(Context context, String restriction) {
- updateCacheIfNeeded(context);
- return cachedUserRestrictions.getBoolean(restriction);
- }
-
- public static synchronized boolean hasApplicationRestriction(Context context, String restriction) {
- updateCacheIfNeeded(context);
- return cachedAppRestrictions.containsKey(restriction);
- }
-
- public static synchronized boolean getApplicationRestriction(Context context, String restriction, boolean defaultValue) {
- updateCacheIfNeeded(context);
- return cachedAppRestrictions.getBoolean(restriction, defaultValue);
- }
-
- public static synchronized boolean hasApplicationRestrictions(Context context) {
- updateCacheIfNeeded(context);
- return !cachedAppRestrictions.isEmpty();
- }
-
- public static synchronized void invalidate() {
- isCacheInvalid = true;
- }
-
- private static void updateCacheIfNeeded(Context context) {
- // If we are not on the UI thread then we can just go ahead and read the values (Bug 1189347).
- // Otherwise we read from the cache to avoid blocking the UI thread. If the cache is invalid
- // then we hazard the consequences and just do the read.
- if (isCacheInvalid || !ThreadUtils.isOnUiThread()) {
- readRestrictions(context);
- isCacheInvalid = false;
- }
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
- private static void readRestrictions(Context context) {
- final UserManager mgr = (UserManager) context.getSystemService(Context.USER_SERVICE);
-
- // If we do not have anything in the cache yet then this read might happen on the UI thread (Bug 1189347).
- final StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads();
-
- try {
- Bundle appRestrictions = mgr.getApplicationRestrictions(context.getPackageName());
- migrateRestrictionsIfNeeded(appRestrictions);
-
- cachedAppRestrictions = appRestrictions;
- cachedUserRestrictions = mgr.getUserRestrictions(); // Always implies disk read
- } finally {
- StrictMode.setThreadPolicy(policy);
- }
- }
-
- /**
- * This method migrates the old set of DISALLOW_ restrictions to the new restrictable feature ones (Bug 1189336).
- */
- /* package-private */ static void migrateRestrictionsIfNeeded(Bundle bundle) {
- if (!bundle.containsKey(Restrictable.INSTALL_EXTENSION.name) && bundle.containsKey("no_install_extensions")) {
- bundle.putBoolean(Restrictable.INSTALL_EXTENSION.name, !bundle.getBoolean("no_install_extensions"));
- }
-
- if (!bundle.containsKey(Restrictable.PRIVATE_BROWSING.name) && bundle.containsKey("no_private_browsing")) {
- bundle.putBoolean(Restrictable.PRIVATE_BROWSING.name, !bundle.getBoolean("no_private_browsing"));
- }
-
- if (!bundle.containsKey(Restrictable.CLEAR_HISTORY.name) && bundle.containsKey("no_clear_history")) {
- bundle.putBoolean(Restrictable.CLEAR_HISTORY.name, !bundle.getBoolean("no_clear_history"));
- }
-
- if (!bundle.containsKey(Restrictable.ADVANCED_SETTINGS.name) && bundle.containsKey("no_advanced_settings")) {
- bundle.putBoolean(Restrictable.ADVANCED_SETTINGS.name, !bundle.getBoolean("no_advanced_settings"));
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/restrictions/RestrictionConfiguration.java b/mobile/android/base/java/org/mozilla/gecko/restrictions/RestrictionConfiguration.java
deleted file mode 100644
index 7c40da734..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/restrictions/RestrictionConfiguration.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.restrictions;
-
-/**
- * Interface for classes that Restrictions will delegate to for making decisions.
- */
-public interface RestrictionConfiguration {
- /**
- * Is the user allowed to perform this action?
- */
- boolean isAllowed(Restrictable restrictable);
-
- /**
- * Is the user allowed to load the given URL?
- */
- boolean canLoadUrl(String url);
-
- /**
- * Is this user restricted in any way?
- */
- boolean isRestricted();
-
- /**
- * Update restrictions if needed.
- */
- void update();
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/restrictions/RestrictionProvider.java b/mobile/android/base/java/org/mozilla/gecko/restrictions/RestrictionProvider.java
deleted file mode 100644
index 26b9a446f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/restrictions/RestrictionProvider.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.restrictions;
-
-import org.mozilla.gecko.AppConstants;
-
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.RestrictionEntry;
-import android.os.Build;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import java.util.ArrayList;
-import java.util.Map;
-
-/**
- * Broadcast receiver providing supported restrictions to the system.
- */
-@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
-public class RestrictionProvider extends BroadcastReceiver {
- @Override
- public void onReceive(final Context context, final Intent intent) {
- if (AppConstants.Versions.preJBMR2) {
- // This broadcast does not make any sense prior to Jelly Bean MR2.
- return;
- }
-
- final PendingResult result = goAsync();
-
- new Thread() {
- @Override
- public void run() {
- final Bundle oldRestrictions = intent.getBundleExtra(Intent.EXTRA_RESTRICTIONS_BUNDLE);
- RestrictionCache.migrateRestrictionsIfNeeded(oldRestrictions);
-
- final Bundle extras = new Bundle();
-
- ArrayList<RestrictionEntry> entries = initRestrictions(context, oldRestrictions);
- extras.putParcelableArrayList(Intent.EXTRA_RESTRICTIONS_LIST, entries);
-
- result.setResult(Activity.RESULT_OK, null, extras);
- result.finish();
- }
- }.start();
- }
-
- private ArrayList<RestrictionEntry> initRestrictions(Context context, Bundle oldRestrictions) {
- ArrayList<RestrictionEntry> entries = new ArrayList<RestrictionEntry>();
-
- final Map<Restrictable, Boolean> configuration = RestrictedProfileConfiguration.getConfiguration();
-
- for (Restrictable restrictable : configuration.keySet()) {
- if (RestrictedProfileConfiguration.shouldHide(restrictable)) {
- continue;
- }
-
- RestrictionEntry entry = createRestrictionEntryWithDefaultValue(context, restrictable,
- oldRestrictions.getBoolean(restrictable.name, configuration.get(restrictable)));
- entries.add(entry);
- }
-
- return entries;
- }
-
- private RestrictionEntry createRestrictionEntryWithDefaultValue(Context context, Restrictable restrictable, boolean defaultValue) {
- RestrictionEntry entry = new RestrictionEntry(restrictable.name, defaultValue);
-
- entry.setTitle(restrictable.getTitle(context));
-
- final String description = restrictable.getDescription(context);
- if (!TextUtils.isEmpty(description)) {
- entry.setDescription(description);
- }
-
- return entry;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/restrictions/Restrictions.java b/mobile/android/base/java/org/mozilla/gecko/restrictions/Restrictions.java
deleted file mode 100644
index 0cf680810..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/restrictions/Restrictions.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.restrictions;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.os.Build;
-import android.util.Log;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.annotation.WrapForJNI;
-
-@RobocopTarget
-public class Restrictions {
- private static final String LOGTAG = "GeckoRestrictedProfiles";
-
- private static RestrictionConfiguration configuration;
-
- private static RestrictionConfiguration getConfiguration(Context context) {
- if (configuration == null) {
- configuration = createConfiguration(context);
- }
-
- return configuration;
- }
-
- public static synchronized RestrictionConfiguration createConfiguration(Context context) {
- if (configuration != null) {
- // This method is synchronized and another thread might already have created the configuration.
- return configuration;
- }
-
- if (isGuestProfile(context)) {
- return new GuestProfileConfiguration();
- } else if (isRestrictedProfile(context)) {
- return new RestrictedProfileConfiguration(context);
- } else {
- return new DefaultConfiguration();
- }
- }
-
- private static boolean isGuestProfile(Context context) {
- if (configuration != null) {
- return configuration instanceof GuestProfileConfiguration;
- }
-
- GeckoAppShell.GeckoInterface geckoInterface = GeckoAppShell.getGeckoInterface();
- if (geckoInterface != null) {
- return geckoInterface.getProfile().inGuestMode();
- }
-
- return GeckoProfile.get(context).inGuestMode();
- }
-
- @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
- public static boolean isRestrictedProfile(Context context) {
- if (configuration != null) {
- return configuration instanceof RestrictedProfileConfiguration;
- }
-
- if (Versions.preJBMR2) {
- // Early versions don't support restrictions at all
- return false;
- }
-
- // The user is on a restricted profile if, and only if, we injected application restrictions during account setup.
- return RestrictionCache.hasApplicationRestrictions(context);
- }
-
- public static void update(Context context) {
- getConfiguration(context).update();
- }
-
- private static Restrictable geckoActionToRestriction(int action) {
- for (Restrictable rest : Restrictable.values()) {
- if (rest.id == action) {
- return rest;
- }
- }
-
- throw new IllegalArgumentException("Unknown action " + action);
- }
-
- private static boolean canLoadUrl(final Context context, final String url) {
- return getConfiguration(context).canLoadUrl(url);
- }
-
- @WrapForJNI(calledFrom = "gecko")
- public static boolean isUserRestricted() {
- return isUserRestricted(GeckoAppShell.getApplicationContext());
- }
-
- public static boolean isUserRestricted(final Context context) {
- return getConfiguration(context).isRestricted();
- }
-
- public static boolean isAllowed(final Context context, final Restrictable restrictable) {
- return getConfiguration(context).isAllowed(restrictable);
- }
-
- @WrapForJNI(calledFrom = "gecko")
- public static boolean isAllowed(int action, String url) {
- final Restrictable restrictable;
- try {
- restrictable = geckoActionToRestriction(action);
- } catch (IllegalArgumentException ex) {
- // Unknown actions represent a coding error, so we
- // refuse the action and log.
- Log.e(LOGTAG, "Unknown action " + action + "; check calling code.");
- return false;
- }
-
- final Context context = GeckoAppShell.getApplicationContext();
-
- if (Restrictable.BROWSE == restrictable) {
- return canLoadUrl(context, url);
- } else {
- return isAllowed(context, restrictable);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/search/SearchEngine.java b/mobile/android/base/java/org/mozilla/gecko/search/SearchEngine.java
deleted file mode 100644
index d4d9938e2..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/search/SearchEngine.java
+++ /dev/null
@@ -1,304 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.search;
-
-import android.net.Uri;
-import android.util.Log;
-import android.util.Xml;
-
-import org.mozilla.gecko.util.StringUtils;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-
-/**
- * Extend this class to add a new search engine to
- * the search activity.
- */
-public class SearchEngine {
- private static final String LOG_TAG = "SearchEngine";
-
- private static final String URLTYPE_SUGGEST_JSON = "application/x-suggestions+json";
- private static final String URLTYPE_SEARCH_HTML = "text/html";
-
- private static final String URL_REL_MOBILE = "mobile";
-
- // Parameters copied from nsSearchService.js
- private static final String MOZ_PARAM_LOCALE = "\\{moz:locale\\}";
- private static final String MOZ_PARAM_DIST_ID = "\\{moz:distributionID\\}";
- private static final String MOZ_PARAM_OFFICIAL = "\\{moz:official\\}";
-
- // Supported OpenSearch parameters
- // See http://opensearch.a9.com/spec/1.1/querysyntax/#core
- private static final String OS_PARAM_USER_DEFINED = "\\{searchTerms\\??\\}";
- private static final String OS_PARAM_INPUT_ENCODING = "\\{inputEncoding\\??\\}";
- private static final String OS_PARAM_LANGUAGE = "\\{language\\??\\}";
- private static final String OS_PARAM_OUTPUT_ENCODING = "\\{outputEncoding\\??\\}";
- private static final String OS_PARAM_OPTIONAL = "\\{(?:\\w+:)?\\w+\\?\\}";
-
- // Boilerplate bookmarklet-style JS for injecting CSS into the
- // head of a web page. The actual CSS is inserted at `%s`.
- private static final String STYLE_INJECTION_SCRIPT =
- "javascript:(function(){" +
- "var tag=document.createElement('style');" +
- "tag.type='text/css';" +
- "document.getElementsByTagName('head')[0].appendChild(tag);" +
- "tag.innerText='%s'})();";
-
- // The Gecko search identifier. This will be null for engines that don't ship with the locale.
- private final String identifier;
-
- private String shortName;
- private String iconURL;
-
- // Ordered list of preferred results URIs.
- private final List<Uri> resultsUris = new ArrayList<Uri>();
- private Uri suggestUri;
-
- /**
- *
- * @param in InputStream of open search plugin XML
- */
- public SearchEngine(String identifier, InputStream in) throws IOException, XmlPullParserException {
- this.identifier = identifier;
-
- final XmlPullParser parser = Xml.newPullParser();
- parser.setInput(in, null);
- parser.nextTag();
- readSearchPlugin(parser);
- }
-
- private void readSearchPlugin(XmlPullParser parser) throws XmlPullParserException, IOException {
- if (XmlPullParser.START_TAG != parser.getEventType()) {
- throw new XmlPullParserException("Expected start tag: " + parser.getPositionDescription());
- }
-
- final String name = parser.getName();
- if (!"SearchPlugin".equals(name) && !"OpenSearchDescription".equals(name)) {
- throw new XmlPullParserException("Expected <SearchPlugin> or <OpenSearchDescription> as root tag: "
- + parser.getPositionDescription());
- }
-
- while (parser.next() != XmlPullParser.END_TAG) {
- if (parser.getEventType() != XmlPullParser.START_TAG) {
- continue;
- }
-
- final String tag = parser.getName();
- if (tag.equals("ShortName")) {
- readShortName(parser);
- } else if (tag.equals("Url")) {
- readUrl(parser);
- } else if (tag.equals("Image")) {
- readImage(parser);
- } else {
- skip(parser);
- }
- }
- }
-
- private void readShortName(XmlPullParser parser) throws IOException, XmlPullParserException {
- parser.require(XmlPullParser.START_TAG, null, "ShortName");
- if (parser.next() == XmlPullParser.TEXT) {
- shortName = parser.getText();
- parser.nextTag();
- }
- }
-
- private void readUrl(XmlPullParser parser) throws XmlPullParserException, IOException {
- parser.require(XmlPullParser.START_TAG, null, "Url");
-
- final String type = parser.getAttributeValue(null, "type");
- final String template = parser.getAttributeValue(null, "template");
- final String rel = parser.getAttributeValue(null, "rel");
-
- Uri uri = Uri.parse(template);
-
- while (parser.next() != XmlPullParser.END_TAG) {
- if (parser.getEventType() != XmlPullParser.START_TAG) {
- continue;
- }
-
- final String tag = parser.getName();
-
- if (tag.equals("Param")) {
- final String name = parser.getAttributeValue(null, "name");
- final String value = parser.getAttributeValue(null, "value");
- uri = uri.buildUpon().appendQueryParameter(name, value).build();
- parser.nextTag();
- // TODO: Support for other tags
- //} else if (tag.equals("MozParam")) {
- } else {
- skip(parser);
- }
- }
-
- if (type.equals(URLTYPE_SEARCH_HTML)) {
- // Prefer mobile URIs.
- if (rel != null && rel.equals(URL_REL_MOBILE)) {
- resultsUris.add(0, uri);
- } else {
- resultsUris.add(uri);
- }
- } else if (type.equals(URLTYPE_SUGGEST_JSON)) {
- suggestUri = uri;
- }
- }
-
- private void readImage(XmlPullParser parser) throws XmlPullParserException, IOException {
- parser.require(XmlPullParser.START_TAG, null, "Image");
-
- // TODO: Use width and height to get a preferred icon URL.
- //final int width = Integer.parseInt(parser.getAttributeValue(null, "width"));
- //final int height = Integer.parseInt(parser.getAttributeValue(null, "height"));
-
- if (parser.next() == XmlPullParser.TEXT) {
- iconURL = parser.getText();
- parser.nextTag();
- }
- }
-
- private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
- if (parser.getEventType() != XmlPullParser.START_TAG) {
- throw new IllegalStateException();
- }
- int depth = 1;
- while (depth != 0) {
- switch (parser.next()) {
- case XmlPullParser.END_TAG:
- depth--;
- break;
- case XmlPullParser.START_TAG:
- depth++;
- break;
- }
- }
- }
-
- /**
- * HACKS! We'll need to replace this with endpoints that return the correct content.
- *
- * Retrieve a JS snippet, in bookmarklet style, that can be used
- * to modify the results page.
- */
- public String getInjectableJs() {
- final String css;
-
- if (identifier == null) {
- css = "";
- } else if (identifier.equals("bing")) {
- css = "#mHeader{display:none}#contentWrapper{margin-top:0}";
- } else if (identifier.equals("google")) {
- css = "#sfcnt,#top_nav{display:none}";
- } else if (identifier.equals("yahoo")) {
- css = "#nav,#header{display:none}";
- } else {
- css = "";
- }
-
- return String.format(STYLE_INJECTION_SCRIPT, css);
- }
-
- public String getIdentifier() {
- return identifier;
- }
-
- public String getName() {
- return shortName;
- }
-
- public String getIconURL() {
- return iconURL;
- }
-
- /**
- * Finds the search query encoded in a given results URL.
- *
- * @param url Current results URL.
- * @return The search query, or an empty string if a query couldn't be found.
- */
- public String queryForResultsUrl(String url) {
- final Uri resultsUri = getResultsUri();
- final Set<String> names = StringUtils.getQueryParameterNames(resultsUri);
- for (String name : names) {
- if (resultsUri.getQueryParameter(name).matches(OS_PARAM_USER_DEFINED)) {
- return Uri.parse(url).getQueryParameter(name);
- }
- }
- return "";
- }
-
- /**
- * Create a uri string that can be used to fetch the results page.
- *
- * @param query The user's query. This method will escape and encode the query.
- */
- public String resultsUriForQuery(String query) {
- final Uri resultsUri = getResultsUri();
- if (resultsUri == null) {
- Log.e(LOG_TAG, "No results URL for search engine: " + shortName);
- return "";
- }
- final String template = Uri.decode(resultsUri.toString());
- return paramSubstitution(template, Uri.encode(query));
- }
-
- /**
- * Create a uri string to fetch autocomplete suggestions.
- *
- * @param query The user's query. This method will escape and encode the query.
- */
- public String getSuggestionTemplate(String query) {
- if (suggestUri == null) {
- Log.e(LOG_TAG, "No suggestions template for search engine: " + shortName);
- return "";
- }
- final String template = Uri.decode(suggestUri.toString());
- return paramSubstitution(template, Uri.encode(query));
- }
-
- /**
- * @return Preferred results URI.
- */
- private Uri getResultsUri() {
- if (resultsUris.isEmpty()) {
- return null;
- }
- return resultsUris.get(0);
- }
-
- /**
- * Formats template string with proper parameters. Modeled after
- * ParamSubstitution in nsSearchService.js
- *
- * @param template
- * @param query
- * @return
- */
- private String paramSubstitution(String template, String query) {
- final String locale = Locale.getDefault().toString();
-
- template = template.replaceAll(MOZ_PARAM_LOCALE, locale);
- template = template.replaceAll(MOZ_PARAM_DIST_ID, "");
- template = template.replaceAll(MOZ_PARAM_OFFICIAL, "unofficial");
-
- template = template.replaceAll(OS_PARAM_USER_DEFINED, query);
- template = template.replaceAll(OS_PARAM_INPUT_ENCODING, "UTF-8");
-
- template = template.replaceAll(OS_PARAM_LANGUAGE, locale);
- template = template.replaceAll(OS_PARAM_OUTPUT_ENCODING, "UTF-8");
-
- // Replace any optional parameters
- template = template.replaceAll(OS_PARAM_OPTIONAL, "");
-
- return template;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/search/SearchEngineManager.java b/mobile/android/base/java/org/mozilla/gecko/search/SearchEngineManager.java
deleted file mode 100644
index 4b33db40a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/search/SearchEngineManager.java
+++ /dev/null
@@ -1,764 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.search;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.support.annotation.Nullable;
-import android.support.annotation.UiThread;
-import android.support.annotation.WorkerThread;
-import android.text.TextUtils;
-import android.util.Log;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.Locales;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.distribution.Distribution;
-import org.mozilla.gecko.util.FileUtils;
-import org.mozilla.gecko.util.GeckoJarReader;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.RawResource;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.lang.ref.WeakReference;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Locale;
-
-/**
- * This class is not thread-safe, except where otherwise noted.
- *
- * This class contains a reference to {@link Context} - DO NOT LEAK!
- */
-public class SearchEngineManager implements SharedPreferences.OnSharedPreferenceChangeListener {
- private static final String LOG_TAG = "GeckoSearchEngineManager";
-
- // Gecko pref that defines the name of the default search engine.
- private static final String PREF_GECKO_DEFAULT_ENGINE = "browser.search.defaultenginename";
-
- // Gecko pref that defines the name of the default searchplugin locale.
- private static final String PREF_GECKO_DEFAULT_LOCALE = "distribution.searchplugins.defaultLocale";
-
- // Key for shared preference that stores default engine name.
- private static final String PREF_DEFAULT_ENGINE_KEY = "search.engines.defaultname";
-
- // Key for shared preference that stores search region.
- private static final String PREF_REGION_KEY = "search.region";
-
- // URL for the geo-ip location service. Keep in sync with "browser.search.geoip.url" perference in Gecko.
- private static final String GEOIP_LOCATION_URL = "https://location.services.mozilla.com/v1/country?key=" + AppConstants.MOZ_MOZILLA_API_KEY;
-
- // This should go through GeckoInterface to get the UA, but the search activity
- // doesn't use a GeckoView yet. Until it does, get the UA directly.
- private static final String USER_AGENT = HardwareUtils.isTablet() ?
- AppConstants.USER_AGENT_FENNEC_TABLET : AppConstants.USER_AGENT_FENNEC_MOBILE;
-
- private final Context context;
- private final Distribution distribution;
- @Nullable private volatile SearchEngineCallback changeCallback;
- @Nullable private volatile SearchEngine engine;
-
- // Cached version of default locale included in Gecko chrome manifest.
- // This should only be accessed from the background thread.
- private String fallbackLocale;
-
- // Cached version of default locale included in Distribution preferences.
- // This should only be accessed from the background thread.
- private String distributionLocale;
-
- public static interface SearchEngineCallback {
- public void execute(@Nullable SearchEngine engine);
- }
-
- public SearchEngineManager(Context context, Distribution distribution) {
- this.context = context;
- this.distribution = distribution;
- GeckoSharedPrefs.forApp(context).registerOnSharedPreferenceChangeListener(this);
- }
-
- /**
- * Sets a callback to be called when the default engine changes. This can be called from any thread.
- *
- * @param changeCallback SearchEngineCallback to be called after the search engine
- * changed. This will run on the UI thread.
- * Note: callback may be called with null engine.
- */
- public void setChangeCallback(SearchEngineCallback changeCallback) {
- this.changeCallback = changeCallback;
- }
-
- /**
- * Perform an action with the user's default search engine. This can be called from any thread.
- *
- * @param callback The callback to be used with the user's default search engine. The call
- * may be sync or async; if the call is async, it will be called on the
- * ui thread.
- */
- public void getEngine(SearchEngineCallback callback) {
- if (engine != null) {
- callback.execute(engine);
- } else {
- getDefaultEngine(callback);
- }
- }
-
- /**
- * Should be called when the object goes out of scope.
- */
- public void unregisterListeners() {
- GeckoSharedPrefs.forApp(context).unregisterOnSharedPreferenceChangeListener(this);
- }
-
- private volatile int ignorePreferenceChange = 0;
-
- @UiThread // according to the docs.
- @Override
- public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
- if (!TextUtils.equals(PREF_DEFAULT_ENGINE_KEY, key)) {
- return;
- }
-
- if (ignorePreferenceChange > 0) {
- ignorePreferenceChange--;
- return;
- }
-
- getDefaultEngine(changeCallback);
- }
-
- /**
- * Runs a SearchEngineCallback on the main thread.
- */
- private void runCallback(final SearchEngine engine, @Nullable final SearchEngineCallback callback) {
- ThreadUtils.postToUiThread(new RunCallbackUiThreadRunnable(this, engine, callback));
- }
-
- // Static is not strictly necessary but the outer class has a reference to Context so we should GC ASAP.
- private static class RunCallbackUiThreadRunnable implements Runnable {
- private final WeakReference<SearchEngineManager> searchEngineManagerWeakReference;
- private final SearchEngine searchEngine;
- private final SearchEngineCallback callback;
-
- public RunCallbackUiThreadRunnable(final SearchEngineManager searchEngineManager, final SearchEngine searchEngine,
- final SearchEngineCallback callback) {
- this.searchEngineManagerWeakReference = new WeakReference<>(searchEngineManager);
- this.searchEngine = searchEngine;
- this.callback = callback;
- }
-
- @UiThread
- @Override
- public void run() {
- final SearchEngineManager searchEngineManager = searchEngineManagerWeakReference.get();
- if (searchEngineManager == null) {
- return;
- }
-
- // Cache engine for future calls to getEngine.
- searchEngineManager.engine = searchEngine;
- if (callback != null) {
- callback.execute(searchEngine);
- }
-
- }
- }
-
- /**
- * This method finds and creates the default search engine. It will first look for
- * the default engine name, then create the engine from that name.
- *
- * To find the default engine name, we first look in shared preferences, then
- * the distribution (if one exists), and finally fall back to the localized default.
- *
- * @param callback SearchEngineCallback to be called after successfully looking
- * up the search engine. This will run on the UI thread.
- * Note: callback may be called with null engine.
- */
- private void getDefaultEngine(final SearchEngineCallback callback) {
- // This runnable is posted to the background thread.
- distribution.addOnDistributionReadyCallback(new GetDefaultEngineDistributionCallbacks(this, callback));
- }
-
- // Static is not strictly necessary but the outer class contains a reference to Context so we should GC ASAP.
- private static class GetDefaultEngineDistributionCallbacks implements Distribution.ReadyCallback {
- private final WeakReference<SearchEngineManager> searchEngineManagerWeakReference;
- private final SearchEngineCallback callback;
-
- public GetDefaultEngineDistributionCallbacks(final SearchEngineManager searchEngineManager,
- final SearchEngineCallback callback) {
- this.searchEngineManagerWeakReference = new WeakReference<>(searchEngineManager);
- this.callback = callback;
- }
-
- @Override
- public void distributionNotFound() {
- defaultBehavior();
- }
-
- @Override
- public void distributionFound(Distribution distribution) {
- defaultBehavior();
- }
-
- @Override
- public void distributionArrivedLate(Distribution distribution) {
- final SearchEngineManager searchEngineManager = searchEngineManagerWeakReference.get();
- if (searchEngineManager == null) {
- return;
- }
-
- // Let's see if there's a name in the distro.
- // If so, just this once we'll override the saved value.
- final String name = searchEngineManager.getDefaultEngineNameFromDistribution();
-
- if (name == null) {
- return;
- }
-
- // Store the default engine name for the future.
- // Increment an 'ignore' counter so that this preference change
- // won't cause getDefaultEngine to be called again.
- searchEngineManager.ignorePreferenceChange++;
- GeckoSharedPrefs.forApp(searchEngineManager.context)
- .edit()
- .putString(PREF_DEFAULT_ENGINE_KEY, name)
- .apply();
-
- final SearchEngine engine = searchEngineManager.createEngineFromName(name);
- searchEngineManager.runCallback(engine, callback);
- }
-
- @WorkerThread // calling methods are @WorkerThread
- private void defaultBehavior() {
- final SearchEngineManager searchEngineManager = searchEngineManagerWeakReference.get();
- if (searchEngineManager == null) {
- return;
- }
-
- // First look for a default name stored in shared preferences.
- String name = GeckoSharedPrefs.forApp(searchEngineManager.context).getString(PREF_DEFAULT_ENGINE_KEY, null);
-
- // Check for a region stored in shared preferences. If we don't have a region,
- // we should force a recheck of the default engine.
- String region = GeckoSharedPrefs.forApp(searchEngineManager.context).getString(PREF_REGION_KEY, null);
-
- if (name != null && region != null) {
- Log.d(LOG_TAG, "Found default engine name in SharedPreferences: " + name);
- } else {
- // First, look for the default search engine in a distribution.
- name = searchEngineManager.getDefaultEngineNameFromDistribution();
- if (name == null) {
- // Otherwise, get the default engine that we ship.
- name = searchEngineManager.getDefaultEngineNameFromLocale();
- }
-
- // Store the default engine name for the future.
- // Increment an 'ignore' counter so that this preference change
- // won't cause getDefaultEngine to be called again.
- searchEngineManager.ignorePreferenceChange++;
- GeckoSharedPrefs.forApp(searchEngineManager.context)
- .edit()
- .putString(PREF_DEFAULT_ENGINE_KEY, name)
- .apply();
- }
-
- final SearchEngine engine = searchEngineManager.createEngineFromName(name);
- searchEngineManager.runCallback(engine, callback);
- }
- }
-
- /**
- * Looks for a default search engine included in a distribution.
- * This method must be called after the distribution is ready.
- *
- * @return search engine name.
- */
- private String getDefaultEngineNameFromDistribution() {
- if (!distribution.exists()) {
- return null;
- }
-
- final File prefFile = distribution.getDistributionFile("preferences.json");
- if (prefFile == null) {
- return null;
- }
-
- try {
- final JSONObject all = FileUtils.readJSONObjectFromFile(prefFile);
-
- // First, look for a default locale specified by the distribution.
- if (all.has("Preferences")) {
- final JSONObject prefs = all.getJSONObject("Preferences");
- if (prefs.has(PREF_GECKO_DEFAULT_LOCALE)) {
- Log.d(LOG_TAG, "Found default searchplugin locale in distribution Preferences.");
- distributionLocale = prefs.getString(PREF_GECKO_DEFAULT_LOCALE);
- }
- }
-
- // Then, check to see if there's a locale-specific default engine override.
- final String languageTag = Locales.getLanguageTag(Locale.getDefault());
- final String overridesKey = "LocalizablePreferences." + languageTag;
- if (all.has(overridesKey)) {
- final JSONObject overridePrefs = all.getJSONObject(overridesKey);
- if (overridePrefs.has(PREF_GECKO_DEFAULT_ENGINE)) {
- Log.d(LOG_TAG, "Found default engine name in distribution LocalizablePreferences override.");
- return overridePrefs.getString(PREF_GECKO_DEFAULT_ENGINE);
- }
- }
-
- // Next, check to see if there's a non-override default engine pref.
- if (all.has("LocalizablePreferences")) {
- final JSONObject localizablePrefs = all.getJSONObject("LocalizablePreferences");
- if (localizablePrefs.has(PREF_GECKO_DEFAULT_ENGINE)) {
- Log.d(LOG_TAG, "Found default engine name in distribution LocalizablePreferences.");
- return localizablePrefs.getString(PREF_GECKO_DEFAULT_ENGINE);
- }
- }
- } catch (IOException e) {
- Log.e(LOG_TAG, "Error getting search engine name from preferences.json", e);
- } catch (JSONException e) {
- Log.e(LOG_TAG, "Error parsing preferences.json", e);
- }
- return null;
- }
-
- /**
- * Helper function for converting an InputStream to a String.
- * @param is InputStream you want to convert to a String
- *
- * @return String containing the data
- */
- private String getHttpResponse(HttpURLConnection conn) {
- InputStream is = null;
- try {
- is = new BufferedInputStream(conn.getInputStream());
- return new java.util.Scanner(is).useDelimiter("\\A").next();
- } catch (Exception e) {
- return "";
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- Log.e(LOG_TAG, "Error closing InputStream", e);
- }
- }
- }
- }
-
- /**
- * Gets the country code based on the current IP, using the Mozilla Location Service.
- * We cache the country code in a shared preference, so we only fetch from the network
- * once.
- *
- * @return String containing the country code
- */
- private String fetchCountryCode() {
- // First, we look to see if we have a cached code.
- final String region = GeckoSharedPrefs.forApp(context).getString(PREF_REGION_KEY, null);
- if (region != null) {
- return region;
- }
-
- // Since we didn't have a cached code, we need to fetch a code from the service.
- try {
- String responseText = null;
-
- URL url = new URL(GEOIP_LOCATION_URL);
- HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
- try {
- // POST an empty JSON object.
- final String message = "{}";
-
- urlConnection.setDoOutput(true);
- urlConnection.setConnectTimeout(10000);
- urlConnection.setReadTimeout(10000);
- urlConnection.setRequestMethod("POST");
- urlConnection.setRequestProperty("User-Agent", USER_AGENT);
- urlConnection.setRequestProperty("Content-Type", "application/json");
- urlConnection.setFixedLengthStreamingMode(message.getBytes().length);
-
- final OutputStream out = urlConnection.getOutputStream();
- out.write(message.getBytes());
- out.close();
-
- responseText = getHttpResponse(urlConnection);
- } finally {
- urlConnection.disconnect();
- }
-
- if (responseText == null) {
- Log.e(LOG_TAG, "Country code fetch failed");
- return null;
- }
-
- // Extract the country code and save it for later in a cache.
- final JSONObject response = new JSONObject(responseText);
- return response.optString("country_code", null);
- } catch (Exception e) {
- Log.e(LOG_TAG, "Country code fetch failed", e);
- }
-
- return null;
- }
-
- /**
- * Looks for the default search engine shipped in the locale.
- *
- * @return search engine name.
- */
- private String getDefaultEngineNameFromLocale() {
- try {
- final JSONObject browsersearch = new JSONObject(RawResource.getAsString(context, R.raw.browsersearch));
-
- // Get the region used to fence search engines.
- String region = fetchCountryCode();
-
- // Store the result, even if it's empty. If we fail to get a region, we never
- // try to get it again, and we will always fallback to the non-region engine.
- GeckoSharedPrefs.forApp(context)
- .edit()
- .putString(PREF_REGION_KEY, (region == null ? "" : region))
- .apply();
-
- if (region != null) {
- if (browsersearch.has("regions")) {
- final JSONObject regions = browsersearch.getJSONObject("regions");
- if (regions.has(region)) {
- final JSONObject regionData = regions.getJSONObject(region);
- Log.d(LOG_TAG, "Found region-specific default engine name in browsersearch.json.");
- return regionData.getString("default");
- }
- }
- }
-
- // Either we have no geoip region, or we didn't find the right region and we are falling back to the default.
- if (browsersearch.has("default")) {
- Log.d(LOG_TAG, "Found default engine name in browsersearch.json.");
- return browsersearch.getString("default");
- }
- } catch (IOException e) {
- Log.e(LOG_TAG, "Error getting search engine name from browsersearch.json", e);
- } catch (JSONException e) {
- Log.e(LOG_TAG, "Error parsing browsersearch.json", e);
- }
- return null;
- }
-
- /**
- * Creates a SearchEngine instance from an engine name.
- *
- * To create the engine, we first try to find the search plugin in the distribution
- * (if one exists), followed by the localized plugins we ship with the browser, and
- * then finally third-party plugins that are installed in the profile directory.
- *
- * This method must be called after the distribution is ready.
- *
- * @param name The search engine name (e.g. "Google" or "Amazon.com")
- * @return SearchEngine instance for name.
- */
- private SearchEngine createEngineFromName(String name) {
- // First, look in the distribution.
- SearchEngine engine = createEngineFromDistribution(name);
-
- // Second, look in the jar for plugins shipped with the locale.
- if (engine == null) {
- engine = createEngineFromLocale(name);
- }
-
- // Finally, look in the profile for third-party plugins.
- if (engine == null) {
- engine = createEngineFromProfile(name);
- }
-
- if (engine == null) {
- Log.e(LOG_TAG, "Could not create search engine from name: " + name);
- }
-
- return engine;
- }
-
- /**
- * Creates a SearchEngine instance for a distribution search plugin.
- *
- * This method iterates through the distribution searchplugins directory,
- * creating SearchEngine instances until it finds one with the right name.
- *
- * This method must be called after the distribution is ready.
- *
- * @param name Search engine name.
- * @return SearchEngine instance for name.
- */
- private SearchEngine createEngineFromDistribution(String name) {
- if (!distribution.exists()) {
- return null;
- }
-
- final File pluginsDir = distribution.getDistributionFile("searchplugins");
- if (pluginsDir == null) {
- return null;
- }
-
- // Collect an array of files to scan using the same approach as
- // DirectoryService._appendDistroSearchDirs which states:
- // Common engines are loaded for all locales. If there is no locale directory for
- // the current locale, there is a pref: "distribution.searchplugins.defaultLocale",
- // which specifies a default locale to use.
- ArrayList<File> files = new ArrayList<>();
-
- // Load files from the common folder first
- final File[] commonFiles = (new File(pluginsDir, "common")).listFiles();
- if (commonFiles != null) {
- Collections.addAll(files, commonFiles);
- }
-
- // Next, check to see if there's a locale-specific override.
- final File localeDir = new File(pluginsDir, "locale");
- if (localeDir != null) {
- final String languageTag = Locales.getLanguageTag(Locale.getDefault());
- final File[] localeFiles = (new File(localeDir, languageTag)).listFiles();
- if (localeFiles != null) {
- Collections.addAll(files, localeFiles);
- } else {
- // We didn't append the locale dir - try the default one.
- if (distributionLocale != null) {
- final File[] defaultLocaleFiles = (new File(localeDir, distributionLocale)).listFiles();
- if (defaultLocaleFiles != null) {
- Collections.addAll(files, defaultLocaleFiles);
- }
- }
- }
- }
-
- if (files.isEmpty()) {
- Log.e(LOG_TAG, "Could not find search plugin files in distribution directory");
- return null;
- }
-
- return createEngineFromFileList(files.toArray(new File[files.size()]), name);
- }
-
- /**
- * Creates a SearchEngine instance for a search plugin shipped in the locale.
- *
- * This method reads the list of search plugin file names from list.txt, then
- * iterates through the files, creating SearchEngine instances until it finds one
- * with the right name. Unfortunately, we need to do this because there is no
- * other way to map the search engine "name" to the file for the search plugin.
- *
- * @param name Search engine name.
- * @return SearchEngine instance for name.
- */
- private SearchEngine createEngineFromLocale(String name) {
- final InputStream in = getInputStreamFromSearchPluginsJar("list.txt");
- if (in == null) {
- return null;
- }
- final BufferedReader br = getBufferedReader(in);
-
- try {
- String identifier;
- while ((identifier = br.readLine()) != null) {
- final InputStream pluginIn = getInputStreamFromSearchPluginsJar(identifier + ".xml");
- // pluginIn can be null if the xml file doesn't exist which
- // can happen with :hidden plugins
- if (pluginIn != null) {
- final SearchEngine engine = createEngineFromInputStream(identifier, pluginIn);
- if (engine != null && engine.getName().equals(name)) {
- return engine;
- }
- }
- }
- } catch (Exception e) {
- Log.e(LOG_TAG, "Error creating shipped search engine from name: " + name, e);
- } finally {
- try {
- br.close();
- } catch (IOException e) {
- // Ignore.
- }
- }
- return null;
- }
-
- /**
- * Creates a SearchEngine instance for a search plugin in the profile directory.
- *
- * This method iterates through the profile searchplugins directory, creating
- * SearchEngine instances until it finds one with the right name.
- *
- * @param name Search engine name.
- * @return SearchEngine instance for name.
- */
- private SearchEngine createEngineFromProfile(String name) {
- final File pluginsDir = GeckoProfile.get(context).getFile("searchplugins");
- if (pluginsDir == null) {
- return null;
- }
-
- final File[] files = pluginsDir.listFiles();
- if (files == null) {
- Log.e(LOG_TAG, "Could not find search plugin files in profile directory");
- return null;
- }
- return createEngineFromFileList(files, name);
- }
-
- /**
- * This method iterates through an array of search plugin files, creating
- * SearchEngine instances until it finds one with the right name.
- *
- * @param files Array of search plugin files. Should not be null.
- * @param name Search engine name.
- * @return SearchEngine instance for name.
- */
- private SearchEngine createEngineFromFileList(File[] files, String name) {
- for (int i = 0; i < files.length; i++) {
- try {
- final FileInputStream fis = new FileInputStream(files[i]);
- final SearchEngine engine = createEngineFromInputStream(null, fis);
- if (engine != null && engine.getName().equals(name)) {
- return engine;
- }
- } catch (IOException e) {
- Log.e(LOG_TAG, "Error creating search engine from name: " + name, e);
- }
- }
- return null;
- }
-
- /**
- * Creates a SearchEngine instance from an InputStream.
- *
- * This method closes the stream after it is done reading it.
- *
- * @param identifier Seach engine identifier. This only exists for search engines that
- * ship with the default set of engines in the locale.
- * @param in InputStream for search plugin XML file.
- * @return SearchEngine instance.
- */
- private SearchEngine createEngineFromInputStream(String identifier, InputStream in) {
- try {
- try {
- return new SearchEngine(identifier, in);
- } finally {
- in.close();
- }
- } catch (Exception e) {
- Log.e(LOG_TAG, "Exception creating search engine", e);
- }
-
- return null;
- }
-
- /**
- * Reads a file from the searchplugins directory in the Gecko jar.
- *
- * @param fileName name of the file to read.
- * @return InputStream for file.
- */
- private InputStream getInputStreamFromSearchPluginsJar(String fileName) {
- final Locale locale = Locale.getDefault();
-
- // First, try a file path for the full locale.
- final String languageTag = Locales.getLanguageTag(locale);
- String url = getSearchPluginsJarURL(context, languageTag, fileName);
-
- InputStream in = GeckoJarReader.getStream(context, url);
- if (in != null) {
- return in;
- }
-
- // If that doesn't work, try a file path for just the language.
- final String language = Locales.getLanguage(locale);
- if (!languageTag.equals(language)) {
- url = getSearchPluginsJarURL(context, language, fileName);
- in = GeckoJarReader.getStream(context, url);
- if (in != null) {
- return in;
- }
- }
-
- // Finally, fall back to default locale defined in chrome registry.
- url = getSearchPluginsJarURL(context, getFallbackLocale(), fileName);
- return GeckoJarReader.getStream(context, url);
- }
-
- /**
- * Finds a fallback locale in the Gecko chrome registry. If a locale is declared
- * here, we should be guaranteed to find a searchplugins directory for it.
- *
- * This method should only be accessed from the background thread.
- */
- private String getFallbackLocale() {
- if (fallbackLocale != null) {
- return fallbackLocale;
- }
-
- final InputStream in = GeckoJarReader.getStream(
- context, GeckoJarReader.getJarURL(context, "chrome/chrome.manifest"));
- if (in == null) {
- return null;
- }
- final BufferedReader br = getBufferedReader(in);
-
- try {
- String line;
- while ((line = br.readLine()) != null) {
- // We're looking for a line like "locale global en-US en-US/locale/en-US/global/"
- // https://developer.mozilla.org/en/docs/Chrome_Registration#locale
- if (line.startsWith("locale global ")) {
- fallbackLocale = line.split(" ", 4)[2];
- break;
- }
- }
- } catch (IOException e) {
- Log.e(LOG_TAG, "Error reading fallback locale from chrome registry", e);
- } finally {
- try {
- br.close();
- } catch (IOException e) {
- // Ignore.
- }
- }
- return fallbackLocale;
- }
-
- /**
- * Gets the jar URL for a file in the searchplugins directory.
- *
- * @param locale String representing the Gecko locale (e.g. "en-US").
- * @param fileName The name of the file to read.
- * @return URL for jar file.
- */
- private static String getSearchPluginsJarURL(Context context, String locale, String fileName) {
- final String path = "chrome/" + locale + "/locale/" + locale + "/browser/searchplugins/" + fileName;
- return GeckoJarReader.getJarURL(context, path);
- }
-
- private BufferedReader getBufferedReader(InputStream in) {
- try {
- return new BufferedReader(new InputStreamReader(in, "UTF-8"));
- } catch (UnsupportedEncodingException e) {
- // Cannot happen.
- return null;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueHelper.java b/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueHelper.java
deleted file mode 100644
index 667eb8f6c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueHelper.java
+++ /dev/null
@@ -1,357 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabqueue;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.graphics.PixelFormat;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.content.ContextCompat;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-import android.view.WindowManager;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class TabQueueHelper {
- private static final String LOGTAG = "Gecko" + TabQueueHelper.class.getSimpleName();
-
- // Disable Tab Queue for API level 10 (GB) - Bug 1206055
- public static final boolean TAB_QUEUE_ENABLED = true;
-
- public static final String FILE_NAME = "tab_queue_url_list.json";
- public static final String LOAD_URLS_ACTION = "TAB_QUEUE_LOAD_URLS_ACTION";
- public static final int TAB_QUEUE_NOTIFICATION_ID = R.id.tabQueueNotification;
-
- public static final String PREF_TAB_QUEUE_COUNT = "tab_queue_count";
- public static final String PREF_TAB_QUEUE_LAUNCHES = "tab_queue_launches";
- public static final String PREF_TAB_QUEUE_TIMES_PROMPT_SHOWN = "tab_queue_times_prompt_shown";
-
- public static final int MAX_TIMES_TO_SHOW_PROMPT = 3;
- public static final int EXTERNAL_LAUNCHES_BEFORE_SHOWING_PROMPT = 3;
-
- // result codes for returning from the prompt
- public static final int TAB_QUEUE_YES = 201;
- public static final int TAB_QUEUE_NO = 202;
-
- /**
- * Checks if the specified context can draw on top of other apps. As of API level 23, an app
- * cannot draw on top of other apps unless it declares the SYSTEM_ALERT_WINDOW permission in
- * its manifest, AND the user specifically grants the app this capability.
- *
- * @return true if the specified context can draw on top of other apps, false otherwise.
- */
- public static boolean canDrawOverlays(Context context) {
- if (AppConstants.Versions.preMarshmallow) {
- return true; // We got the permission at install time.
- }
-
- // It would be nice to just use Settings.canDrawOverlays() - but this helper is buggy for
- // apps using sharedUserId (See bug 1244722).
- // Instead we'll add and remove an invisible view. If this is successful then we seem to
- // have permission to draw overlays.
-
- View view = new View(context);
- view.setVisibility(View.INVISIBLE);
-
- WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
- 1, 1,
- WindowManager.LayoutParams.TYPE_PHONE,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
- PixelFormat.TRANSLUCENT);
-
- WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-
- try {
- windowManager.addView(view, layoutParams);
- windowManager.removeView(view);
- return true;
- } catch (final SecurityException | WindowManager.BadTokenException e) {
- return false;
- }
- }
-
- /**
- * Check if we should show the tab queue prompt
- *
- * @param context
- * @return true if we should display the prompt, false if not.
- */
- public static boolean shouldShowTabQueuePrompt(Context context) {
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
-
- int numberOfTimesTabQueuePromptSeen = prefs.getInt(PREF_TAB_QUEUE_TIMES_PROMPT_SHOWN, 0);
-
- // Exit early if the feature is already enabled or the user has seen the
- // prompt more than MAX_TIMES_TO_SHOW_PROMPT times.
- if (isTabQueueEnabled(prefs) || numberOfTimesTabQueuePromptSeen >= MAX_TIMES_TO_SHOW_PROMPT) {
- return false;
- }
-
- final int viewActionIntentLaunches = prefs.getInt(PREF_TAB_QUEUE_LAUNCHES, 0) + 1;
- if (viewActionIntentLaunches < EXTERNAL_LAUNCHES_BEFORE_SHOWING_PROMPT) {
- // Allow a few external links to open before we prompt the user.
- prefs.edit().putInt(PREF_TAB_QUEUE_LAUNCHES, viewActionIntentLaunches).apply();
- } else if (viewActionIntentLaunches == EXTERNAL_LAUNCHES_BEFORE_SHOWING_PROMPT) {
- // Reset to avoid repeatedly showing the prompt if the user doesn't interact with it and
- // we get more external VIEW action intents in.
- final SharedPreferences.Editor editor = prefs.edit();
- editor.remove(TabQueueHelper.PREF_TAB_QUEUE_LAUNCHES);
-
- int timesPromptShown = prefs.getInt(TabQueueHelper.PREF_TAB_QUEUE_TIMES_PROMPT_SHOWN, 0) + 1;
- editor.putInt(TabQueueHelper.PREF_TAB_QUEUE_TIMES_PROMPT_SHOWN, timesPromptShown);
- editor.apply();
-
- // Show the prompt
- return true;
- }
-
- return false;
- }
-
- /**
- * Reads file and converts any content to JSON, adds passed in URL to the data and writes back to the file,
- * creating the file if it doesn't already exist. This should not be run on the UI thread.
- *
- * @param profile
- * @param url URL to add
- * @param filename filename to add URL to
- * @return the number of tabs currently queued
- */
- public static int queueURL(final GeckoProfile profile, final String url, final String filename) {
- ThreadUtils.assertNotOnUiThread();
-
- JSONArray jsonArray = profile.readJSONArrayFromFile(filename);
-
- jsonArray.put(url);
-
- profile.writeFile(filename, jsonArray.toString());
-
- return jsonArray.length();
- }
-
- /**
- * Remove a url from the file, if it exists.
- * If the url exists multiple times, all instances of it will be removed.
- * This should not be run on the UI thread.
- *
- * @param context
- * @param urlToRemove URL to remove
- * @param filename filename to remove URL from
- * @return the number of queued urls
- */
- public static int removeURLFromFile(final Context context, final String urlToRemove, final String filename) {
- ThreadUtils.assertNotOnUiThread();
-
- final GeckoProfile profile = GeckoProfile.get(context);
-
- JSONArray jsonArray = profile.readJSONArrayFromFile(filename);
- JSONArray newArray = new JSONArray();
- String url;
-
- // Since JSONArray.remove was only added in API 19, we have to use two arrays in order to remove.
- for (int i = 0; i < jsonArray.length(); i++) {
- try {
- url = jsonArray.getString(i);
- } catch (JSONException e) {
- url = "";
- }
- if (!TextUtils.isEmpty(url) && !urlToRemove.equals(url)) {
- newArray.put(url);
- }
- }
-
- profile.writeFile(filename, newArray.toString());
-
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
- prefs.edit().putInt(PREF_TAB_QUEUE_COUNT, newArray.length()).apply();
-
- return newArray.length();
- }
-
- /**
- * Get up to eight of the last queued URLs for displaying in the notification.
- */
- public static List<String> getLastURLs(final Context context, final String filename) {
- final GeckoProfile profile = GeckoProfile.get(context);
- final JSONArray jsonArray = profile.readJSONArrayFromFile(filename);
- final List<String> urls = new ArrayList<>(8);
-
- for (int i = 0; i < 8; i++) {
- try {
- urls.add(jsonArray.getString(i));
- } catch (JSONException e) {
- Log.w(LOGTAG, "Unable to parse URL from tab queue array", e);
- }
- }
-
- return urls;
- }
-
- /**
- * Displays a notification showing the total number of tabs queue. If there is already a notification displayed, it
- * will be replaced.
- *
- * @param context
- * @param tabsQueued
- */
- public static void showNotification(final Context context, final int tabsQueued, final List<String> urls) {
- ThreadUtils.assertNotOnUiThread();
-
- Intent resultIntent = new Intent();
- resultIntent.setClassName(context, AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
- resultIntent.setAction(TabQueueHelper.LOAD_URLS_ACTION);
-
- PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, resultIntent, PendingIntent.FLAG_CANCEL_CURRENT);
-
- final String text;
- final Resources resources = context.getResources();
- if (tabsQueued == 1) {
- text = resources.getString(R.string.tab_queue_notification_text_singular);
- } else {
- text = resources.getString(R.string.tab_queue_notification_text_plural, tabsQueued);
- }
-
- NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
- inboxStyle.setBigContentTitle(text);
- for (String url : urls) {
- inboxStyle.addLine(url);
- }
- inboxStyle.setSummaryText(resources.getString(R.string.tab_queue_notification_title));
-
- NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
- .setSmallIcon(R.drawable.ic_status_logo)
- .setContentTitle(text)
- .setContentText(resources.getString(R.string.tab_queue_notification_title))
- .setStyle(inboxStyle)
- .setColor(ContextCompat.getColor(context, R.color.fennec_ui_orange))
- .setNumber(tabsQueued)
- .setContentIntent(pendingIntent);
-
- NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- notificationManager.notify(TabQueueHelper.TAB_QUEUE_NOTIFICATION_ID, builder.build());
- }
-
- public static boolean shouldOpenTabQueueUrls(final Context context) {
- ThreadUtils.assertNotOnUiThread();
-
- // TODO: Use profile shared prefs when bug 1147925 gets fixed.
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
-
- int tabsQueued = prefs.getInt(PREF_TAB_QUEUE_COUNT, 0);
-
- return isTabQueueEnabled(prefs) && tabsQueued > 0;
- }
-
- public static int getTabQueueLength(final Context context) {
- ThreadUtils.assertNotOnUiThread();
-
- // TODO: Use profile shared prefs when bug 1147925 gets fixed.
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
- return prefs.getInt(PREF_TAB_QUEUE_COUNT, 0);
- }
-
- public static void openQueuedUrls(final Context context, final GeckoProfile profile, final String filename, boolean shouldPerformJavaScriptCallback) {
- ThreadUtils.assertNotOnUiThread();
-
- removeNotification(context);
-
- // exit early if we don't have any tabs queued
- if (getTabQueueLength(context) < 1) {
- return;
- }
-
- JSONArray jsonArray = profile.readJSONArrayFromFile(filename);
-
- if (jsonArray.length() > 0) {
- JSONObject data = new JSONObject();
- try {
- data.put("urls", jsonArray);
- data.put("shouldNotifyTabsOpenedToJava", shouldPerformJavaScriptCallback);
- GeckoAppShell.notifyObservers("Tabs:OpenMultiple", data.toString());
- } catch (JSONException e) {
- // Don't exit early as we perform cleanup at the end of this function.
- Log.e(LOGTAG, "Error sending tab queue data", e);
- }
- }
-
- try {
- profile.deleteFileFromProfileDir(filename);
- } catch (IllegalArgumentException e) {
- Log.e(LOGTAG, "Error deleting Tab Queue data file.", e);
- }
-
- // TODO: Use profile shared prefs when bug 1147925 gets fixed.
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
- prefs.edit().remove(PREF_TAB_QUEUE_COUNT).apply();
- }
-
- protected static void removeNotification(Context context) {
- NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- notificationManager.cancel(TAB_QUEUE_NOTIFICATION_ID);
- }
-
- public static boolean processTabQueuePromptResponse(int resultCode, Context context) {
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
- final SharedPreferences.Editor editor = prefs.edit();
-
- switch (resultCode) {
- case TAB_QUEUE_YES:
- editor.putBoolean(GeckoPreferences.PREFS_TAB_QUEUE, true);
-
- // By making this one more than EXTERNAL_LAUNCHES_BEFORE_SHOWING_PROMPT we ensure the prompt
- // will never show again without having to keep track of an extra pref.
- editor.putInt(TabQueueHelper.PREF_TAB_QUEUE_LAUNCHES,
- TabQueueHelper.EXTERNAL_LAUNCHES_BEFORE_SHOWING_PROMPT + 1);
- break;
-
- case TAB_QUEUE_NO:
- // The user clicked the 'no' button, so let's make sure the user never sees the prompt again by
- // maxing out the pref used to count the VIEW action intents received and times they've seen the prompt.
-
- editor.putInt(TabQueueHelper.PREF_TAB_QUEUE_LAUNCHES,
- TabQueueHelper.EXTERNAL_LAUNCHES_BEFORE_SHOWING_PROMPT + 1);
-
- editor.putInt(TabQueueHelper.PREF_TAB_QUEUE_TIMES_PROMPT_SHOWN,
- TabQueueHelper.MAX_TIMES_TO_SHOW_PROMPT + 1);
- break;
-
- default:
- // We shouldn't ever get here.
- Log.w(LOGTAG, "Unrecognized result code received from the tab queue prompt: " + resultCode);
- }
-
- editor.apply();
-
- return resultCode == TAB_QUEUE_YES;
- }
-
- public static boolean isTabQueueEnabled(Context context) {
- return isTabQueueEnabled(GeckoSharedPrefs.forApp(context));
- }
-
- public static boolean isTabQueueEnabled(SharedPreferences prefs) {
- return prefs.getBoolean(GeckoPreferences.PREFS_TAB_QUEUE, false);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueuePrompt.java b/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueuePrompt.java
deleted file mode 100644
index ead16ccba..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueuePrompt.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabqueue;
-
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.Locales;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-
-import android.annotation.TargetApi;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.Toast;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-
-public class TabQueuePrompt extends Locales.LocaleAwareActivity {
- public static final String LOGTAG = "Gecko" + TabQueuePrompt.class.getSimpleName();
-
- private static final int SETTINGS_REQUEST_CODE = 1;
-
- // Flag set during animation to prevent animation multiple-start.
- private boolean isAnimating;
-
- private View containerView;
- private View buttonContainer;
- private View enabledConfirmation;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- showTabQueueEnablePrompt();
- }
-
- private void showTabQueueEnablePrompt() {
- setContentView(R.layout.tab_queue_prompt);
-
- final View okButton = findViewById(R.id.ok_button);
- okButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- onConfirmButtonPressed();
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "tabqueue_prompt_yes");
- }
- });
- findViewById(R.id.cancel_button).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "tabqueue_prompt_no");
- setResult(TabQueueHelper.TAB_QUEUE_NO);
- finish();
- }
- });
- final View settingsButton = findViewById(R.id.settings_button);
- settingsButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- onSettingsButtonPressed();
- }
- });
-
- final View tipView = findViewById(R.id.tip_text);
- final View settingsPermitView = findViewById(R.id.settings_permit_text);
-
- if (TabQueueHelper.canDrawOverlays(this)) {
- okButton.setVisibility(View.VISIBLE);
- settingsButton.setVisibility(View.GONE);
- tipView.setVisibility(View.VISIBLE);
- settingsPermitView.setVisibility(View.GONE);
- } else {
- okButton.setVisibility(View.GONE);
- settingsButton.setVisibility(View.VISIBLE);
- tipView.setVisibility(View.GONE);
- settingsPermitView.setVisibility(View.VISIBLE);
- }
-
- containerView = findViewById(R.id.tab_queue_container);
- buttonContainer = findViewById(R.id.button_container);
- enabledConfirmation = findViewById(R.id.enabled_confirmation);
-
- containerView.setTranslationY(500);
- containerView.setAlpha(0);
-
- final Animator translateAnimator = ObjectAnimator.ofFloat(containerView, "translationY", 0);
- translateAnimator.setDuration(400);
-
- final Animator alphaAnimator = ObjectAnimator.ofFloat(containerView, "alpha", 1);
- alphaAnimator.setStartDelay(200);
- alphaAnimator.setDuration(600);
-
- final AnimatorSet set = new AnimatorSet();
- set.playTogether(alphaAnimator, translateAnimator);
- set.setStartDelay(400);
-
- set.start();
- }
-
- @Override
- public void finish() {
- super.finish();
-
- // Don't perform an activity-dismiss animation.
- overridePendingTransition(0, 0);
- }
-
- private void onConfirmButtonPressed() {
- enabledConfirmation.setVisibility(View.VISIBLE);
- enabledConfirmation.setAlpha(0);
-
- final Animator buttonsAlphaAnimator = ObjectAnimator.ofFloat(buttonContainer, "alpha", 0);
- buttonsAlphaAnimator.setDuration(300);
-
- final Animator messagesAlphaAnimator = ObjectAnimator.ofFloat(enabledConfirmation, "alpha", 1);
- messagesAlphaAnimator.setDuration(300);
- messagesAlphaAnimator.setStartDelay(200);
-
- final AnimatorSet set = new AnimatorSet();
- set.playTogether(buttonsAlphaAnimator, messagesAlphaAnimator);
-
- set.addListener(new AnimatorListenerAdapter() {
-
- @Override
- public void onAnimationEnd(Animator animation) {
-
- new Handler().postDelayed(new Runnable() {
- @Override
- public void run() {
- slideOut();
- setResult(TabQueueHelper.TAB_QUEUE_YES);
- }
- }, 1000);
- }
- });
-
- set.start();
- }
-
- @TargetApi(Build.VERSION_CODES.M)
- private void onSettingsButtonPressed() {
- Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
- intent.setData(Uri.parse("package:" + getPackageName()));
- startActivityForResult(intent, SETTINGS_REQUEST_CODE);
-
- Toast.makeText(this, R.string.tab_queue_prompt_permit_drawing_over_apps, Toast.LENGTH_LONG).show();
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode != SETTINGS_REQUEST_CODE) {
- return;
- }
-
- if (TabQueueHelper.canDrawOverlays(this)) {
- // User granted the permission in Android's settings.
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.BUTTON, "tabqueue_prompt_yes");
-
- setResult(TabQueueHelper.TAB_QUEUE_YES);
- finish();
- }
- }
-
- /**
- * Slide the overlay down off the screen and destroy it.
- */
- private void slideOut() {
- if (isAnimating) {
- return;
- }
-
- isAnimating = true;
-
- ObjectAnimator animator = ObjectAnimator.ofFloat(containerView, "translationY", containerView.getHeight());
- animator.addListener(new AnimatorListenerAdapter() {
-
- @Override
- public void onAnimationEnd(Animator animation) {
- finish();
- }
-
- });
- animator.start();
- }
-
- /**
- * Close the dialog if back is pressed.
- */
- @Override
- public void onBackPressed() {
- slideOut();
- }
-
- /**
- * Close the dialog if the anything that isn't a button is tapped.
- */
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- slideOut();
- return true;
- }
-} \ No newline at end of file
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueService.java b/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueService.java
deleted file mode 100644
index ebb1bd761..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabQueueService.java
+++ /dev/null
@@ -1,342 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabqueue;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-
-import android.annotation.TargetApi;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.graphics.PixelFormat;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.provider.Settings;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationManagerCompat;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.TextView;
-import android.widget.Toast;
-import org.mozilla.gecko.mozglue.SafeIntent;
-
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-
-/**
- * On launch this Service displays a View over the currently running process with an action to open the url in Fennec
- * immediately. If the user takes no action, allowing the runnable to be processed after the specified
- * timeout (TOAST_TIMEOUT), the url is added to a file which is then read in Fennec on next launch, this allows the
- * user to quickly queue urls to open without having to open Fennec each time. If the Service receives an Intent whilst
- * the created View is still active, the old url is immediately processed and the View is re-purposed with the new
- * Intent data.
- * <p/>
- * The SYSTEM_ALERT_WINDOW permission is used to allow us to insert a View from this Service which responds to user
- * interaction, whilst still allowing whatever is in the background to be seen and interacted with.
- * <p/>
- * Using an Activity to do this doesn't seem to work as there's an issue to do with the native android intent resolver
- * dialog not being hidden when the toast is shown. Using an IntentService instead of a Service doesn't work as
- * each new Intent received kicks off the IntentService lifecycle anew which means that a new View is created each time,
- * meaning that we can't quickly queue the current data and re-purpose the View. The asynchronous nature of the
- * IntentService is another prohibitive factor.
- * <p/>
- * General approach taken is similar to the FB chat heads functionality:
- * http://stackoverflow.com/questions/15975988/what-apis-in-android-is-facebook-using-to-create-chat-heads
- */
-public class TabQueueService extends Service {
- private static final String LOGTAG = "Gecko" + TabQueueService.class.getSimpleName();
-
- private static final long TOAST_TIMEOUT = 3000;
- private static final long TOAST_DOUBLE_TAP_TIMEOUT_MILLIS = 6000;
-
- private WindowManager windowManager;
- private View toastLayout;
- private Button openNowButton;
- private Handler tabQueueHandler;
- private WindowManager.LayoutParams toastLayoutParams;
- private volatile StopServiceRunnable stopServiceRunnable;
- private HandlerThread handlerThread;
- private ExecutorService executorService;
-
- @Override
- public IBinder onBind(Intent intent) {
- // Not used
- return null;
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- executorService = Executors.newSingleThreadExecutor();
-
- handlerThread = new HandlerThread("TabQueueHandlerThread");
- handlerThread.start();
- tabQueueHandler = new Handler(handlerThread.getLooper());
-
- windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
-
- LayoutInflater layoutInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
- toastLayout = layoutInflater.inflate(R.layout.tab_queue_toast, null);
-
- final Resources resources = getResources();
-
- TextView messageView = (TextView) toastLayout.findViewById(R.id.toast_message);
- messageView.setText(resources.getText(R.string.tab_queue_toast_message));
-
- openNowButton = (Button) toastLayout.findViewById(R.id.toast_button);
- openNowButton.setText(resources.getText(R.string.tab_queue_toast_action));
-
- toastLayoutParams = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.TYPE_PHONE,
- WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
- WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
- PixelFormat.TRANSLUCENT);
-
- toastLayoutParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
- }
-
- @Override
- public int onStartCommand(final Intent intent, final int flags, final int startId) {
- // If this is a redelivery then lets bypass the entire double tap to open now code as that's a big can of worms,
- // we also don't expect redeliveries because of the short time window associated with this feature.
- if (flags != START_FLAG_REDELIVERY) {
- final Context applicationContext = getApplicationContext();
- final SharedPreferences sharedPreferences = GeckoSharedPrefs.forApp(applicationContext);
-
- final String lastUrl = sharedPreferences.getString(GeckoPreferences.PREFS_TAB_QUEUE_LAST_SITE, "");
-
- final SafeIntent safeIntent = new SafeIntent(intent);
- final String intentUrl = safeIntent.getDataString();
-
- final long lastRunTime = sharedPreferences.getLong(GeckoPreferences.PREFS_TAB_QUEUE_LAST_TIME, 0);
- final boolean isWithinDoubleTapTimeLimit = System.currentTimeMillis() - lastRunTime < TOAST_DOUBLE_TAP_TIMEOUT_MILLIS;
-
- if (!TextUtils.isEmpty(lastUrl) && lastUrl.equals(intentUrl) && isWithinDoubleTapTimeLimit) {
- // Background thread because we could do some file IO if we have to remove a url from the list.
- tabQueueHandler.post(new Runnable() {
- @Override
- public void run() {
- // If there is a runnable around, that means that the previous process hasn't yet completed, so
- // we will need to prevent it from running and remove the view from the window manager.
- // If there is no runnable around then the url has already been added to the list, so we'll
- // need to remove it before proceeding or that url will open multiple times.
- if (stopServiceRunnable != null) {
- tabQueueHandler.removeCallbacks(stopServiceRunnable);
- stopSelfResult(stopServiceRunnable.getStartId());
- stopServiceRunnable = null;
- removeView();
- } else {
- TabQueueHelper.removeURLFromFile(applicationContext, intentUrl, TabQueueHelper.FILE_NAME);
- }
- openNow(safeIntent.getUnsafe());
-
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "tabqueue-doubletap");
- stopSelfResult(startId);
- }
- });
-
- return START_REDELIVER_INTENT;
- }
-
- sharedPreferences.edit().putString(GeckoPreferences.PREFS_TAB_QUEUE_LAST_SITE, intentUrl)
- .putLong(GeckoPreferences.PREFS_TAB_QUEUE_LAST_TIME, System.currentTimeMillis())
- .apply();
- }
-
- if (stopServiceRunnable != null) {
- // If we're already displaying a toast, keep displaying it but store the previous url.
- // The open button will refer to the most recently opened link.
- tabQueueHandler.removeCallbacks(stopServiceRunnable);
- stopServiceRunnable.run(false);
- } else {
- try {
- windowManager.addView(toastLayout, toastLayoutParams);
- } catch (final SecurityException | WindowManager.BadTokenException e) {
- Toast.makeText(this, getText(R.string.tab_queue_toast_message), Toast.LENGTH_SHORT).show();
- showSettingsNotification();
- }
- }
-
- stopServiceRunnable = new StopServiceRunnable(startId) {
- @Override
- public void onRun() {
- addURLToTabQueue(intent, TabQueueHelper.FILE_NAME);
- stopServiceRunnable = null;
- }
- };
-
- openNowButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View view) {
- tabQueueHandler.removeCallbacks(stopServiceRunnable);
- stopServiceRunnable = null;
- removeView();
- openNow(intent);
-
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "tabqueue-now");
- stopSelfResult(startId);
- }
- });
-
- tabQueueHandler.postDelayed(stopServiceRunnable, TOAST_TIMEOUT);
-
- return START_REDELIVER_INTENT;
- }
-
- private void openNow(Intent intent) {
- Intent forwardIntent = new Intent(intent);
- forwardIntent.setClassName(getApplicationContext(), AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
- forwardIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(forwardIntent);
-
- TabQueueHelper.removeNotification(getApplicationContext());
-
- GeckoSharedPrefs.forApp(getApplicationContext()).edit().remove(GeckoPreferences.PREFS_TAB_QUEUE_LAST_SITE)
- .remove(GeckoPreferences.PREFS_TAB_QUEUE_LAST_TIME)
- .apply();
-
- executorService.submit(new Runnable() {
- @Override
- public void run() {
- int queuedTabCount = TabQueueHelper.getTabQueueLength(TabQueueService.this);
- Telemetry.addToHistogram("FENNEC_TABQUEUE_QUEUESIZE", queuedTabCount);
- }
- });
-
- }
-
- @TargetApi(Build.VERSION_CODES.M)
- private void showSettingsNotification() {
- if (AppConstants.Versions.preMarshmallow) {
- return;
- }
-
- final Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
- intent.setData(Uri.parse("package:" + getPackageName()));
- PendingIntent pendingIntent = PendingIntent.getActivity(this, intent.hashCode(), intent, 0);
-
- final String text = getString(R.string.tab_queue_notification_settings);
-
- final NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle()
- .bigText(text);
-
- final Notification notification = new NotificationCompat.Builder(this)
- .setContentTitle(getString(R.string.pref_tab_queue_title))
- .setContentText(text)
- .setCategory(NotificationCompat.CATEGORY_ERROR)
- .setStyle(style)
- .setSmallIcon(R.drawable.ic_status_logo)
- .setContentIntent(pendingIntent)
- .setPriority(NotificationCompat.PRIORITY_MAX)
- .setAutoCancel(true)
- .addAction(R.drawable.ic_action_settings, getString(R.string.tab_queue_prompt_settings_button), pendingIntent)
- .build();
-
- NotificationManagerCompat.from(this).notify(R.id.tabQueueSettingsNotification, notification);
- }
-
- private void removeView() {
- try {
- windowManager.removeView(toastLayout);
- } catch (IllegalArgumentException | IllegalStateException e) {
- // This can happen if the Service is killed by the system. If this happens the View will have already
- // been removed but the runnable will have been kept alive.
- Log.e(LOGTAG, "Error removing Tab Queue toast from service", e);
- }
- }
-
- private void addURLToTabQueue(final Intent intent, final String filename) {
- if (intent == null) {
- // This should never happen, but let's return silently instead of crashing if it does.
- Log.w(LOGTAG, "Error adding URL to tab queue - invalid intent passed in.");
- return;
- }
- final SafeIntent safeIntent = new SafeIntent(intent);
- final String intentData = safeIntent.getDataString();
-
- // As we're doing disk IO, let's run this stuff in a separate thread.
- executorService.submit(new Runnable() {
- @Override
- public void run() {
- Context applicationContext = getApplicationContext();
- final GeckoProfile profile = GeckoProfile.get(applicationContext);
- int tabsQueued = TabQueueHelper.queueURL(profile, intentData, filename);
- List<String> urls = TabQueueHelper.getLastURLs(applicationContext, filename);
-
- TabQueueHelper.showNotification(applicationContext, tabsQueued, urls);
-
- // Store the number of URLs queued so that we don't have to read and process the file to see if we have
- // any urls to open.
- // TODO: Use profile shared prefs when bug 1147925 gets fixed.
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(applicationContext);
-
- prefs.edit().putInt(TabQueueHelper.PREF_TAB_QUEUE_COUNT, tabsQueued).apply();
- }
- });
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- handlerThread.quit();
- }
-
- /**
- * A modified Runnable which additionally removes the view from the window view hierarchy and stops the service
- * when run, unless explicitly instructed not to.
- */
- private abstract class StopServiceRunnable implements Runnable {
-
- private final int startId;
-
- public StopServiceRunnable(final int startId) {
- this.startId = startId;
- }
-
- public void run() {
- run(true);
- }
-
- public void run(final boolean shouldRemoveView) {
- onRun();
-
- if (shouldRemoveView) {
- removeView();
- }
-
- stopSelfResult(startId);
- }
-
- public int getStartId() {
- return startId;
- }
-
- public abstract void onRun();
- }
-} \ No newline at end of file
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabReceivedService.java b/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabReceivedService.java
deleted file mode 100644
index 4f5baacdb..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabqueue/TabReceivedService.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabqueue;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.BrowserLocaleManager;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract;
-
-import android.app.IntentService;
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.support.annotation.Nullable;
-import android.support.annotation.WorkerThread;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationManagerCompat;
-import android.util.Log;
-
-/**
- * An IntentService that displays a notification for a tab sent to this device.
- *
- * The expected Intent should contain:
- * * Data: URI to open in the notification
- * * EXTRA_TITLE: Page title of the URI to open
- */
-public class TabReceivedService extends IntentService {
- private static final String LOGTAG = "Gecko" + TabReceivedService.class.getSimpleName();
-
- private static final String PREF_NOTIFICATION_ID = "tab_received_notification_id";
-
- private static final int MAX_NOTIFICATION_COUNT = 1000;
-
- public TabReceivedService() {
- super(LOGTAG);
- setIntentRedelivery(true);
- }
-
- @Override
- protected void onHandleIntent(final Intent intent) {
- // IntentServices don't keep the process alive so
- // we need to do this every time. Ideally, we wouldn't.
- final Resources res = getResources();
- BrowserLocaleManager.getInstance().correctLocale(this, res, res.getConfiguration());
-
- final String uri = intent.getDataString();
- if (uri == null) {
- Log.d(LOGTAG, "Received null uri – ignoring");
- return;
- }
-
- final Intent notificationIntent = new Intent(Intent.ACTION_VIEW, intent.getData());
- notificationIntent.putExtra(BrowserContract.SKIP_TAB_QUEUE_FLAG, true);
- final PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
-
- final String notificationTitle = getNotificationTitle(intent.getStringExtra(BrowserContract.EXTRA_CLIENT_GUID));
- final NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
- builder.setSmallIcon(R.drawable.flat_icon);
- builder.setContentTitle(notificationTitle);
- builder.setWhen(System.currentTimeMillis());
- builder.setAutoCancel(true);
- builder.setContentText(uri);
- builder.setContentIntent(contentIntent);
-
- // Trigger "heads-up" notification mode on supported Android versions.
- builder.setPriority(NotificationCompat.PRIORITY_HIGH);
- final Uri notificationSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
- if (notificationSoundUri != null) {
- builder.setSound(notificationSoundUri);
- }
-
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(this);
- final int notificationId = getNextNotificationId(prefs.getInt(PREF_NOTIFICATION_ID, 0));
- final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
- notificationManager.notify(notificationId, builder.build());
-
- // Save the ID last so if the Service is killed and the Intent is redelivered,
- // the ID is unlikely to have been updated and we would re-use the the old one.
- // This would prevent two identical notifications from appearing if the
- // notification was shown during the previous Intent processing attempt.
- prefs.edit().putInt(PREF_NOTIFICATION_ID, notificationId).apply();
- }
-
- /**
- * @param clientGUID the guid of the client in the clients table
- * @return the client's name from the clients table, if possible, else the brand name.
- */
- @WorkerThread
- private String getNotificationTitle(@Nullable final String clientGUID) {
- if (clientGUID == null) {
- Log.w(LOGTAG, "Received null guid, using brand name.");
- return AppConstants.MOZ_APP_DISPLAYNAME;
- }
-
- final Cursor c = getContentResolver().query(BrowserContract.Clients.CONTENT_URI,
- new String[] { BrowserContract.Clients.NAME },
- BrowserContract.Clients.GUID + "=?", new String[] { clientGUID }, null);
- try {
- if (c != null && c.moveToFirst()) {
- return c.getString(c.getColumnIndex(BrowserContract.Clients.NAME));
- } else {
- Log.w(LOGTAG, "Device not found, using brand name.");
- return AppConstants.MOZ_APP_DISPLAYNAME;
- }
- } finally {
- if (c != null) {
- c.close();
- }
- }
- }
-
- /**
- * Notification IDs must be unique else a notification
- * will be overwritten so we cycle them.
- */
- private int getNextNotificationId(final int currentId) {
- if (currentId > MAX_NOTIFICATION_COUNT) {
- return 0;
- } else {
- return currentId + 1;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/PrivateTabsPanel.java b/mobile/android/base/java/org/mozilla/gecko/tabs/PrivateTabsPanel.java
deleted file mode 100644
index b7bd83376..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/PrivateTabsPanel.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.tabs.TabsPanel.CloseAllPanelView;
-import org.mozilla.gecko.tabs.TabsPanel.TabsLayout;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.FrameLayout;
-
-/**
- * A container that wraps the private tabs {@link android.widget.AdapterView} and empty
- * {@link android.view.View} to manage both of their visibility states by changing the visibility of
- * this container as calling {@link android.widget.AdapterView#setVisibility} does not affect the
- * empty View's visibility.
- */
-class PrivateTabsPanel extends FrameLayout implements CloseAllPanelView {
- private final TabsLayout tabsLayout;
-
- public PrivateTabsPanel(final Context context, final AttributeSet attrs) {
- super(context, attrs);
-
- LayoutInflater.from(context).inflate(R.layout.private_tabs_panel, this);
- tabsLayout = (TabsLayout) findViewById(R.id.private_tabs_layout);
-
- final View emptyTabsFrame = findViewById(R.id.private_tabs_empty);
- tabsLayout.setEmptyView(emptyTabsFrame);
- }
-
- @Override
- public void setTabsPanel(final TabsPanel panel) {
- tabsLayout.setTabsPanel(panel);
- }
-
- @Override
- public void show() {
- tabsLayout.show();
- setVisibility(View.VISIBLE);
- }
-
- @Override
- public void hide() {
- setVisibility(View.GONE);
- tabsLayout.hide();
- }
-
- @Override
- public boolean shouldExpand() {
- return tabsLayout.shouldExpand();
- }
-
- @Override
- public void closeAll() {
- tabsLayout.closeAll();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabCurve.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabCurve.java
deleted file mode 100644
index 0b6a30d7a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabCurve.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import android.graphics.Path;
-
-/**
- * Utility methods to draws Firefox's tab curve shape.
- */
-public class TabCurve {
-
- public enum Direction {
- LEFT(-1),
- RIGHT(1);
-
- private final int value;
-
- private Direction(int value) {
- this.value = value;
- }
- }
-
- // Curve's aspect ratio
- private static final float ASPECT_RATIO = 0.729f;
-
- // Width multipliers
- private static final float W_M1 = 0.343f;
- private static final float W_M2 = 0.514f;
- private static final float W_M3 = 0.49f;
- private static final float W_M4 = 0.545f;
- private static final float W_M5 = 0.723f;
-
- // Height multipliers
- private static final float H_M1 = 0.25f;
- private static final float H_M2 = 0.5f;
- private static final float H_M3 = 0.72f;
- private static final float H_M4 = 0.961f;
-
- private TabCurve() {
- }
-
- public static float getWidthForHeight(float height) {
- return (int) (height * ASPECT_RATIO);
- }
-
- public static void drawFromTop(Path path, float from, float height, Direction dir) {
- final float width = getWidthForHeight(height);
-
- path.cubicTo(from + width * W_M1 * dir.value, 0.0f,
- from + width * W_M3 * dir.value, height * H_M1,
- from + width * W_M2 * dir.value, height * H_M2);
- path.cubicTo(from + width * W_M4 * dir.value, height * H_M3,
- from + width * W_M5 * dir.value, height * H_M4,
- from + width * dir.value, height);
- }
-
- public static void drawFromBottom(Path path, float from, float height, Direction dir) {
- final float width = getWidthForHeight(height);
-
- path.cubicTo(from + width * (1f - W_M5) * dir.value, height * H_M4,
- from + width * (1f - W_M4) * dir.value, height * H_M3,
- from + width * (1f - W_M2) * dir.value, height * H_M2);
- path.cubicTo(from + width * (1f - W_M3) * dir.value, height * H_M1,
- from + width * (1f - W_M1) * dir.value, 0.0f,
- from + width * dir.value, 0.0f);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryController.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryController.java
deleted file mode 100644
index 7b06c994c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryController.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.util.GeckoRequest;
-import org.mozilla.gecko.util.NativeJSObject;
-
-import android.util.Log;
-
-public class TabHistoryController {
- private static final String LOGTAG = "TabHistoryController";
- private final OnShowTabHistory showTabHistoryListener;
-
- public static enum HistoryAction {
- ALL,
- BACK,
- FORWARD
- };
-
- public interface OnShowTabHistory {
- void onShowHistory(List<TabHistoryPage> historyPageList, int toIndex);
- }
-
- public TabHistoryController(OnShowTabHistory showTabHistoryListener) {
- this.showTabHistoryListener = showTabHistoryListener;
- }
-
- /**
- * This method will show the history for the current tab.
- */
- public boolean showTabHistory(final Tab tab, final HistoryAction action) {
- JSONObject json = new JSONObject();
- try {
- json.put("action", action.name());
- json.put("tabId", tab.getId());
- } catch (JSONException e) {
- Log.e(LOGTAG, "JSON error", e);
- }
-
- GeckoAppShell.sendRequestToGecko(new GeckoRequest("Session:GetHistory", json) {
- @Override
- public void onResponse(NativeJSObject nativeJSObject) {
- /*
- * The response from gecko request is of the form
- * {
- * "historyItems" : [
- * {
- * "title": "google",
- * "url": "google.com",
- * "selected": false
- * }
- * ],
- * toIndex = 1
- * }
- */
-
- final NativeJSObject[] historyItems = nativeJSObject.getObjectArray("historyItems");
- if (historyItems.length == 0) {
- // Empty history, return without showing the popup.
- return;
- }
-
- final List<TabHistoryPage> historyPageList = new ArrayList<>(historyItems.length);
- final int toIndex = nativeJSObject.getInt("toIndex");
-
- for (NativeJSObject obj : historyItems) {
- final String title = obj.getString("title");
- final String url = obj.getString("url");
- final boolean selected = obj.getBoolean("selected");
- historyPageList.add(new TabHistoryPage(title, url, selected));
- }
-
- showTabHistoryListener.onShowHistory(historyPageList, toIndex);
- }
- });
- return true;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryFragment.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryFragment.java
deleted file mode 100644
index e6deabdcf..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryFragment.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentTransaction;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
-
-public class TabHistoryFragment extends Fragment implements OnItemClickListener, OnClickListener {
- private static final String ARG_LIST = "historyPageList";
- private static final String ARG_INDEX = "index";
- private static final String BACK_STACK_ID = "backStateId";
-
- private List<TabHistoryPage> historyPageList;
- private int toIndex;
- private ListView dialogList;
- private int backStackId = -1;
- private ViewGroup parent;
- private boolean dismissed;
-
- public TabHistoryFragment() {
-
- }
-
- public static TabHistoryFragment newInstance(List<TabHistoryPage> historyPageList, int toIndex) {
- final TabHistoryFragment fragment = new TabHistoryFragment();
- final Bundle args = new Bundle();
- args.putParcelableArrayList(ARG_LIST, (ArrayList<? extends Parcelable>) historyPageList);
- args.putInt(ARG_INDEX, toIndex);
- fragment.setArguments(args);
- return fragment;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (savedInstanceState != null) {
- backStackId = savedInstanceState.getInt(BACK_STACK_ID, -1);
- }
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- this.parent = container;
- parent.setVisibility(View.VISIBLE);
- View view = inflater.inflate(R.layout.tab_history_layout, container, false);
- view.setOnClickListener(this);
- dialogList = (ListView) view.findViewById(R.id.tab_history_list);
- dialogList.setDivider(null);
- return view;
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- Bundle bundle = getArguments();
- historyPageList = bundle.getParcelableArrayList(ARG_LIST);
- toIndex = bundle.getInt(ARG_INDEX);
- final ArrayAdapter<TabHistoryPage> urlAdapter = new TabHistoryAdapter(getActivity(), historyPageList);
- dialogList.setAdapter(urlAdapter);
- dialogList.setOnItemClickListener(this);
- }
-
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- String index = String.valueOf(toIndex - position);
- GeckoAppShell.notifyObservers("Session:Navigate", index);
- dismiss();
- }
-
- @Override
- public void onClick(View v) {
- // Since the fragment view fills the entire screen, any clicks outside of the history
- // ListView will end up here.
- dismiss();
- }
-
- @Override
- public void onPause() {
- super.onPause();
- dismiss();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- dismiss();
-
- GeckoApplication.watchReference(getActivity(), this);
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- if (backStackId >= 0) {
- outState.putInt(BACK_STACK_ID, backStackId);
- }
- }
-
- // Function to add this fragment to activity state with containerViewId as parent.
- // This similar in functionality to DialogFragment.show() except that containerId is provided here.
- public void show(final int containerViewId, final FragmentTransaction transaction, final String tag) {
- dismissed = false;
- transaction.add(containerViewId, this, tag);
- transaction.addToBackStack(tag);
- // Populating the tab history requires a gecko call (which can be slow) - therefore the app
- // state by the time we try to show this fragment is unknown, and we could be in the
- // middle of shutting down:
- backStackId = transaction.commitAllowingStateLoss();
- }
-
- // Pop the fragment from backstack if it exists.
- public void dismiss() {
- if (dismissed) {
- return;
- }
-
- dismissed = true;
-
- if (backStackId >= 0) {
- getFragmentManager().popBackStackImmediate(backStackId, FragmentManager.POP_BACK_STACK_INCLUSIVE);
- backStackId = -1;
- }
-
- if (parent != null) {
- parent.setVisibility(View.GONE);
- }
- }
-
- private static class TabHistoryAdapter extends ArrayAdapter<TabHistoryPage> {
- private final List<TabHistoryPage> pages;
- private final Context context;
-
- public TabHistoryAdapter(Context context, List<TabHistoryPage> pages) {
- super(context, R.layout.tab_history_item_row, pages);
- this.context = context;
- this.pages = pages;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- TabHistoryItemRow row = (TabHistoryItemRow) convertView;
- if (row == null) {
- row = new TabHistoryItemRow(context, null);
- }
-
- row.update(pages.get(position), position == 0, position == pages.size() - 1);
- return row;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryItemRow.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryItemRow.java
deleted file mode 100644
index 112dbc07d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryItemRow.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import android.content.Context;
-import android.graphics.Typeface;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.widget.FaviconView;
-
-import java.util.concurrent.Future;
-
-public class TabHistoryItemRow extends RelativeLayout {
- private final FaviconView favicon;
- private final TextView title;
- private final ImageView timeLineTop;
- private final ImageView timeLineBottom;
- private Future<IconResponse> ongoingIconLoad;
-
- public TabHistoryItemRow(Context context, AttributeSet attrs) {
- super(context, attrs);
- LayoutInflater.from(context).inflate(R.layout.tab_history_item_row, this);
- favicon = (FaviconView) findViewById(R.id.tab_history_icon);
- title = (TextView) findViewById(R.id.tab_history_title);
- timeLineTop = (ImageView) findViewById(R.id.tab_history_timeline_top);
- timeLineBottom = (ImageView) findViewById(R.id.tab_history_timeline_bottom);
- }
-
- // Update the views with historic page detail.
- public void update(final TabHistoryPage historyPage, boolean isFirstElement, boolean isLastElement) {
- ThreadUtils.assertOnUiThread();
-
- timeLineTop.setVisibility(isFirstElement ? View.INVISIBLE : View.VISIBLE);
- timeLineBottom.setVisibility(isLastElement ? View.INVISIBLE : View.VISIBLE);
- title.setText(historyPage.getTitle());
-
- if (historyPage.isSelected()) {
- // Highlight title with bold font.
- title.setTypeface(null, Typeface.BOLD);
- } else {
- // Clear previously set bold font.
- title.setTypeface(null, Typeface.NORMAL);
- }
-
- favicon.setEnabled(historyPage.isSelected());
- favicon.clearImage();
-
- if (ongoingIconLoad != null) {
- ongoingIconLoad.cancel(true);
- }
-
- ongoingIconLoad = Icons.with(getContext())
- .pageUrl(historyPage.getUrl())
- .skipNetwork()
- .build()
- .execute(favicon.createIconCallback());
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryPage.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryPage.java
deleted file mode 100644
index 6c608b2ac..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabHistoryPage.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-public class TabHistoryPage implements Parcelable {
- private final String title;
- private final String url;
- private final boolean selected;
-
- public TabHistoryPage(String title, String url, boolean selected) {
- this.title = title;
- this.url = url;
- this.selected = selected;
- }
-
- public String getTitle() {
- return title;
- }
-
- public String getUrl() {
- return url;
- }
-
- public boolean isSelected() {
- return selected;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(title);
- dest.writeString(url);
- dest.writeInt(selected ? 1 : 0);
- }
-
- public static final Parcelable.Creator<TabHistoryPage> CREATOR = new Parcelable.Creator<TabHistoryPage>() {
- @Override
- public TabHistoryPage createFromParcel(final Parcel source) {
- final String title = source.readString();
- final String url = source.readString();
- final boolean selected = source.readByte() != 0;
-
- final TabHistoryPage page = new TabHistoryPage(title, url, selected);
- return page;
- }
-
- @Override
- public TabHistoryPage[] newArray(int size) {
- return new TabHistoryPage[size];
- }
- };
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabPanelBackButton.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabPanelBackButton.java
deleted file mode 100644
index 7ea02407e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabPanelBackButton.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-
-public class TabPanelBackButton extends ImageButton {
-
- private int dividerWidth = 0;
-
- private final Drawable divider;
- private final int dividerPadding;
-
- public TabPanelBackButton(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabPanelBackButton);
- divider = a.getDrawable(R.styleable.TabPanelBackButton_rightDivider);
- dividerPadding = (int) a.getDimension(R.styleable.TabPanelBackButton_dividerVerticalPadding, 0);
- a.recycle();
-
- if (divider != null) {
- dividerWidth = divider.getIntrinsicWidth();
- }
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- setMeasuredDimension(getMeasuredWidth() + dividerWidth, getMeasuredHeight());
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- if (divider != null) {
- final ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
- final int left = getRight() - lp.rightMargin - dividerWidth;
-
- divider.setBounds(left, getPaddingTop() + dividerPadding,
- left + dividerWidth, getHeight() - getPaddingBottom() - dividerPadding);
- divider.draw(canvas);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStrip.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabStrip.java
deleted file mode 100644
index 5d3719343..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStrip.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.StateListDrawable;
-import android.graphics.Rect;
-import android.support.v4.content.ContextCompat;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.TouchDelegate;
-import android.view.View;
-import android.view.ViewTreeObserver;
-
-import org.mozilla.gecko.BrowserApp.TabStripInterface;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.widget.themed.ThemedImageButton;
-import org.mozilla.gecko.widget.themed.ThemedLinearLayout;
-
-public class TabStrip extends ThemedLinearLayout
- implements TabStripInterface {
- private static final String LOGTAG = "GeckoTabStrip";
-
- private final TabStripView tabStripView;
- private final ThemedImageButton addTabButton;
-
- private final TabsListener tabsListener;
- private OnTabAddedOrRemovedListener tabChangedListener;
-
- public TabStrip(Context context) {
- this(context, null);
- }
-
- public TabStrip(Context context, AttributeSet attrs) {
- super(context, attrs);
- setOrientation(HORIZONTAL);
-
- LayoutInflater.from(context).inflate(R.layout.tab_strip_inner, this);
- tabStripView = (TabStripView) findViewById(R.id.tab_strip);
-
- addTabButton = (ThemedImageButton) findViewById(R.id.add_tab);
- addTabButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- final Tabs tabs = Tabs.getInstance();
- if (isPrivateMode()) {
- tabs.addPrivateTab();
- } else {
- tabs.addTab();
- }
- }
- });
-
- getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- getViewTreeObserver().removeOnPreDrawListener(this);
-
- final Rect r = new Rect();
- r.left = addTabButton.getRight();
- r.right = getWidth();
- r.top = 0;
- r.bottom = getHeight();
-
- // Redirect touch events between the 'new tab' button and the edge
- // of the screen to the 'new tab' button.
- setTouchDelegate(new TouchDelegate(r, addTabButton));
-
- return true;
- }
- });
-
- tabsListener = new TabsListener();
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- Tabs.registerOnTabsChangedListener(tabsListener);
- tabStripView.refreshTabs();
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- Tabs.unregisterOnTabsChangedListener(tabsListener);
- tabStripView.clearTabs();
- }
-
- @Override
- public void setPrivateMode(boolean isPrivate) {
- super.setPrivateMode(isPrivate);
- addTabButton.setPrivateMode(isPrivate);
- }
-
- public void setOnTabChangedListener(OnTabAddedOrRemovedListener listener) {
- tabChangedListener = listener;
- }
-
- private class TabsListener implements Tabs.OnTabsChangedListener {
- @Override
- public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
- switch (msg) {
- case RESTORED:
- tabStripView.restoreTabs();
- break;
-
- case ADDED:
- tabStripView.addTab(tab);
- if (tabChangedListener != null) {
- tabChangedListener.onTabChanged();
- }
- break;
-
- case CLOSED:
- tabStripView.removeTab(tab);
- if (tabChangedListener != null) {
- tabChangedListener.onTabChanged();
- }
- break;
-
- case SELECTED:
- // Update the selected position, then fall through...
- tabStripView.selectTab(tab);
- setPrivateMode(tab.isPrivate());
- case UNSELECTED:
- // We just need to update the style for the unselected tab...
- case TITLE:
- case FAVICON:
- case RECORDING_CHANGE:
- case AUDIO_PLAYING_CHANGE:
- tabStripView.updateTab(tab);
- break;
- }
- }
- }
-
- @Override
- public void refresh() {
- tabStripView.refresh();
- }
-
- @Override
- public void onLightweightThemeChanged() {
- final Drawable drawable = getTheme().getDrawable(this);
- if (drawable == null) {
- return;
- }
-
- final StateListDrawable stateList = new StateListDrawable();
- stateList.addState(PRIVATE_STATE_SET, getColorDrawable(R.color.text_and_tabs_tray_grey));
- stateList.addState(EMPTY_STATE_SET, drawable);
-
- setBackgroundDrawable(stateList);
- }
-
- @Override
- public void onLightweightThemeReset() {
- final int defaultBackgroundColor = ContextCompat.getColor(getContext(), R.color.text_and_tabs_tray_grey);
- setBackgroundColor(defaultBackgroundColor);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripAdapter.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripAdapter.java
deleted file mode 100644
index 8778aac31..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripAdapter.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-
-class TabStripAdapter extends BaseAdapter {
- private static final String LOGTAG = "GeckoTabStripAdapter";
-
- private final Context context;
- private List<Tab> tabs;
-
- public TabStripAdapter(Context context) {
- this.context = context;
- }
-
- @Override
- public Tab getItem(int position) {
- return (tabs != null &&
- position >= 0 &&
- position < tabs.size() ? tabs.get(position) : null);
- }
-
- @Override
- public long getItemId(int position) {
- final Tab tab = getItem(position);
- return (tab != null ? tab.getId() : -1);
- }
-
- @Override
- public boolean hasStableIds() {
- return true;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final TabStripItemView item;
- if (convertView == null) {
- item = (TabStripItemView)
- LayoutInflater.from(context).inflate(R.layout.tab_strip_item, parent, false);
- } else {
- item = (TabStripItemView) convertView;
- }
-
- final Tab tab = tabs.get(position);
- item.updateFromTab(tab);
-
- return item;
- }
-
- @Override
- public int getCount() {
- return (tabs != null ? tabs.size() : 0);
- }
-
- int getPositionForTab(Tab tab) {
- if (tabs == null || tab == null) {
- return -1;
- }
-
- return tabs.indexOf(tab);
- }
-
- void removeTab(Tab tab) {
- if (tabs == null) {
- return;
- }
-
- tabs.remove(tab);
- notifyDataSetChanged();
- }
-
- void refresh(List<Tab> tabs) {
- // The list of tabs is guaranteed to be non-null.
- // See TabStripView.refreshTabs().
- this.tabs = tabs;
- notifyDataSetChanged();
- }
-
- void clear() {
- tabs = null;
- notifyDataSetInvalidated();
- }
-} \ No newline at end of file
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripItemView.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripItemView.java
deleted file mode 100644
index 27eaed125..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripItemView.java
+++ /dev/null
@@ -1,254 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.widget.ResizablePathDrawable;
-import org.mozilla.gecko.widget.ResizablePathDrawable.NonScaledPathShape;
-import org.mozilla.gecko.widget.themed.ThemedImageButton;
-import org.mozilla.gecko.widget.themed.ThemedLinearLayout;
-import org.mozilla.gecko.widget.themed.ThemedTextView;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.Region;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.Checkable;
-import android.widget.ImageView;
-
-public class TabStripItemView extends ThemedLinearLayout
- implements Checkable {
- private static final String LOGTAG = "GeckoTabStripItem";
-
- private static final int[] STATE_CHECKED = {
- android.R.attr.state_checked
- };
-
- private int id = -1;
- private boolean checked;
-
- private final ImageView faviconView;
- private final ThemedTextView titleView;
- private final ThemedImageButton closeView;
-
- private final ResizablePathDrawable backgroundDrawable;
- private final Region tabRegion;
- private final Region tabClipRegion;
- private boolean tabRegionNeedsUpdate;
-
- private final int faviconSize;
- private Bitmap lastFavicon;
-
- public TabStripItemView(Context context) {
- this(context, null);
- }
-
- public TabStripItemView(Context context, AttributeSet attrs) {
- super(context, attrs);
- setOrientation(HORIZONTAL);
-
- tabRegion = new Region();
- tabClipRegion = new Region();
-
- final Resources res = context.getResources();
-
- final ColorStateList tabColors =
- res.getColorStateList(R.color.tab_strip_item_bg);
- backgroundDrawable = new ResizablePathDrawable(new TabCurveShape(), tabColors);
- setBackgroundDrawable(backgroundDrawable);
-
- faviconSize = res.getDimensionPixelSize(R.dimen.browser_toolbar_favicon_size);
-
- LayoutInflater.from(context).inflate(R.layout.tab_strip_item_view, this);
- setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (id < 0) {
- throw new IllegalStateException("Invalid tab id:" + id);
- }
-
- Tabs.getInstance().selectTab(id);
- }
- });
-
- faviconView = (ImageView) findViewById(R.id.favicon);
- titleView = (ThemedTextView) findViewById(R.id.title);
-
- closeView = (ThemedImageButton) findViewById(R.id.close);
- closeView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (id < 0) {
- throw new IllegalStateException("Invalid tab id:" + id);
- }
-
- final Tabs tabs = Tabs.getInstance();
- tabs.closeTab(tabs.getTab(id), true);
- }
- });
- }
-
- @Override
- protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
- super.onSizeChanged(width, height, oldWidth, oldHeight);
-
- // Queue a tab region update in the next draw() call. We don't
- // update it immediately here because we need the new path from
- // the background drawable to be updated first.
- tabRegionNeedsUpdate = true;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- final int action = event.getActionMasked();
- final int x = (int) event.getX();
- final int y = (int) event.getY();
-
- // Let motion events through if they're off the tab shape bounds.
- if (action == MotionEvent.ACTION_DOWN && !tabRegion.contains(x, y)) {
- return false;
- }
-
- return super.onTouchEvent(event);
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
-
- if (tabRegionNeedsUpdate) {
- final Path path = backgroundDrawable.getPath();
- tabClipRegion.set(0, 0, getWidth(), getHeight());
- tabRegion.setPath(path, tabClipRegion);
- tabRegionNeedsUpdate = false;
- }
- }
-
- @Override
- public int[] onCreateDrawableState(int extraSpace) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
-
- if (checked) {
- mergeDrawableStates(drawableState, STATE_CHECKED);
- }
-
- return drawableState;
- }
-
- @Override
- public boolean isChecked() {
- return checked;
- }
-
- @Override
- public void setChecked(boolean checked) {
- if (this.checked == checked) {
- return;
- }
-
- this.checked = checked;
- refreshDrawableState();
- }
-
- @Override
- public void toggle() {
- setChecked(!checked);
- }
-
- @Override
- public void setPressed(boolean pressed) {
- super.setPressed(pressed);
-
- // The surrounding tab strip dividers need to be hidden
- // when a tab item enters pressed state.
- View parent = (View) getParent();
- if (parent != null) {
- parent.invalidate();
- }
- }
-
- void updateFromTab(Tab tab) {
- if (tab == null) {
- return;
- }
-
- id = tab.getId();
-
- updateTitle(tab);
- updateFavicon(tab.getFavicon());
- setPrivateMode(tab.isPrivate());
- }
-
- private void updateTitle(Tab tab) {
- final String title;
-
- // Avoid flickering the about:home URL on every load given how often
- // this page is used in the UI.
- if (AboutPages.isAboutHome(tab.getURL())) {
- titleView.setText(R.string.home_title);
- } else {
- titleView.setText(tab.getDisplayTitle());
- }
-
- // TODO: Set content description to indicate audio is playing.
- if (tab.isAudioPlaying()) {
- titleView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.tab_audio_playing, 0, 0, 0);
- } else {
- titleView.setCompoundDrawables(null, null, null, null);
- }
- }
-
- private void updateFavicon(final Bitmap favicon) {
- if (favicon == null) {
- lastFavicon = null;
- faviconView.setImageResource(R.drawable.toolbar_favicon_default);
- return;
- }
- if (favicon == lastFavicon) {
- return;
- }
-
- // Cache the original so we can debounce without scaling.
- lastFavicon = favicon;
-
- final Bitmap scaledFavicon =
- Bitmap.createScaledBitmap(favicon, faviconSize, faviconSize, false);
- faviconView.setImageBitmap(scaledFavicon);
- }
-
- private static class TabCurveShape extends NonScaledPathShape {
- @Override
- protected void onResize(float width, float height) {
- final Path path = getPath();
-
- path.reset();
-
- final float curveWidth = TabCurve.getWidthForHeight(height);
-
- path.moveTo(0, height);
- TabCurve.drawFromBottom(path, 0, height, TabCurve.Direction.RIGHT);
- path.lineTo(width - curveWidth, 0);
-
- TabCurve.drawFromTop(path, width - curveWidth, height, TabCurve.Direction.RIGHT);
- path.lineTo(0, height);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripView.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripView.java
deleted file mode 100644
index f3ec19cef..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripView.java
+++ /dev/null
@@ -1,449 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.LinearGradient;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.Shader;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.animation.DecelerateInterpolator;
-import android.view.View;
-import android.view.ViewTreeObserver.OnPreDrawListener;
-
-import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.widget.TwoWayView;
-
-public class TabStripView extends TwoWayView {
- private static final String LOGTAG = "GeckoTabStrip";
-
- private static final int ANIM_TIME_MS = 200;
- private static final DecelerateInterpolator ANIM_INTERPOLATOR =
- new DecelerateInterpolator();
-
- private final TabStripAdapter adapter;
- private final Drawable divider;
-
- private final TabAnimatorListener animatorListener;
-
- private boolean isRestoringTabs;
-
- // Filled by calls to ShapeDrawable.getPadding();
- // saved to prevent allocation in draw().
- private final Rect dividerPadding = new Rect();
-
- private boolean isPrivate;
-
- private final Paint fadingEdgePaint;
- private final int fadingEdgeSize;
-
- public TabStripView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- setOrientation(Orientation.HORIZONTAL);
- setChoiceMode(ChoiceMode.SINGLE);
- setItemsCanFocus(true);
- setChildrenDrawingOrderEnabled(true);
- setWillNotDraw(false);
-
- final Resources resources = getResources();
-
- divider = resources.getDrawable(R.drawable.tab_strip_divider);
- divider.getPadding(dividerPadding);
-
- final int itemMargin =
- resources.getDimensionPixelSize(R.dimen.tablet_tab_strip_item_margin);
- setItemMargin(itemMargin);
-
- animatorListener = new TabAnimatorListener();
-
- fadingEdgePaint = new Paint();
- fadingEdgeSize =
- resources.getDimensionPixelOffset(R.dimen.tablet_tab_strip_fading_edge_size);
-
- adapter = new TabStripAdapter(context);
- setAdapter(adapter);
- }
-
- private View getViewForTab(Tab tab) {
- final int position = adapter.getPositionForTab(tab);
- return getChildAt(position - getFirstVisiblePosition());
- }
-
- private int getPositionForSelectedTab() {
- return adapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
- }
-
- private void updateSelectedStyle(int selected) {
- setItemChecked(selected, true);
- }
-
- private void updateSelectedPosition(boolean ensureVisible) {
- final int selected = getPositionForSelectedTab();
- if (selected != -1) {
- updateSelectedStyle(selected);
-
- if (ensureVisible) {
- ensurePositionIsVisible(selected, true);
- }
- }
- }
-
- private void animateRemoveTab(Tab removedTab) {
- final int removedPosition = adapter.getPositionForTab(removedTab);
-
- final View removedView = getViewForTab(removedTab);
-
- // The removed position might not have a matching child view
- // when it's not within the visible range of positions in the strip.
- if (removedView == null) {
- return;
- }
-
- // We don't animate the removed child view (it just disappears)
- // but we still need its size of animate all affected children
- // within the visible viewport.
- final int removedSize = removedView.getWidth() + getItemMargin();
-
- getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- getViewTreeObserver().removeOnPreDrawListener(this);
-
- final int firstPosition = getFirstVisiblePosition();
- final List<Animator> childAnimators = new ArrayList<Animator>();
-
- final int childCount = getChildCount();
- for (int i = removedPosition - firstPosition; i < childCount; i++) {
- final View child = getChildAt(i);
-
- final ObjectAnimator animator =
- ObjectAnimator.ofFloat(child, "translationX", removedSize, 0);
- childAnimators.add(animator);
- }
-
- final AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.playTogether(childAnimators);
- animatorSet.setDuration(ANIM_TIME_MS);
- animatorSet.setInterpolator(ANIM_INTERPOLATOR);
- animatorSet.addListener(animatorListener);
-
- animatorSet.start();
-
- return true;
- }
- });
- }
-
- private void animateNewTab(Tab newTab) {
- final int newPosition = adapter.getPositionForTab(newTab);
- if (newPosition < 0) {
- return;
- }
-
- getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- getViewTreeObserver().removeOnPreDrawListener(this);
-
- final int firstPosition = getFirstVisiblePosition();
-
- final View newChild = getChildAt(newPosition - firstPosition);
- if (newChild == null) {
- return true;
- }
-
- final List<Animator> childAnimators = new ArrayList<Animator>();
- childAnimators.add(
- ObjectAnimator.ofFloat(newChild, "translationY", newChild.getHeight(), 0));
-
- // This will momentaneously add a gap on the right side
- // because TwoWayView doesn't provide APIs to control
- // view recycling programatically to handle these transitory
- // states in the container during animations.
-
- final int tabSize = newChild.getWidth();
- final int newIndex = newPosition - firstPosition;
- final int childCount = getChildCount();
- for (int i = newIndex + 1; i < childCount; i++) {
- final View child = getChildAt(i);
-
- childAnimators.add(
- ObjectAnimator.ofFloat(child, "translationX", -tabSize, 0));
- }
-
- final AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.playTogether(childAnimators);
- animatorSet.setDuration(ANIM_TIME_MS);
- animatorSet.setInterpolator(ANIM_INTERPOLATOR);
- animatorSet.addListener(animatorListener);
-
- animatorSet.start();
-
- return true;
- }
- });
- }
-
- private void animateRestoredTabs() {
- getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- getViewTreeObserver().removeOnPreDrawListener(this);
-
- final List<Animator> childAnimators = new ArrayList<Animator>();
-
- final int tabHeight = getHeight() - getPaddingTop() - getPaddingBottom();
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
-
- childAnimators.add(
- ObjectAnimator.ofFloat(child, "translationY", tabHeight, 0));
- }
-
- final AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.playTogether(childAnimators);
- animatorSet.setDuration(ANIM_TIME_MS);
- animatorSet.setInterpolator(ANIM_INTERPOLATOR);
- animatorSet.addListener(animatorListener);
-
- animatorSet.start();
-
- return true;
- }
- });
- }
-
- /**
- * Ensures the tab at the given position is visible. If we are not restoring tabs and
- * shouldAnimate == true, the tab will animate to be visible, if it is not already visible.
- */
- private void ensurePositionIsVisible(final int position, final boolean shouldAnimate) {
- // We just want to move the strip to the right position
- // when restoring tabs on startup.
- if (isRestoringTabs || !shouldAnimate) {
- setSelection(position);
- return;
- }
-
- getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- getViewTreeObserver().removeOnPreDrawListener(this);
- smoothScrollToPosition(position);
- return true;
- }
- });
- }
-
- private int getCheckedIndex(int childCount) {
- final int checkedIndex = getCheckedItemPosition() - getFirstVisiblePosition();
- if (checkedIndex < 0 || checkedIndex > childCount - 1) {
- return INVALID_POSITION;
- }
-
- return checkedIndex;
- }
-
- void refreshTabs() {
- // Store a different copy of the tabs, so that we don't have
- // to worry about accidentally updating it on the wrong thread.
- final List<Tab> tabs = new ArrayList<Tab>();
-
- for (Tab tab : Tabs.getInstance().getTabsInOrder()) {
- if (tab.isPrivate() == isPrivate) {
- tabs.add(tab);
- }
- }
-
- adapter.refresh(tabs);
- updateSelectedPosition(true);
- }
-
- void clearTabs() {
- adapter.clear();
- }
-
- void restoreTabs() {
- isRestoringTabs = true;
- refreshTabs();
- animateRestoredTabs();
- isRestoringTabs = false;
- }
-
- void addTab(Tab tab) {
- // Refresh the list to make sure the new tab is
- // added in the right position.
- refreshTabs();
- animateNewTab(tab);
- }
-
- void removeTab(Tab tab) {
- animateRemoveTab(tab);
- adapter.removeTab(tab);
- updateSelectedPosition(false);
- }
-
- void selectTab(Tab tab) {
- if (tab.isPrivate() != isPrivate) {
- isPrivate = tab.isPrivate();
- refreshTabs();
- } else {
- updateSelectedPosition(true);
- }
- }
-
- void updateTab(Tab tab) {
- final TabStripItemView item = (TabStripItemView) getViewForTab(tab);
- if (item != null) {
- item.updateFromTab(tab);
- }
- }
-
- private float getFadingEdgeStrength() {
- final int childCount = getChildCount();
- if (childCount == 0) {
- return 0.0f;
- } else {
- if (getFirstVisiblePosition() + childCount - 1 < adapter.getCount() - 1) {
- return 1.0f;
- }
-
- final int right = getChildAt(childCount - 1).getRight();
- final int paddingRight = getPaddingRight();
- final int width = getWidth();
-
- final float strength = (right > width - paddingRight ?
- (float) (right - width + paddingRight) / fadingEdgeSize : 0.0f);
-
- return Math.max(0.0f, Math.min(strength, 1.0f));
- }
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
-
- fadingEdgePaint.setShader(new LinearGradient(w - fadingEdgeSize, 0, w, 0,
- new int[] { 0x0, 0x11292C29, 0xDD292C29 },
- new float[] { 0, 0.4f, 1.0f }, Shader.TileMode.CLAMP));
- }
-
- @Override
- protected int getChildDrawingOrder(int childCount, int i) {
- final int checkedIndex = getCheckedIndex(childCount);
- if (checkedIndex == INVALID_POSITION) {
- return i;
- }
-
- // Always draw the currently selected tab on top of all
- // other child views so that its curve is fully visible.
- if (i == childCount - 1) {
- return checkedIndex;
- } else if (checkedIndex <= i) {
- return i + 1;
- } else {
- return i;
- }
- }
-
- private void drawDividers(Canvas canvas) {
- final int bottom = getHeight() - getPaddingBottom() - dividerPadding.bottom;
- final int top = bottom - divider.getIntrinsicHeight();
-
- final int dividerWidth = divider.getIntrinsicWidth();
- final int itemMargin = getItemMargin();
-
- final int childCount = getChildCount();
- final int checkedIndex = getCheckedIndex(childCount);
-
- for (int i = 1; i < childCount; i++) {
- final View child = getChildAt(i);
-
- final boolean pressed = (child.isPressed() || getChildAt(i - 1).isPressed());
- final boolean checked = (i == checkedIndex || i == checkedIndex + 1);
-
- // Don't draw dividers for around checked or pressed items
- // so that they are not drawn on top of the tab curves.
- if (pressed || checked) {
- continue;
- }
-
- final int left = child.getLeft() - (itemMargin / 2) - dividerWidth;
- final int right = left + dividerWidth;
-
- divider.setBounds(left, top, right, bottom);
- divider.draw(canvas);
- }
- }
-
- private void drawFadingEdge(Canvas canvas) {
- final float strength = getFadingEdgeStrength();
- if (strength > 0.0f) {
- final int r = getRight();
- canvas.drawRect(r - fadingEdgeSize, getTop(), r, getBottom(), fadingEdgePaint);
- fadingEdgePaint.setAlpha((int) (strength * 255));
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
- drawDividers(canvas);
- drawFadingEdge(canvas);
- }
-
- public void refresh() {
- final int selectedPosition = getPositionForSelectedTab();
- if (selectedPosition != -1) {
- ensurePositionIsVisible(selectedPosition, false);
- }
- }
-
- private class TabAnimatorListener implements AnimatorListener {
- private void setLayerType(int layerType) {
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- getChildAt(i).setLayerType(layerType, null);
- }
- }
-
- @Override
- public void onAnimationStart(Animator animation) {
- setLayerType(View.LAYER_TYPE_HARDWARE);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- // This method is called even if the animator is canceled.
- setLayerType(View.LAYER_TYPE_NONE);
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
-
- }
-} \ No newline at end of file
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsGridLayout.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsGridLayout.java
deleted file mode 100644
index ead7db9fe..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsGridLayout.java
+++ /dev/null
@@ -1,712 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.tabs.TabsPanel.TabsLayout;
-import org.mozilla.gecko.widget.themed.ThemedRelativeLayout;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.os.Build;
-import android.util.AttributeSet;
-import android.util.SparseArray;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.AbsListView;
-import android.widget.AdapterView;
-import android.widget.Button;
-import android.widget.GridView;
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.animation.ValueAnimator;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A tabs layout implementation for the tablet redesign (bug 1014156) and later ported to mobile (bug 1193745).
- */
-
-class TabsGridLayout extends GridView
- implements TabsLayout,
- Tabs.OnTabsChangedListener {
-
- private static final String LOGTAG = "Gecko" + TabsGridLayout.class.getSimpleName();
-
- public static final int ANIM_DELAY_MULTIPLE_MS = 20;
- private static final int ANIM_TIME_MS = 200;
- private static final DecelerateInterpolator ANIM_INTERPOLATOR = new DecelerateInterpolator();
-
- private final SparseArray<PointF> tabLocations = new SparseArray<PointF>();
- private final boolean isPrivate;
- private final TabsLayoutAdapter tabsAdapter;
- private final int columnWidth;
- private TabsPanel tabsPanel;
- private int lastSelectedTabId;
-
- public TabsGridLayout(final Context context, final AttributeSet attrs) {
- super(context, attrs, R.attr.tabGridLayoutViewStyle);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsLayout);
- isPrivate = (a.getInt(R.styleable.TabsLayout_tabs, 0x0) == 1);
- a.recycle();
-
- tabsAdapter = new TabsGridLayoutAdapter(context);
- setAdapter(tabsAdapter);
-
- setRecyclerListener(new RecyclerListener() {
- @Override
- public void onMovedToScrapHeap(View view) {
- TabsLayoutItemView item = (TabsLayoutItemView) view;
- item.setThumbnail(null);
- }
- });
-
- // The clipToPadding setting in the styles.xml doesn't seem to be working (bug 1101784)
- // so lets set it manually in code for the moment as it's needed for the padding animation
- setClipToPadding(false);
-
- setVerticalFadingEdgeEnabled(false);
-
- final Resources resources = getResources();
- columnWidth = resources.getDimensionPixelSize(R.dimen.tab_panel_column_width);
-
- final int padding = resources.getDimensionPixelSize(R.dimen.tab_panel_grid_padding);
- final int paddingTop = resources.getDimensionPixelSize(R.dimen.tab_panel_grid_padding_top);
-
- // Lets set double the top padding on the bottom so that the last row shows up properly!
- // Your demise, GridView, cannot come fast enough.
- final int paddingBottom = paddingTop * 2;
-
- setPadding(padding, paddingTop, padding, paddingBottom);
-
- setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- final TabsLayoutItemView tabView = (TabsLayoutItemView) view;
- final int tabId = tabView.getTabId();
- final Tab tab = Tabs.getInstance().selectTab(tabId);
- if (tab == null) {
- return;
- }
- autoHidePanel();
- Tabs.getInstance().notifyListeners(tab, Tabs.TabEvents.OPENED_FROM_TABS_TRAY);
- }
- });
-
- TabSwipeGestureListener mSwipeListener = new TabSwipeGestureListener();
- setOnTouchListener(mSwipeListener);
- setOnScrollListener(mSwipeListener.makeScrollListener());
- }
-
- private void populateTabLocations(final Tab removedTab) {
- tabLocations.clear();
-
- final int firstPosition = getFirstVisiblePosition();
- final int lastPosition = getLastVisiblePosition();
- final int numberOfColumns = getNumColumns();
- final int childCount = getChildCount();
- final int removedPosition = tabsAdapter.getPositionForTab(removedTab);
-
- for (int x = 1, i = (removedPosition - firstPosition) + 1; i < childCount; i++, x++) {
- final View child = getChildAt(i);
- if (child != null) {
- // Reset the transformations here in case the user is swiping tabs away fast and they swipe a tab
- // before the last animation has finished (bug 1179195).
- resetTransforms(child);
-
- tabLocations.append(x, new PointF(child.getX(), child.getY()));
- }
- }
-
- final boolean firstChildOffScreen = ((firstPosition > 0) || getChildAt(0).getY() < 0);
- final boolean lastChildVisible = (lastPosition - childCount == firstPosition - 1);
- final boolean oneItemOnLastRow = (lastPosition % numberOfColumns == 0);
- if (firstChildOffScreen && lastChildVisible && oneItemOnLastRow) {
- // We need to set the view's bottom padding to prevent a sudden jump as the
- // last item in the row is being removed. We then need to remove the padding
- // via a sweet animation
-
- final int removedHeight = getChildAt(0).getMeasuredHeight();
- final int verticalSpacing =
- getResources().getDimensionPixelOffset(R.dimen.tab_panel_grid_vspacing);
-
- ValueAnimator paddingAnimator = ValueAnimator.ofInt(getPaddingBottom() + removedHeight + verticalSpacing, getPaddingBottom());
- paddingAnimator.setDuration(ANIM_TIME_MS * 2);
-
- paddingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (Integer) animation.getAnimatedValue());
- }
- });
- paddingAnimator.start();
- }
- }
-
- @Override
- public void setTabsPanel(TabsPanel panel) {
- tabsPanel = panel;
- }
-
- @Override
- public void show() {
- setVisibility(View.VISIBLE);
- Tabs.getInstance().refreshThumbnails();
- Tabs.registerOnTabsChangedListener(this);
- refreshTabsData();
-
- final Tab currentlySelectedTab = Tabs.getInstance().getSelectedTab();
- final int position = currentlySelectedTab != null ? tabsAdapter.getPositionForTab(currentlySelectedTab) : -1;
- if (position != -1) {
- final boolean selectionChanged = lastSelectedTabId != currentlySelectedTab.getId();
- final boolean positionIsVisible = position >= getFirstVisiblePosition() && position <= getLastVisiblePosition();
-
- if (selectionChanged || !positionIsVisible) {
- smoothScrollToPosition(position);
- }
- }
- }
-
- @Override
- public void hide() {
- lastSelectedTabId = Tabs.getInstance().getSelectedTab().getId();
- setVisibility(View.GONE);
- Tabs.unregisterOnTabsChangedListener(this);
- GeckoAppShell.notifyObservers("Tab:Screenshot:Cancel", "");
- tabsAdapter.clear();
- }
-
- @Override
- public boolean shouldExpand() {
- return true;
- }
-
- private void autoHidePanel() {
- tabsPanel.autoHidePanel();
- }
-
- @Override
- public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
- switch (msg) {
- case ADDED:
- // Refresh only if panel is shown. show() will call refreshTabsData() later again.
- if (tabsPanel.isShown()) {
- // Refresh the list to make sure the new tab is added in the right position.
- refreshTabsData();
- }
- break;
-
- case CLOSED:
-
- // This is limited to >= ICS as animations on GB devices are generally pants
- if (Build.VERSION.SDK_INT >= 11 && tabsAdapter.getCount() > 0) {
- animateRemoveTab(tab);
- }
-
- final Tabs tabsInstance = Tabs.getInstance();
-
- if (tabsAdapter.removeTab(tab)) {
- if (tab.isPrivate() == isPrivate && tabsAdapter.getCount() > 0) {
- int selected = tabsAdapter.getPositionForTab(tabsInstance.getSelectedTab());
- updateSelectedStyle(selected);
- }
- if (!tab.isPrivate()) {
- // Make sure we always have at least one normal tab
- final Iterable<Tab> tabs = tabsInstance.getTabsInOrder();
- boolean removedTabIsLastNormalTab = true;
- for (Tab singleTab : tabs) {
- if (!singleTab.isPrivate()) {
- removedTabIsLastNormalTab = false;
- break;
- }
- }
- if (removedTabIsLastNormalTab) {
- tabsInstance.addTab();
- }
- }
- }
- break;
-
- case SELECTED:
- // Update the selected position, then fall through...
- updateSelectedPosition();
- case UNSELECTED:
- // We just need to update the style for the unselected tab...
- case THUMBNAIL:
- case TITLE:
- case RECORDING_CHANGE:
- case AUDIO_PLAYING_CHANGE:
- View view = getChildAt(tabsAdapter.getPositionForTab(tab) - getFirstVisiblePosition());
- if (view == null)
- return;
-
- ((TabsLayoutItemView) view).assignValues(tab);
- break;
- }
- }
-
- // Updates the selected position in the list so that it will be scrolled to the right place.
- private void updateSelectedPosition() {
- int selected = tabsAdapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
- updateSelectedStyle(selected);
-
- if (selected != -1) {
- setSelection(selected);
- }
- }
-
- /**
- * Updates the selected/unselected style for the tabs.
- *
- * @param selected position of the selected tab
- */
- private void updateSelectedStyle(final int selected) {
- post(new Runnable() {
- @Override
- public void run() {
- final int displayCount = tabsAdapter.getCount();
-
- for (int i = 0; i < displayCount; i++) {
- final Tab tab = tabsAdapter.getItem(i);
- final boolean checked = displayCount == 1 || i == selected;
- final View tabView = getViewForTab(tab);
- if (tabView != null) {
- ((TabsLayoutItemView) tabView).setChecked(checked);
- }
- // setItemChecked doesn't exist until API 11, despite what the API docs say!
- setItemChecked(i, checked);
- }
- }
- });
- }
-
- private void refreshTabsData() {
- // Store a different copy of the tabs, so that we don't have to worry about
- // accidentally updating it on the wrong thread.
- ArrayList<Tab> tabData = new ArrayList<>();
-
- Iterable<Tab> allTabs = Tabs.getInstance().getTabsInOrder();
- for (Tab tab : allTabs) {
- if (tab.isPrivate() == isPrivate)
- tabData.add(tab);
- }
-
- tabsAdapter.setTabs(tabData);
- updateSelectedPosition();
- }
-
- private void resetTransforms(View view) {
- view.setAlpha(1);
- view.setTranslationX(0);
- view.setTranslationY(0);
-
- ((TabsLayoutItemView) view).setCloseVisible(true);
- }
-
- @Override
- public void closeAll() {
-
- autoHidePanel();
-
- if (getChildCount() == 0) {
- return;
- }
-
- final Iterable<Tab> tabs = Tabs.getInstance().getTabsInOrder();
- for (Tab tab : tabs) {
- // In the normal panel we want to close all tabs (both private and normal),
- // but in the private panel we only want to close private tabs.
- if (!isPrivate || tab.isPrivate()) {
- Tabs.getInstance().closeTab(tab, false);
- }
- }
- }
-
- private View getViewForTab(Tab tab) {
- final int position = tabsAdapter.getPositionForTab(tab);
- return getChildAt(position - getFirstVisiblePosition());
- }
-
- void closeTab(View v) {
- if (tabsAdapter.getCount() == 1) {
- autoHidePanel();
- }
-
- TabsLayoutItemView itemView = (TabsLayoutItemView) v.getTag();
- Tab tab = Tabs.getInstance().getTab(itemView.getTabId());
-
- Tabs.getInstance().closeTab(tab, true);
- }
-
- private void animateRemoveTab(final Tab removedTab) {
- final int removedPosition = tabsAdapter.getPositionForTab(removedTab);
-
- final View removedView = getViewForTab(removedTab);
-
- // The removed position might not have a matching child view
- // when it's not within the visible range of positions in the strip.
- if (removedView == null) {
- return;
- }
- final int removedHeight = removedView.getMeasuredHeight();
-
- populateTabLocations(removedTab);
-
- getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- getViewTreeObserver().removeOnPreDrawListener(this);
- // We don't animate the removed child view (it just disappears)
- // but we still need its size to animate all affected children
- // within the visible viewport.
- final int childCount = getChildCount();
- final int firstPosition = getFirstVisiblePosition();
- final int numberOfColumns = getNumColumns();
-
- final List<Animator> childAnimators = new ArrayList<>();
-
- PropertyValuesHolder translateX, translateY;
- for (int x = 0, i = removedPosition - firstPosition; i < childCount; i++, x++) {
- final View child = getChildAt(i);
- ObjectAnimator animator;
-
- if (i % numberOfColumns == numberOfColumns - 1) {
- // Animate X & Y
- translateX = PropertyValuesHolder.ofFloat("translationX", -(columnWidth * numberOfColumns), 0);
- translateY = PropertyValuesHolder.ofFloat("translationY", removedHeight, 0);
- animator = ObjectAnimator.ofPropertyValuesHolder(child, translateX, translateY);
- } else {
- // Just animate X
- translateX = PropertyValuesHolder.ofFloat("translationX", columnWidth, 0);
- animator = ObjectAnimator.ofPropertyValuesHolder(child, translateX);
- }
- animator.setStartDelay(x * ANIM_DELAY_MULTIPLE_MS);
- childAnimators.add(animator);
- }
-
- final AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.playTogether(childAnimators);
- animatorSet.setDuration(ANIM_TIME_MS);
- animatorSet.setInterpolator(ANIM_INTERPOLATOR);
- animatorSet.start();
-
- // Set the starting position of the child views - because we are delaying the start
- // of the animation, we need to prevent the items being drawn in their final position
- // prior to the animation starting
- for (int x = 1, i = (removedPosition - firstPosition) + 1; i < childCount; i++, x++) {
- final View child = getChildAt(i);
-
- final PointF targetLocation = tabLocations.get(x + 1);
- if (targetLocation == null) {
- continue;
- }
-
- child.setX(targetLocation.x);
- child.setY(targetLocation.y);
- }
-
- return true;
- }
- });
- }
-
-
- private void animateCancel(final View view) {
- PropertyAnimator animator = new PropertyAnimator(ANIM_TIME_MS);
- animator.attach(view, PropertyAnimator.Property.ALPHA, 1);
- animator.attach(view, PropertyAnimator.Property.TRANSLATION_X, 0);
-
- animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
- @Override
- public void onPropertyAnimationStart() {
- }
-
- @Override
- public void onPropertyAnimationEnd() {
- TabsLayoutItemView tab = (TabsLayoutItemView) view;
- tab.setCloseVisible(true);
- }
- });
-
- animator.start();
- }
-
- private class TabsGridLayoutAdapter extends TabsLayoutAdapter {
-
- final private Button.OnClickListener mCloseClickListener;
-
- public TabsGridLayoutAdapter(Context context) {
- super(context, R.layout.tabs_layout_item_view);
-
- mCloseClickListener = new Button.OnClickListener() {
- @Override
- public void onClick(View v) {
- closeTab(v);
- }
- };
- }
-
- @Override
- TabsLayoutItemView newView(int position, ViewGroup parent) {
- final TabsLayoutItemView item = super.newView(position, parent);
-
- item.setCloseOnClickListener(mCloseClickListener);
- ((ThemedRelativeLayout) item.findViewById(R.id.wrapper)).setPrivateMode(isPrivate);
-
- return item;
- }
-
- @Override
- public void bindView(TabsLayoutItemView view, Tab tab) {
- super.bindView(view, tab);
-
- // If we're recycling this view, there's a chance it was transformed during
- // the close animation. Remove any of those properties.
- resetTransforms(view);
- }
- }
-
- private class TabSwipeGestureListener implements View.OnTouchListener {
- // same value the stock browser uses for after drag animation velocity in pixels/sec
- // http://androidxref.com/4.0.4/xref/packages/apps/Browser/src/com/android/browser/NavTabScroller.java#61
- private static final float MIN_VELOCITY = 750;
-
- private final int mSwipeThreshold;
- private final int mMinFlingVelocity;
-
- private final int mMaxFlingVelocity;
- private VelocityTracker mVelocityTracker;
-
- private int mTabWidth = 1;
-
- private View mSwipeView;
- private Runnable mPendingCheckForTap;
-
- private float mSwipeStartX;
- private boolean mSwiping;
- private boolean mEnabled;
-
- public TabSwipeGestureListener() {
- mEnabled = true;
-
- ViewConfiguration vc = ViewConfiguration.get(TabsGridLayout.this.getContext());
- mSwipeThreshold = vc.getScaledTouchSlop();
- mMinFlingVelocity = (int) (TabsGridLayout.this.getContext().getResources().getDisplayMetrics().density * MIN_VELOCITY);
- mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
- }
-
- public void setEnabled(boolean enabled) {
- mEnabled = enabled;
- }
-
- public OnScrollListener makeScrollListener() {
- return new OnScrollListener() {
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- setEnabled(scrollState != GridView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
- }
-
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
-
- }
- };
- }
-
- @Override
- public boolean onTouch(View view, MotionEvent e) {
- if (!mEnabled) {
- return false;
- }
-
- switch (e.getActionMasked()) {
- case MotionEvent.ACTION_DOWN: {
- // Check if we should set pressed state on the
- // touched view after a standard delay.
- triggerCheckForTap();
-
- final float x = e.getRawX();
- final float y = e.getRawY();
-
- // Find out which view is being touched
- mSwipeView = findViewAt(x, y);
-
- if (mSwipeView != null) {
- if (mTabWidth < 2) {
- mTabWidth = mSwipeView.getWidth();
- }
-
- mSwipeStartX = e.getRawX();
-
- mVelocityTracker = VelocityTracker.obtain();
- mVelocityTracker.addMovement(e);
- }
-
- view.onTouchEvent(e);
- return true;
- }
-
- case MotionEvent.ACTION_UP: {
- if (mSwipeView == null) {
- break;
- }
-
- cancelCheckForTap();
- mSwipeView.setPressed(false);
-
- if (!mSwiping) {
- final TabsLayoutItemView item = (TabsLayoutItemView) mSwipeView;
- final int tabId = item.getTabId();
- final Tab tab = Tabs.getInstance().selectTab(tabId);
- if (tab != null) {
- autoHidePanel();
- Tabs.getInstance().notifyListeners(tab, Tabs.TabEvents.OPENED_FROM_TABS_TRAY);
- }
-
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- break;
- }
-
- mVelocityTracker.addMovement(e);
- mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
-
- float velocityX = Math.abs(mVelocityTracker.getXVelocity());
-
- boolean dismiss = false;
-
- float deltaX = mSwipeView.getTranslationX();
-
- if (Math.abs(deltaX) > mTabWidth / 2) {
- dismiss = true;
- } else if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity) {
- dismiss = mSwiping && (deltaX * mVelocityTracker.getYVelocity() > 0);
- }
- if (dismiss) {
- closeTab(mSwipeView.findViewById(R.id.close));
- } else {
- animateCancel(mSwipeView);
- }
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- mSwipeView = null;
-
- mSwipeStartX = 0;
- mSwiping = false;
- }
-
- case MotionEvent.ACTION_MOVE: {
- if (mSwipeView == null || mVelocityTracker == null) {
- break;
- }
-
- mVelocityTracker.addMovement(e);
-
- float delta = e.getRawX() - mSwipeStartX;
-
- boolean isScrollingX = Math.abs(delta) > mSwipeThreshold;
- boolean isSwipingToClose = isScrollingX;
-
- // If we're actually swiping, make sure we don't
- // set pressed state on the swiped view.
- if (isScrollingX) {
- cancelCheckForTap();
- }
-
- if (isSwipingToClose) {
- mSwiping = true;
- TabsGridLayout.this.requestDisallowInterceptTouchEvent(true);
-
- ((TabsLayoutItemView) mSwipeView).setCloseVisible(false);
-
- // Stops listview from highlighting the touched item
- // in the list when swiping.
- MotionEvent cancelEvent = MotionEvent.obtain(e);
- cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
- (e.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
- TabsGridLayout.this.onTouchEvent(cancelEvent);
- cancelEvent.recycle();
- }
-
- if (mSwiping) {
- mSwipeView.setTranslationX(delta);
-
- mSwipeView.setAlpha(Math.min(1f, 1f - 2f * Math.abs(delta) / mTabWidth));
-
- return true;
- }
-
- break;
- }
- }
- return false;
- }
-
- private View findViewAt(float rawX, float rawY) {
- Rect rect = new Rect();
-
- int[] listViewCoords = new int[2];
- TabsGridLayout.this.getLocationOnScreen(listViewCoords);
-
- int x = (int) rawX - listViewCoords[0];
- int y = (int) rawY - listViewCoords[1];
-
- for (int i = 0; i < TabsGridLayout.this.getChildCount(); i++) {
- View child = TabsGridLayout.this.getChildAt(i);
- child.getHitRect(rect);
-
- if (rect.contains(x, y)) {
- return child;
- }
- }
-
- return null;
- }
-
- private void triggerCheckForTap() {
- if (mPendingCheckForTap == null) {
- mPendingCheckForTap = new CheckForTap();
- }
-
- TabsGridLayout.this.postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
- }
-
- private void cancelCheckForTap() {
- if (mPendingCheckForTap == null) {
- return;
- }
-
- TabsGridLayout.this.removeCallbacks(mPendingCheckForTap);
- }
-
- private class CheckForTap implements Runnable {
- @Override
- public void run() {
- if (!mSwiping && mSwipeView != null && mEnabled) {
- mSwipeView.setPressed(true);
- }
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayout.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayout.java
deleted file mode 100644
index d5362f1f1..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayout.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.widget.RecyclerViewClickSupport;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.Button;
-
-import java.util.ArrayList;
-
-public abstract class TabsLayout extends RecyclerView
- implements TabsPanel.TabsLayout,
- Tabs.OnTabsChangedListener,
- RecyclerViewClickSupport.OnItemClickListener,
- TabsTouchHelperCallback.DismissListener {
-
- private static final String LOGTAG = "Gecko" + TabsLayout.class.getSimpleName();
-
- private final boolean isPrivate;
- private TabsPanel tabsPanel;
- private final TabsLayoutRecyclerAdapter tabsAdapter;
-
- public TabsLayout(Context context, AttributeSet attrs, int itemViewLayoutResId) {
- super(context, attrs);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsLayout);
- isPrivate = (a.getInt(R.styleable.TabsLayout_tabs, 0x0) == 1);
- a.recycle();
-
- tabsAdapter = new TabsLayoutRecyclerAdapter(context, itemViewLayoutResId, isPrivate,
- /* close on click listener */
- new Button.OnClickListener() {
- @Override
- public void onClick(View v) {
- // The view here is the close button, which has a reference
- // to the parent TabsLayoutItemView in its tag, hence the getTag() call.
- TabsLayoutItemView itemView = (TabsLayoutItemView) v.getTag();
- closeTab(itemView);
- }
- });
- setAdapter(tabsAdapter);
-
- RecyclerViewClickSupport.addTo(this).setOnItemClickListener(this);
-
- setRecyclerListener(new RecyclerListener() {
- @Override
- public void onViewRecycled(RecyclerView.ViewHolder holder) {
- final TabsLayoutItemView itemView = (TabsLayoutItemView) holder.itemView;
- itemView.setThumbnail(null);
- itemView.setCloseVisible(true);
- }
- });
- }
-
- @Override
- public void setTabsPanel(TabsPanel panel) {
- tabsPanel = panel;
- }
-
- @Override
- public void show() {
- setVisibility(View.VISIBLE);
- Tabs.getInstance().refreshThumbnails();
- Tabs.registerOnTabsChangedListener(this);
- refreshTabsData();
- }
-
- @Override
- public void hide() {
- setVisibility(View.GONE);
- Tabs.unregisterOnTabsChangedListener(this);
- GeckoAppShell.notifyObservers("Tab:Screenshot:Cancel", "");
- tabsAdapter.clear();
- }
-
- @Override
- public boolean shouldExpand() {
- return true;
- }
-
- protected void autoHidePanel() {
- tabsPanel.autoHidePanel();
- }
-
- @Override
- public void onTabChanged(Tab tab, Tabs.TabEvents msg, String data) {
- switch (msg) {
- case ADDED:
- final int tabIndex = Integer.parseInt(data);
- tabsAdapter.notifyTabInserted(tab, tabIndex);
- if (addAtIndexRequiresScroll(tabIndex)) {
- // (The current Tabs implementation updates the SELECTED tab *after* this
- // call to ADDED, so don't just call updateSelectedPosition().)
- scrollToPosition(tabIndex);
- }
- break;
-
- case CLOSED:
- if (tab.isPrivate() == isPrivate && tabsAdapter.getItemCount() > 0) {
- tabsAdapter.removeTab(tab);
- }
- break;
-
- case SELECTED:
- case UNSELECTED:
- case THUMBNAIL:
- case TITLE:
- case RECORDING_CHANGE:
- case AUDIO_PLAYING_CHANGE:
- tabsAdapter.notifyTabChanged(tab);
- break;
- }
- }
-
- // Addition of a tab at selected positions (dependent on LayoutManager) will result in a tab
- // being added out of view - return true if index is such a position.
- abstract protected boolean addAtIndexRequiresScroll(int index);
-
- @Override
- public void onItemClicked(RecyclerView recyclerView, int position, View v) {
- final TabsLayoutItemView item = (TabsLayoutItemView) v;
- final int tabId = item.getTabId();
- final Tab tab = Tabs.getInstance().selectTab(tabId);
- if (tab == null) {
- // The tab that was clicked no longer exists in the tabs list (which can happen if you
- // tap on a tab while its remove animation is running), so ignore the click.
- return;
- }
-
- autoHidePanel();
- Tabs.getInstance().notifyListeners(tab, Tabs.TabEvents.OPENED_FROM_TABS_TRAY);
- }
-
- // Updates the selected position in the list so that it will be scrolled to the right place.
- private void updateSelectedPosition() {
- final int selected = tabsAdapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
- if (selected != NO_POSITION) {
- scrollToPosition(selected);
- }
- }
-
- private void refreshTabsData() {
- // Store a different copy of the tabs, so that we don't have to worry about
- // accidentally updating it on the wrong thread.
- final ArrayList<Tab> tabData = new ArrayList<>();
- final Iterable<Tab> allTabs = Tabs.getInstance().getTabsInOrder();
-
- for (final Tab tab : allTabs) {
- if (tab.isPrivate() == isPrivate) {
- tabData.add(tab);
- }
- }
-
- tabsAdapter.setTabs(tabData);
- updateSelectedPosition();
- }
-
- private void closeTab(View view) {
- final TabsLayoutItemView itemView = (TabsLayoutItemView) view;
- final Tab tab = getTabForView(itemView);
- if (tab == null) {
- // We can be null here if this is the second closeTab call resulting from a sufficiently
- // fast double tap on the close tab button.
- return;
- }
-
- final boolean closingLastTab = tabsAdapter.getItemCount() == 1;
- Tabs.getInstance().closeTab(tab, true);
- if (closingLastTab) {
- autoHidePanel();
- }
- }
-
- protected void closeAllTabs() {
- final Iterable<Tab> tabs = Tabs.getInstance().getTabsInOrder();
- for (final Tab tab : tabs) {
- // In the normal panel we want to close all tabs (both private and normal),
- // but in the private panel we only want to close private tabs.
- if (!isPrivate || tab.isPrivate()) {
- Tabs.getInstance().closeTab(tab, false);
- }
- }
- }
-
- @Override
- public void onItemDismiss(View view) {
- closeTab(view);
- }
-
- private Tab getTabForView(View view) {
- if (view == null) {
- return null;
- }
- return Tabs.getInstance().getTab(((TabsLayoutItemView) view).getTabId());
- }
-
- @Override
- public void setEmptyView(View emptyView) {
- // We never display an empty view.
- }
-
- @Override
- abstract public void closeAll();
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutAdapter.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutAdapter.java
deleted file mode 100644
index 367da640f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutAdapter.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-
-import java.util.ArrayList;
-
-// Adapter to bind tabs into a list
-public class TabsLayoutAdapter extends BaseAdapter {
- public static final String LOGTAG = "Gecko" + TabsLayoutAdapter.class.getSimpleName();
-
- private final Context mContext;
- private final int mTabLayoutId;
- private ArrayList<Tab> mTabs;
- private final LayoutInflater mInflater;
-
- public TabsLayoutAdapter (Context context, int tabLayoutId) {
- mContext = context;
- mInflater = LayoutInflater.from(mContext);
- mTabLayoutId = tabLayoutId;
- }
-
- final void setTabs (ArrayList<Tab> tabs) {
- mTabs = tabs;
- notifyDataSetChanged(); // Be sure to call this whenever mTabs changes.
- }
-
- final boolean removeTab (Tab tab) {
- boolean tabRemoved = mTabs.remove(tab);
- if (tabRemoved) {
- notifyDataSetChanged(); // Be sure to call this whenever mTabs changes.
- }
- return tabRemoved;
- }
-
- final void clear() {
- mTabs = null;
-
- notifyDataSetChanged(); // Be sure to call this whenever mTabs changes.
- }
-
- @Override
- public int getCount() {
- return (mTabs == null ? 0 : mTabs.size());
- }
-
- @Override
- public Tab getItem(int position) {
- return mTabs.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- final int getPositionForTab(Tab tab) {
- if (mTabs == null || tab == null)
- return -1;
-
- return mTabs.indexOf(tab);
- }
-
- @Override
- public boolean isEnabled(int position) {
- return true;
- }
-
- @Override
- final public TabsLayoutItemView getView(int position, View convertView, ViewGroup parent) {
- final TabsLayoutItemView view;
- if (convertView == null) {
- view = newView(position, parent);
- } else {
- view = (TabsLayoutItemView) convertView;
- }
- final Tab tab = mTabs.get(position);
- bindView(view, tab);
- return view;
- }
-
- TabsLayoutItemView newView(int position, ViewGroup parent) {
- return (TabsLayoutItemView) mInflater.inflate(mTabLayoutId, parent, false);
- }
-
- void bindView(TabsLayoutItemView view, Tab tab) {
- view.assignValues(tab);
- }
-} \ No newline at end of file
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutItemView.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutItemView.java
deleted file mode 100644
index 975e779d6..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutItemView.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.widget.TabThumbnailWrapper;
-import org.mozilla.gecko.widget.TouchDelegateWithReset;
-import org.mozilla.gecko.widget.themed.ThemedRelativeLayout;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.View;
-import android.view.ViewTreeObserver;
-import android.widget.Checkable;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-public class TabsLayoutItemView extends LinearLayout
- implements Checkable {
- private static final String LOGTAG = "Gecko" + TabsLayoutItemView.class.getSimpleName();
- private static final int[] STATE_CHECKED = { android.R.attr.state_checked };
- private boolean mChecked;
-
- private int mTabId;
- private TextView mTitle;
- private TabsPanelThumbnailView mThumbnail;
- private ImageView mCloseButton;
- private TabThumbnailWrapper mThumbnailWrapper;
-
- public TabsLayoutItemView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public int[] onCreateDrawableState(int extraSpace) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
-
- if (mChecked) {
- mergeDrawableStates(drawableState, STATE_CHECKED);
- }
-
- return drawableState;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
-
- @Override
- public boolean isChecked() {
- return mChecked;
- }
-
- @Override
- public void setChecked(boolean checked) {
- if (mChecked == checked) {
- return;
- }
-
- mChecked = checked;
- refreshDrawableState();
-
- int count = getChildCount();
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- if (child instanceof Checkable) {
- ((Checkable) child).setChecked(checked);
- }
- }
- }
-
- @Override
- public void toggle() {
- mChecked = !mChecked;
- }
-
- public void setCloseOnClickListener(OnClickListener mOnClickListener) {
- mCloseButton.setOnClickListener(mOnClickListener);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mTitle = (TextView) findViewById(R.id.title);
- mThumbnail = (TabsPanelThumbnailView) findViewById(R.id.thumbnail);
- mCloseButton = (ImageView) findViewById(R.id.close);
- mThumbnailWrapper = (TabThumbnailWrapper) findViewById(R.id.wrapper);
-
- growCloseButtonHitArea();
- }
-
- private void growCloseButtonHitArea() {
- getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- getViewTreeObserver().removeOnPreDrawListener(this);
-
- // Ideally we want the close button hit area to be 40x40dp but we are constrained by the height of the parent, so
- // we make it as tall as the parent view and 40dp across.
- final int targetHitArea = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, getResources().getDisplayMetrics());;
-
- final Rect hitRect = new Rect();
- hitRect.top = 0;
- hitRect.right = getWidth();
- hitRect.left = getWidth() - targetHitArea;
- hitRect.bottom = targetHitArea;
-
- setTouchDelegate(new TouchDelegateWithReset(hitRect, mCloseButton));
-
- return true;
- }
- });
- }
-
- protected void assignValues(Tab tab) {
- if (tab == null) {
- return;
- }
-
- mTabId = tab.getId();
-
- setChecked(Tabs.getInstance().isSelectedTab(tab));
-
- Drawable thumbnailImage = tab.getThumbnail();
- mThumbnail.setImageDrawable(thumbnailImage);
-
- mThumbnail.setPrivateMode(tab.isPrivate());
-
- if (mThumbnailWrapper != null) {
- mThumbnailWrapper.setRecording(tab.isRecording());
- }
-
- final String tabTitle = tab.getDisplayTitle();
- mTitle.setText(tabTitle);
- mCloseButton.setTag(this);
-
- if (tab.isAudioPlaying()) {
- mTitle.setCompoundDrawablesWithIntrinsicBounds(R.drawable.tab_audio_playing, 0, 0, 0);
- final String tabTitleWithAudio =
- getResources().getString(R.string.tab_title_prefix_is_playing_audio, tabTitle);
- mTitle.setContentDescription(tabTitleWithAudio);
- } else {
- mTitle.setCompoundDrawables(null, null, null, null);
- mTitle.setContentDescription(tabTitle);
- }
- }
-
- public int getTabId() {
- return mTabId;
- }
-
- public void setThumbnail(Drawable thumbnail) {
- mThumbnail.setImageDrawable(thumbnail);
- }
-
- public void setCloseVisible(boolean visible) {
- mCloseButton.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
- }
-
- public void setPrivateMode(boolean isPrivate) {
- ((ThemedRelativeLayout) findViewById(R.id.wrapper)).setPrivateMode(isPrivate);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutRecyclerAdapter.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutRecyclerAdapter.java
deleted file mode 100644
index 090d74f9d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutRecyclerAdapter.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import org.mozilla.gecko.Tab;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-
-import java.util.ArrayList;
-
-public class TabsLayoutRecyclerAdapter
- extends RecyclerView.Adapter<TabsLayoutRecyclerAdapter.TabsListViewHolder> {
-
- private static final String LOGTAG = "Gecko" + TabsLayoutRecyclerAdapter.class.getSimpleName();
-
- private final int tabLayoutId;
- private @NonNull ArrayList<Tab> tabs;
- private final LayoutInflater inflater;
- private final boolean isPrivate;
- // Click listener for the close button on itemViews.
- private final Button.OnClickListener closeOnClickListener;
-
- // The TabsLayoutItemView takes care of caching its own Views, so we don't need to do anything
- // here except not be abstract.
- public static class TabsListViewHolder extends RecyclerView.ViewHolder {
- public TabsListViewHolder(View itemView) {
- super(itemView);
- }
- }
-
- public TabsLayoutRecyclerAdapter(Context context, int tabLayoutId, boolean isPrivate,
- Button.OnClickListener closeOnClickListener) {
- inflater = LayoutInflater.from(context);
- this.tabLayoutId = tabLayoutId;
- this.isPrivate = isPrivate;
- this.closeOnClickListener = closeOnClickListener;
- tabs = new ArrayList<>(0);
- }
-
- /* package */ final void setTabs(@NonNull ArrayList<Tab> tabs) {
- this.tabs = tabs;
- notifyDataSetChanged();
- }
-
- /* package */ final void clear() {
- tabs = new ArrayList<>(0);
- notifyDataSetChanged();
- }
-
- /* package */ final boolean removeTab(Tab tab) {
- final int position = getPositionForTab(tab);
- if (position == -1) {
- return false;
- }
- tabs.remove(position);
- notifyItemRemoved(position);
- return true;
- }
-
- /* package */ final int getPositionForTab(Tab tab) {
- if (tab == null) {
- return -1;
- }
-
- return tabs.indexOf(tab);
- }
-
- /* package */ void notifyTabChanged(Tab tab) {
- notifyItemChanged(getPositionForTab(tab));
- }
-
- /* package */ void notifyTabInserted(Tab tab, int index) {
- if (index >= 0 && index <= tabs.size()) {
- tabs.add(index, tab);
- notifyItemInserted(index);
- } else {
- // Add to the end.
- tabs.add(tab);
- notifyItemInserted(tabs.size() - 1);
- // index == -1 is a valid way to add to the end, the other cases are errors.
- if (index != -1) {
- Log.e(LOGTAG, "Tab was inserted at an invalid position: " + Integer.toString(index));
- }
- }
- }
-
- @Override
- public int getItemCount() {
- return tabs.size();
- }
-
- private Tab getItem(int position) {
- return tabs.get(position);
- }
-
- @Override
- public void onBindViewHolder(TabsListViewHolder viewHolder, int position) {
- final Tab tab = getItem(position);
- final TabsLayoutItemView itemView = (TabsLayoutItemView) viewHolder.itemView;
- itemView.assignValues(tab);
- // Be careful (re)setting position values here: bind is called on each notifyItemChanged,
- // so you could be stomping on values that have been set in support of other animations
- // that are already underway.
- }
-
- @Override
- public TabsListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- final TabsLayoutItemView viewItem = (TabsLayoutItemView) inflater.inflate(tabLayoutId, parent, false);
- viewItem.setPrivateMode(isPrivate);
- viewItem.setCloseOnClickListener(closeOnClickListener);
-
- return new TabsListViewHolder(viewItem);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsListLayout.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsListLayout.java
deleted file mode 100644
index 8cf2f8ede..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsListLayout.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.content.Context;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.helper.ItemTouchHelper;
-import android.util.AttributeSet;
-import android.view.View;
-
-public class TabsListLayout extends TabsLayout {
- // Time to animate non-flinged tabs of screen, in milliseconds
- private static final int ANIMATION_DURATION = 250;
-
- // Time between starting successive tab animations in closeAllTabs.
- private static final int ANIMATION_CASCADE_DELAY = 75;
-
- private int closeAllAnimationCount;
-
- public TabsListLayout(Context context, AttributeSet attrs) {
- super(context, attrs, R.layout.tabs_list_item_view);
-
- setHasFixedSize(true);
-
- setLayoutManager(new LinearLayoutManager(context));
-
- // A TouchHelper handler for swipe to close.
- final TabsTouchHelperCallback callback = new TabsTouchHelperCallback(this);
- final ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
- touchHelper.attachToRecyclerView(this);
-
- setItemAnimator(new TabsListLayoutAnimator(ANIMATION_DURATION));
- }
-
- @Override
- public void closeAll() {
- final int childCount = getChildCount();
-
- // Just close the panel if there are no tabs to close.
- if (childCount == 0) {
- autoHidePanel();
- return;
- }
-
- // Disable the view so that gestures won't interfere wth the tab close animation.
- setEnabled(false);
-
- // Delay starting each successive animation to create a cascade effect.
- int cascadeDelay = 0;
- closeAllAnimationCount = 0;
- for (int i = childCount - 1; i >= 0; i--) {
- final View view = getChildAt(i);
- if (view == null) {
- continue;
- }
-
- final PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION);
- animator.attach(view, PropertyAnimator.Property.ALPHA, 0);
-
- animator.attach(view, PropertyAnimator.Property.TRANSLATION_X, view.getWidth());
-
- closeAllAnimationCount++;
-
- animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
- @Override
- public void onPropertyAnimationStart() {
- }
-
- @Override
- public void onPropertyAnimationEnd() {
- closeAllAnimationCount--;
- if (closeAllAnimationCount > 0) {
- return;
- }
-
- // Hide the panel after the animation is done.
- autoHidePanel();
-
- // Re-enable the view after the animation is done.
- TabsListLayout.this.setEnabled(true);
-
- // Then actually close all the tabs.
- closeAllTabs();
- }
- });
-
- ThreadUtils.postDelayedToUiThread(new Runnable() {
- @Override
- public void run() {
- animator.start();
- }
- }, cascadeDelay);
-
- cascadeDelay += ANIMATION_CASCADE_DELAY;
- }
- }
-
- @Override
- protected boolean addAtIndexRequiresScroll(int index) {
- return index == 0 || index == getAdapter().getItemCount() - 1;
- }
-
- @Override
- public void onChildAttachedToWindow(View child) {
- // Make sure we reset any attributes that may have been animated in this child's previous
- // incarnation.
- child.setTranslationX(0);
- child.setTranslationY(0);
- child.setAlpha(1);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsListLayoutAnimator.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsListLayoutAnimator.java
deleted file mode 100644
index 471abf883..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsListLayoutAnimator.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import org.mozilla.gecko.widget.DefaultItemAnimatorBase;
-
-import android.support.v4.view.ViewCompat;
-import android.support.v7.widget.RecyclerView;
-import android.view.View;
-
-class TabsListLayoutAnimator extends DefaultItemAnimatorBase {
- public TabsListLayoutAnimator(int animationDuration) {
- setRemoveDuration(animationDuration);
- setAddDuration(animationDuration);
- // A fade in/out each time the title/thumbnail/etc. gets updated isn't helpful, so disable
- // the change animation.
- setSupportsChangeAnimations(false);
- }
-
- @Override
- protected boolean preAnimateRemoveImpl(final RecyclerView.ViewHolder holder) {
- // If the view isn't at full alpha then we were closed by a swipe which an
- // ItemTouchHelper is animating for us, so just return without animating the remove and
- // let runPendingAnimations pick up the rest.
- if (holder.itemView.getAlpha() < 1) {
- return false;
- }
- resetAnimation(holder);
- return true;
- }
-
- @Override
- protected void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
- final View itemView = holder.itemView;
- ViewCompat.animate(itemView)
- .setDuration(getRemoveDuration())
- .translationX(itemView.getWidth())
- .alpha(0)
- .setListener(new DefaultRemoveVpaListener(holder))
- .start();
- }
-
- @Override
- protected boolean preAnimateAddImpl(RecyclerView.ViewHolder holder) {
- resetAnimation(holder);
- final View itemView = holder.itemView;
- itemView.setTranslationX(itemView.getWidth());
- itemView.setAlpha(0);
- return true;
- }
-
- @Override
- protected void animateAddImpl(final RecyclerView.ViewHolder holder) {
- final View itemView = holder.itemView;
- ViewCompat.animate(itemView)
- .setDuration(getAddDuration())
- .translationX(0)
- .alpha(1)
- .setListener(new DefaultAddVpaListener(holder))
- .start();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsPanel.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsPanel.java
deleted file mode 100644
index 2be127010..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsPanel.java
+++ /dev/null
@@ -1,456 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.animation.ViewHelper;
-import org.mozilla.gecko.lwt.LightweightTheme;
-import org.mozilla.gecko.lwt.LightweightThemeDrawable;
-import org.mozilla.gecko.restrictions.Restrictable;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.widget.GeckoPopupMenu;
-import org.mozilla.gecko.widget.IconTabWidget;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.Button;
-import android.widget.FrameLayout;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
-import org.mozilla.gecko.widget.themed.ThemedImageButton;
-
-public class TabsPanel extends LinearLayout
- implements GeckoPopupMenu.OnMenuItemClickListener,
- LightweightTheme.OnChangeListener,
- IconTabWidget.OnTabChangedListener {
- private static final String LOGTAG = "Gecko" + TabsPanel.class.getSimpleName();
-
- public enum Panel {
- NORMAL_TABS,
- PRIVATE_TABS,
- }
-
- public interface PanelView {
- void setTabsPanel(TabsPanel panel);
- void show();
- void hide();
- boolean shouldExpand();
- }
-
- public interface CloseAllPanelView extends PanelView {
- void closeAll();
- }
-
- public interface TabsLayout extends CloseAllPanelView {
- void setEmptyView(View view);
- }
-
- public interface TabsLayoutChangeListener {
- void onTabsLayoutChange(int width, int height);
- }
-
- public static View createTabsLayout(final Context context, final AttributeSet attrs) {
- final boolean isLandscape = context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
-
- if (HardwareUtils.isTablet() || isLandscape) {
- return new TabsGridLayout(context, attrs);
- } else {
- return new TabsListLayout(context, attrs);
- }
- }
-
- private final Context mContext;
- private final GeckoApp mActivity;
- private final LightweightTheme mTheme;
- private RelativeLayout mHeader;
- private FrameLayout mTabsContainer;
- private PanelView mPanel;
- private PanelView mPanelNormal;
- private PanelView mPanelPrivate;
- private TabsLayoutChangeListener mLayoutChangeListener;
-
- private IconTabWidget mTabWidget;
- private View mMenuButton;
- private ImageButton mAddTab;
- private ImageButton mNavBackButton;
-
- private Panel mCurrentPanel;
- private boolean mVisible;
- private boolean mHeaderVisible;
-
- private final GeckoPopupMenu mPopupMenu;
-
- public TabsPanel(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- mActivity = (GeckoApp) context;
- mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
-
- mCurrentPanel = Panel.NORMAL_TABS;
-
- mPopupMenu = new GeckoPopupMenu(context);
- mPopupMenu.inflate(R.menu.tabs_menu);
- mPopupMenu.setOnMenuItemClickListener(this);
-
- inflateLayout(context);
- initialize();
- }
-
- private void inflateLayout(Context context) {
- LayoutInflater.from(context).inflate(R.layout.tabs_panel_default, this);
- }
-
- private void initialize() {
- mHeader = (RelativeLayout) findViewById(R.id.tabs_panel_header);
- mTabsContainer = (FrameLayout) findViewById(R.id.tabs_container);
-
- mPanelNormal = (PanelView) findViewById(R.id.normal_tabs);
- mPanelNormal.setTabsPanel(this);
-
- mPanelPrivate = (PanelView) findViewById(R.id.private_tabs_panel);
- mPanelPrivate.setTabsPanel(this);
-
- mAddTab = (ImageButton) findViewById(R.id.add_tab);
- mAddTab.setOnClickListener(new Button.OnClickListener() {
- @Override
- public void onClick(View v) {
- TabsPanel.this.addTab();
- }
- });
-
- mTabWidget = (IconTabWidget) findViewById(R.id.tab_widget);
-
- mTabWidget.addTab(R.drawable.tabs_normal, R.string.tabs_normal);
- final ThemedImageButton privateTabsPanel =
- (ThemedImageButton) mTabWidget.addTab(R.drawable.tabs_private, R.string.tabs_private);
- privateTabsPanel.setPrivateMode(true);
-
- if (!Restrictions.isAllowed(mContext, Restrictable.PRIVATE_BROWSING)) {
- mTabWidget.setVisibility(View.GONE);
- }
-
- mTabWidget.setTabSelectionListener(this);
-
- mMenuButton = findViewById(R.id.menu);
- mMenuButton.setOnClickListener(new Button.OnClickListener() {
- @Override
- public void onClick(View view) {
- showMenu();
- }
- });
-
- mNavBackButton = (ImageButton) findViewById(R.id.nav_back);
- mNavBackButton.setOnClickListener(new Button.OnClickListener() {
- @Override
- public void onClick(View view) {
- mActivity.onBackPressed();
- }
- });
- }
-
- public void showMenu() {
- final Menu menu = mPopupMenu.getMenu();
-
- // Each panel has a "+" shortcut button, so don't show it for that panel.
- menu.findItem(R.id.new_tab).setVisible(mCurrentPanel != Panel.NORMAL_TABS);
- menu.findItem(R.id.new_private_tab).setVisible(mCurrentPanel != Panel.PRIVATE_TABS
- && Restrictions.isAllowed(mContext, Restrictable.PRIVATE_BROWSING));
-
- // Only show "Clear * tabs" for current panel.
- menu.findItem(R.id.close_all_tabs).setVisible(mCurrentPanel == Panel.NORMAL_TABS);
- menu.findItem(R.id.close_private_tabs).setVisible(mCurrentPanel == Panel.PRIVATE_TABS);
-
- mPopupMenu.show();
- }
-
- private void addTab() {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "new_tab");
-
- if (mCurrentPanel == Panel.NORMAL_TABS) {
- mActivity.addTab();
- } else {
- mActivity.addPrivateTab();
- }
-
- mActivity.autoHideTabs();
- }
-
- @Override
- public void onTabChanged(int index) {
- if (index == 0) {
- show(Panel.NORMAL_TABS);
- } else {
- show(Panel.PRIVATE_TABS);
- }
- }
-
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- final int itemId = item.getItemId();
-
- if (itemId == R.id.close_all_tabs) {
- if (mCurrentPanel == Panel.NORMAL_TABS) {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.MENU, "close_all_tabs");
-
- // Disable the menu button so that the menu won't interfere with the tab close animation.
- mMenuButton.setEnabled(false);
- ((CloseAllPanelView) mPanelNormal).closeAll();
- } else {
- Log.e(LOGTAG, "Close all tabs menu item should only be visible for normal tabs panel");
- }
- return true;
- }
-
- if (itemId == R.id.close_private_tabs) {
- if (mCurrentPanel == Panel.PRIVATE_TABS) {
- // Mask private browsing
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.MENU, "close_all_tabs");
-
- ((CloseAllPanelView) mPanelPrivate).closeAll();
- } else {
- Log.e(LOGTAG, "Close private tabs menu item should only be visible for private tabs panel");
- }
- return true;
- }
-
- if (itemId == R.id.new_tab || itemId == R.id.new_private_tab) {
- hide();
- }
-
- return mActivity.onOptionsItemSelected(item);
- }
-
- private static int getTabContainerHeight(FrameLayout tabsContainer) {
- final Resources resources = tabsContainer.getContext().getResources();
-
- final int screenHeight = resources.getDisplayMetrics().heightPixels;
- final int actionBarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height);
-
- return screenHeight - actionBarHeight;
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- mTheme.addListener(this);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mTheme.removeListener(this);
- }
-
- @Override
- @SuppressWarnings("deprecation") // setBackgroundDrawable deprecated by API level 16
- public void onLightweightThemeChanged() {
- final int background = ContextCompat.getColor(getContext(), R.color.text_and_tabs_tray_grey);
- final LightweightThemeDrawable drawable = mTheme.getColorDrawable(this, background, true);
- if (drawable == null)
- return;
-
- drawable.setAlpha(34, 0);
- setBackgroundDrawable(drawable);
- }
-
- @Override
- public void onLightweightThemeReset() {
- setBackgroundColor(ContextCompat.getColor(getContext(), R.color.text_and_tabs_tray_grey));
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- onLightweightThemeChanged();
- }
-
- // Tabs Panel Toolbar contains the Buttons
- static class TabsPanelToolbar extends LinearLayout
- implements LightweightTheme.OnChangeListener {
- private final LightweightTheme mTheme;
-
- public TabsPanelToolbar(Context context, AttributeSet attrs) {
- super(context, attrs);
- mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- mTheme.addListener(this);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mTheme.removeListener(this);
- }
-
- @Override
- @SuppressWarnings("deprecation") // setBackgroundDrawable deprecated by API level 16
- public void onLightweightThemeChanged() {
- final int background = ContextCompat.getColor(getContext(), R.color.text_and_tabs_tray_grey);
- final LightweightThemeDrawable drawable = mTheme.getColorDrawable(this, background);
- if (drawable == null)
- return;
-
- drawable.setAlpha(34, 34);
- setBackgroundDrawable(drawable);
- }
-
- @Override
- public void onLightweightThemeReset() {
- setBackgroundColor(ContextCompat.getColor(getContext(), R.color.text_and_tabs_tray_grey));
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- onLightweightThemeChanged();
- }
- }
-
- public void show(Panel panelToShow) {
- prepareToShow(panelToShow);
- int height = getVerticalPanelHeight();
- dispatchLayoutChange(getWidth(), height);
- mHeaderVisible = true;
- }
-
- public void prepareToShow(Panel panelToShow) {
- if (!isShown()) {
- setVisibility(View.VISIBLE);
- }
-
- if (mPanel != null) {
- // Hide the old panel.
- mPanel.hide();
- }
-
- mVisible = true;
- mCurrentPanel = panelToShow;
-
- int index = panelToShow.ordinal();
- mTabWidget.setCurrentTab(index);
-
- switch (panelToShow) {
- case NORMAL_TABS:
- mPanel = mPanelNormal;
- break;
- case PRIVATE_TABS:
- mPanel = mPanelPrivate;
- break;
-
- default:
- throw new IllegalArgumentException("Unknown panel type " + panelToShow);
- }
- mPanel.show();
-
- mAddTab.setVisibility(View.VISIBLE);
-
- mMenuButton.setEnabled(true);
- mPopupMenu.setAnchor(mMenuButton);
- }
-
- public int getVerticalPanelHeight() {
- final int actionBarHeight = mContext.getResources().getDimensionPixelSize(R.dimen.browser_toolbar_height);
- final int height = actionBarHeight + getTabContainerHeight(mTabsContainer);
- return height;
- }
-
- public void hide() {
- mHeaderVisible = false;
-
- if (mVisible) {
- mVisible = false;
- mPopupMenu.dismiss();
- dispatchLayoutChange(0, 0);
- }
- }
-
- public void refresh() {
- removeAllViews();
-
- inflateLayout(mContext);
- initialize();
-
- if (mVisible)
- show(mCurrentPanel);
- }
-
- public void autoHidePanel() {
- mActivity.autoHideTabs();
- }
-
- @Override
- public boolean isShown() {
- return mVisible;
- }
-
- public void setHWLayerEnabled(boolean enabled) {
- if (enabled) {
- mHeader.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- mTabsContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- } else {
- mHeader.setLayerType(View.LAYER_TYPE_NONE, null);
- mTabsContainer.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- }
-
- public void prepareTabsAnimation(PropertyAnimator animator) {
- if (!mHeaderVisible) {
- final Resources resources = getContext().getResources();
- final int toolbarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height);
- final int translationY = (mVisible ? 0 : -toolbarHeight);
- if (mVisible) {
- ViewHelper.setTranslationY(mHeader, -toolbarHeight);
- ViewHelper.setTranslationY(mTabsContainer, -toolbarHeight);
- ViewHelper.setAlpha(mTabsContainer, 0.0f);
- }
- animator.attach(mTabsContainer, PropertyAnimator.Property.ALPHA, mVisible ? 1.0f : 0.0f);
- animator.attach(mTabsContainer, PropertyAnimator.Property.TRANSLATION_Y, translationY);
- animator.attach(mHeader, PropertyAnimator.Property.TRANSLATION_Y, translationY);
- }
-
- setHWLayerEnabled(true);
- }
-
- public void finishTabsAnimation() {
- setHWLayerEnabled(false);
-
- // If the tray is now hidden, call hide() on current panel and unset it as the current panel
- // to avoid hide() being called again when the layout is opened next.
- if (!mVisible && mPanel != null) {
- mPanel.hide();
- mPanel = null;
- }
- }
-
- public void setTabsLayoutChangeListener(TabsLayoutChangeListener listener) {
- mLayoutChangeListener = listener;
- }
-
- private void dispatchLayoutChange(int width, int height) {
- if (mLayoutChangeListener != null)
- mLayoutChangeListener.onTabsLayoutChange(width, height);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsPanelThumbnailView.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsPanelThumbnailView.java
deleted file mode 100644
index 09254bf76..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsPanelThumbnailView.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.ThumbnailHelper;
-import org.mozilla.gecko.widget.CropImageView;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-
-/**
- * A width constrained ImageView to show thumbnails of open tabs in the tabs panel.
- */
-public class TabsPanelThumbnailView extends CropImageView {
- public static final String LOGTAG = "Gecko" + TabsPanelThumbnailView.class.getSimpleName();
-
-
- public TabsPanelThumbnailView(final Context context) {
- this(context, null);
- }
-
- public TabsPanelThumbnailView(final Context context, final AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public TabsPanelThumbnailView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- protected float getAspectRatio() {
- return ThumbnailHelper.TABS_PANEL_THUMBNAIL_ASPECT_RATIO;
- }
-
- @Override
- public void setImageDrawable(Drawable drawable) {
- boolean resize = true;
-
- if (drawable == null) {
- drawable = getResources().getDrawable(R.drawable.tab_panel_tab_background);
- resize = false;
- setScaleType(ScaleType.FIT_XY);
- }
-
- super.setImageDrawable(drawable, resize);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsTouchHelperCallback.java b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsTouchHelperCallback.java
deleted file mode 100644
index 36e9e4739..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsTouchHelperCallback.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.tabs;
-
-import android.graphics.Canvas;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.helper.ItemTouchHelper;
-import android.view.View;
-
-class TabsTouchHelperCallback extends ItemTouchHelper.Callback {
- private final DismissListener dismissListener;
-
- interface DismissListener {
- void onItemDismiss(View view);
- }
-
- public TabsTouchHelperCallback(DismissListener dismissListener) {
- this.dismissListener = dismissListener;
- }
-
- @Override
- public boolean isItemViewSwipeEnabled() {
- return true;
- }
-
- @Override
- public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
- return makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
- }
-
- @Override
- public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) {
- dismissListener.onItemDismiss(viewHolder.itemView);
- }
-
- @Override
- public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
- RecyclerView.ViewHolder target) {
- return false;
- }
-
- // Alpha on an itemView being swiped should decrease to a min over a distance equal to the
- // width of the item being swiped.
- @Override
- public void onChildDraw(Canvas c,
- RecyclerView recyclerView,
- RecyclerView.ViewHolder viewHolder,
- float dX,
- float dY,
- int actionState,
- boolean isCurrentlyActive) {
- if (actionState != ItemTouchHelper.ACTION_STATE_SWIPE) {
- return;
- }
-
- super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
-
- viewHolder.itemView.setAlpha(Math.max(0.1f,
- Math.min(1f, 1f - 2f * Math.abs(dX) / viewHolder.itemView.getWidth())));
- }
-
- public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
- super.clearView(recyclerView, viewHolder);
- viewHolder.itemView.setAlpha(1);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryConstants.java b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryConstants.java
deleted file mode 100644
index 6ed4bb0d4..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryConstants.java
+++ /dev/null
@@ -1,16 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.telemetry;
-
-import org.mozilla.gecko.AppConstants;
-
-public class TelemetryConstants {
- // To test, set this to true & change "toolkit.telemetry.server" in about:config.
- public static final boolean UPLOAD_ENABLED = AppConstants.MOZILLA_OFFICIAL; // Disabled for developer builds.
-
- public static final String USER_AGENT =
- "Firefox-Android-Telemetry/" + AppConstants.MOZ_APP_VERSION + " (" + AppConstants.MOZ_APP_UA_NAME + ")";
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryCorePingDelegate.java b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryCorePingDelegate.java
deleted file mode 100644
index fae674b2d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryCorePingDelegate.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, you can obtain one at http://mozilla.org/MPL/2.0/.
- */
-
-package org.mozilla.gecko.telemetry;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.support.annotation.Nullable;
-import android.support.annotation.WorkerThread;
-import android.util.Log;
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.adjust.AttributionHelperListener;
-import org.mozilla.gecko.telemetry.measurements.CampaignIdMeasurements;
-import org.mozilla.gecko.delegates.BrowserAppDelegateWithReference;
-import org.mozilla.gecko.distribution.DistributionStoreCallback;
-import org.mozilla.gecko.search.SearchEngineManager;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.telemetry.measurements.SearchCountMeasurements;
-import org.mozilla.gecko.telemetry.measurements.SessionMeasurements;
-import org.mozilla.gecko.telemetry.pingbuilders.TelemetryCorePingBuilder;
-import org.mozilla.gecko.util.StringUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.io.IOException;
-
-/**
- * An activity-lifecycle delegate for uploading the core ping.
- */
-public class TelemetryCorePingDelegate extends BrowserAppDelegateWithReference
- implements SearchEngineManager.SearchEngineCallback, AttributionHelperListener {
- private static final String LOGTAG = StringUtils.safeSubstring(
- "Gecko" + TelemetryCorePingDelegate.class.getSimpleName(), 0, 23);
-
- private static final String PREF_IS_FIRST_RUN = "telemetry-isFirstRun";
-
- private TelemetryDispatcher telemetryDispatcher; // lazy
- private final SessionMeasurements sessionMeasurements = new SessionMeasurements();
-
- @Override
- public void onStart(final BrowserApp browserApp) {
- TelemetryPreferences.initPreferenceObserver(browserApp, browserApp.getProfile().getName());
-
- // We don't upload in onCreate because that's only called when the Activity needs to be instantiated
- // and it's possible the system will never free the Activity from memory.
- //
- // We don't upload in onResume/onPause because that will be called each time the Activity is obscured,
- // including by our own Activities/dialogs, and there is no reason to upload each time we're unobscured.
- //
- // We're left with onStart/onStop and we upload in onStart because onStop is not guaranteed to be called
- // and we want to upload the first run ASAP (e.g. to get install data before the app may crash).
- uploadPing(browserApp);
- }
-
- @Override
- public void onStop(final BrowserApp browserApp) {
- // We've decided to upload primarily in onStart (see note there). However, if it's the first run,
- // it's possible a user used fennec and decided never to return to it again - it'd be great to get
- // their session information before they decided to give it up so we upload here on first run.
- //
- // Caveats:
- // * onStop is not guaranteed to be called in low memory conditions so it's possible we won't upload,
- // but it's better than it was before.
- // * Besides first run (because of this call), we can never get the user's *last* session data.
- //
- // If we are really interested in the user's last session data, we could consider uploading in onStop
- // but it's less robust (see discussion in bug 1277091).
- final SharedPreferences sharedPrefs = getSharedPreferences(browserApp);
- if (sharedPrefs.getBoolean(PREF_IS_FIRST_RUN, true)) {
- sharedPrefs.edit()
- .putBoolean(PREF_IS_FIRST_RUN, false)
- .apply();
- uploadPing(browserApp);
- }
- }
-
- private void uploadPing(final BrowserApp browserApp) {
- final SearchEngineManager searchEngineManager = browserApp.getSearchEngineManager();
- searchEngineManager.getEngine(this);
- }
-
- @Override
- public void onResume(BrowserApp browserApp) {
- sessionMeasurements.recordSessionStart();
- }
-
- @Override
- public void onPause(BrowserApp browserApp) {
- // onStart/onStop is ideal over onResume/onPause. However, onStop is not guaranteed to be called and
- // dealing with that possibility adds a lot of complexity that we don't want to handle at this point.
- sessionMeasurements.recordSessionEnd(browserApp);
- }
-
- @WorkerThread // via constructor
- private TelemetryDispatcher getTelemetryDispatcher(final BrowserApp browserApp) {
- if (telemetryDispatcher == null) {
- final GeckoProfile profile = browserApp.getProfile();
- final String profilePath = profile.getDir().getAbsolutePath();
- final String profileName = profile.getName();
- telemetryDispatcher = new TelemetryDispatcher(profilePath, profileName);
- }
- return telemetryDispatcher;
- }
-
- private SharedPreferences getSharedPreferences(final BrowserApp activity) {
- return GeckoSharedPrefs.forProfileName(activity, activity.getProfile().getName());
- }
-
- // via SearchEngineCallback - may be called from any thread.
- @Override
- public void execute(@Nullable final org.mozilla.gecko.search.SearchEngine engine) {
- // Don't waste resources queueing to the background thread if we don't have a reference.
- if (getBrowserApp() == null) {
- return;
- }
-
- // The containing method can be called from onStart: queue this work so that
- // the first launch of the activity doesn't trigger profile init too early.
- //
- // Additionally, getAndIncrementSequenceNumber must be called from a worker thread.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @WorkerThread
- @Override
- public void run() {
- final BrowserApp activity = getBrowserApp();
- if (activity == null) {
- return;
- }
-
- final GeckoProfile profile = activity.getProfile();
- if (!TelemetryUploadService.isUploadEnabledByProfileConfig(activity, profile)) {
- Log.d(LOGTAG, "Core ping upload disabled by profile config. Returning.");
- return;
- }
-
- final String clientID;
- try {
- clientID = profile.getClientId();
- } catch (final IOException e) {
- Log.w(LOGTAG, "Unable to get client ID to generate core ping: " + e);
- return;
- }
-
- // Each profile can have different telemetry data so we intentionally grab the shared prefs for the profile.
- final SharedPreferences sharedPrefs = getSharedPreferences(activity);
- final SessionMeasurements.SessionMeasurementsContainer sessionMeasurementsContainer =
- sessionMeasurements.getAndResetSessionMeasurements(activity);
- final TelemetryCorePingBuilder pingBuilder = new TelemetryCorePingBuilder(activity)
- .setClientID(clientID)
- .setDefaultSearchEngine(TelemetryCorePingBuilder.getEngineIdentifier(engine))
- .setProfileCreationDate(TelemetryCorePingBuilder.getProfileCreationDate(activity, profile))
- .setSequenceNumber(TelemetryCorePingBuilder.getAndIncrementSequenceNumber(sharedPrefs))
- .setSessionCount(sessionMeasurementsContainer.sessionCount)
- .setSessionDuration(sessionMeasurementsContainer.elapsedSeconds);
- maybeSetOptionalMeasurements(activity, sharedPrefs, pingBuilder);
-
- getTelemetryDispatcher(activity).queuePingForUpload(activity, pingBuilder);
- }
- });
- }
-
- private void maybeSetOptionalMeasurements(final Context context, final SharedPreferences sharedPrefs,
- final TelemetryCorePingBuilder pingBuilder) {
- final String distributionId = sharedPrefs.getString(DistributionStoreCallback.PREF_DISTRIBUTION_ID, null);
- if (distributionId != null) {
- pingBuilder.setOptDistributionID(distributionId);
- }
-
- final ExtendedJSONObject searchCounts = SearchCountMeasurements.getAndZeroSearch(sharedPrefs);
- if (searchCounts.size() > 0) {
- pingBuilder.setOptSearchCounts(searchCounts);
- }
-
- final String campaignId = CampaignIdMeasurements.getCampaignIdFromPrefs(context);
- if (campaignId != null) {
- pingBuilder.setOptCampaignId(campaignId);
- }
- }
-
- @Override
- public void onCampaignIdChanged(String campaignId) {
- CampaignIdMeasurements.updateCampaignIdPref(getBrowserApp(), campaignId);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryDispatcher.java b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryDispatcher.java
deleted file mode 100644
index c702bb92c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryDispatcher.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, you can obtain one at http://mozilla.org/MPL/2.0/.
- */
-
-package org.mozilla.gecko.telemetry;
-
-import android.content.Context;
-import android.support.annotation.WorkerThread;
-import android.util.Log;
-import org.mozilla.gecko.telemetry.pingbuilders.TelemetryCorePingBuilder;
-import org.mozilla.gecko.telemetry.schedulers.TelemetryUploadScheduler;
-import org.mozilla.gecko.telemetry.schedulers.TelemetryUploadAllPingsImmediatelyScheduler;
-import org.mozilla.gecko.telemetry.stores.TelemetryJSONFilePingStore;
-import org.mozilla.gecko.telemetry.stores.TelemetryPingStore;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * The entry-point for Java-based telemetry. This class handles:
- * * Initializing the Stores & Schedulers.
- * * Queueing upload requests for a given ping.
- *
- * To test Telemetry , see {@link TelemetryConstants} &
- * https://wiki.mozilla.org/Mobile/Fennec/Android/Java_telemetry.
- *
- * The full architecture is:
- *
- * Fennec -(PingBuilder)-> Dispatcher -2-> Scheduler -> UploadService
- * | 1 |
- * Store <--------------------------
- *
- * The store acts as a single store of truth and contains a list of all
- * pings waiting to be uploaded. The dispatcher will queue a ping to upload
- * by writing it to the store. Later, the UploadService will try to upload
- * this queued ping by reading directly from the store.
- *
- * To implement a new ping type, you should:
- * 1) Implement a {@link org.mozilla.gecko.telemetry.pingbuilders.TelemetryPingBuilder} for your ping type.
- * 2) Re-use a ping store in .../stores/ or implement a new one: {@link TelemetryPingStore}. The
- * type of store may be affected by robustness requirements (e.g. do you have data in addition to
- * pings that need to be atomically updated when a ping is stored?) and performance requirements.
- * 3) Re-use an upload scheduler in .../schedulers/ or implement a new one: {@link TelemetryUploadScheduler}.
- * 4) Initialize your Store & (if new) Scheduler in the constructor of this class
- * 5) Add a queuePingForUpload method for your PingBuilder class (see
- * {@link #queuePingForUpload(Context, TelemetryCorePingBuilder)})
- * 6) In Fennec, where you want to store a ping and attempt upload, create a PingBuilder and
- * pass it to the new queuePingForUpload method.
- */
-public class TelemetryDispatcher {
- private static final String LOGTAG = "Gecko" + TelemetryDispatcher.class.getSimpleName();
-
- private static final String STORE_CONTAINER_DIR_NAME = "telemetry_java";
- private static final String CORE_STORE_DIR_NAME = "core";
-
- private final TelemetryJSONFilePingStore coreStore;
-
- private final TelemetryUploadAllPingsImmediatelyScheduler uploadAllPingsImmediatelyScheduler;
-
- @WorkerThread // via TelemetryJSONFilePingStore
- public TelemetryDispatcher(final String profilePath, final String profileName) {
- final String storePath = profilePath + File.separator + STORE_CONTAINER_DIR_NAME;
-
- // There are measurements in the core ping (e.g. seq #) that would ideally be atomically updated
- // when the ping is stored. However, for simplicity, we use the json store and accept the possible
- // loss of data (see bug 1243585 comment 16+ for more).
- coreStore = new TelemetryJSONFilePingStore(new File(storePath, CORE_STORE_DIR_NAME), profileName);
-
- uploadAllPingsImmediatelyScheduler = new TelemetryUploadAllPingsImmediatelyScheduler();
- }
-
- private void queuePingForUpload(final Context context, final TelemetryPing ping, final TelemetryPingStore store,
- final TelemetryUploadScheduler scheduler) {
- final QueuePingRunnable runnable = new QueuePingRunnable(context, ping, store, scheduler);
- ThreadUtils.postToBackgroundThread(runnable); // TODO: Investigate how busy this thread is. See if we want another.
- }
-
- /**
- * Queues the given ping for upload and potentially schedules upload. This method can be called from any thread.
- */
- public void queuePingForUpload(final Context context, final TelemetryCorePingBuilder pingBuilder) {
- final TelemetryPing ping = pingBuilder.build();
- queuePingForUpload(context, ping, coreStore, uploadAllPingsImmediatelyScheduler);
- }
-
- private static class QueuePingRunnable implements Runnable {
- private final Context applicationContext;
- private final TelemetryPing ping;
- private final TelemetryPingStore store;
- private final TelemetryUploadScheduler scheduler;
-
- public QueuePingRunnable(final Context context, final TelemetryPing ping, final TelemetryPingStore store,
- final TelemetryUploadScheduler scheduler) {
- this.applicationContext = context.getApplicationContext();
- this.ping = ping;
- this.store = store;
- this.scheduler = scheduler;
- }
-
- @Override
- public void run() {
- // We block while storing the ping so the scheduled upload is guaranteed to have the newly-stored value.
- try {
- store.storePing(ping);
- } catch (final IOException e) {
- // Don't log exception to avoid leaking profile path.
- Log.e(LOGTAG, "Unable to write ping to disk. Continuing with upload attempt");
- }
-
- if (scheduler.isReadyToUpload(store)) {
- scheduler.scheduleUpload(applicationContext, store);
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryPing.java b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryPing.java
deleted file mode 100644
index b6ee9c2d8..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryPing.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.telemetry;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-/**
- * Container for telemetry data and the data necessary to upload it.
- *
- * The doc ID is used by a Store to manipulate its internal pings and should
- * be the same value found in the urlPath.
- *
- * If you want to create one of these, consider extending
- * {@link org.mozilla.gecko.telemetry.pingbuilders.TelemetryPingBuilder}
- * or one of its descendants.
- */
-public class TelemetryPing {
- private final String urlPath;
- private final ExtendedJSONObject payload;
- private final String docID;
-
- public TelemetryPing(final String urlPath, final ExtendedJSONObject payload, final String docID) {
- this.urlPath = urlPath;
- this.payload = payload;
- this.docID = docID;
- }
-
- public String getURLPath() { return urlPath; }
- public ExtendedJSONObject getPayload() { return payload; }
- public String getDocID() { return docID; }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryPreferences.java b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryPreferences.java
deleted file mode 100644
index 329f5b803..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryPreferences.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.telemetry;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.PrefsHelper;
-import org.mozilla.gecko.PrefsHelper.PrefHandler;
-
-import java.lang.ref.WeakReference;
-
-/**
- * Manages getting and setting any preferences related to telemetry.
- *
- * This class persists any Gecko preferences beyond shutdown so that these values
- * can be accessed on the next run before Gecko is started as we expect Telemetry
- * to run before Gecko is available.
- */
-public class TelemetryPreferences {
- private TelemetryPreferences() {}
-
- private static final String GECKO_PREF_SERVER_URL = "toolkit.telemetry.server";
- private static final String SHARED_PREF_SERVER_URL = "telemetry-serverUrl";
-
- // Defaults are a mirror of about:config defaults so we can access them before Gecko is available.
- private static final String DEFAULT_SERVER_URL = "https://incoming.telemetry.mozilla.org";
-
- private static final String[] OBSERVED_PREFS = {
- GECKO_PREF_SERVER_URL,
- };
-
- public static String getServerSchemeHostPort(final Context context, final String profileName) {
- return getSharedPrefs(context, profileName).getString(SHARED_PREF_SERVER_URL, DEFAULT_SERVER_URL);
- }
-
- public static void initPreferenceObserver(final Context context, final String profileName) {
- final PrefHandler prefHandler = new TelemetryPrefHandler(context, profileName);
- PrefsHelper.addObserver(OBSERVED_PREFS, prefHandler); // gets preference value when gecko starts.
- }
-
- private static SharedPreferences getSharedPrefs(final Context context, final String profileName) {
- return GeckoSharedPrefs.forProfileName(context, profileName);
- }
-
- private static class TelemetryPrefHandler extends PrefsHelper.PrefHandlerBase {
- private final WeakReference<Context> contextWeakReference;
- private final String profileName;
-
- private TelemetryPrefHandler(final Context context, final String profileName) {
- contextWeakReference = new WeakReference<>(context);
- this.profileName = profileName;
- }
-
- @Override
- public void prefValue(final String pref, final String value) {
- final Context context = contextWeakReference.get();
- if (context == null) {
- return;
- }
-
- if (!pref.equals(GECKO_PREF_SERVER_URL)) {
- throw new IllegalStateException("Unknown preference: " + pref);
- }
-
- getSharedPrefs(context, profileName).edit()
- .putString(SHARED_PREF_SERVER_URL, value)
- .apply();
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryUploadService.java b/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryUploadService.java
deleted file mode 100644
index 543281174..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/TelemetryUploadService.java
+++ /dev/null
@@ -1,347 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.telemetry;
-
-import android.app.IntentService;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-import ch.boye.httpclientandroidlib.HttpHeaders;
-import ch.boye.httpclientandroidlib.HttpResponse;
-import ch.boye.httpclientandroidlib.client.ClientProtocolException;
-import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
-import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.restrictions.Restrictable;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.net.BaseResource;
-import org.mozilla.gecko.sync.net.BaseResourceDelegate;
-import org.mozilla.gecko.sync.net.Resource;
-import org.mozilla.gecko.telemetry.stores.TelemetryPingStore;
-import org.mozilla.gecko.util.DateUtil;
-import org.mozilla.gecko.util.NetworkUtils;
-import org.mozilla.gecko.util.StringUtils;
-
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.security.GeneralSecurityException;
-import java.util.Calendar;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * The service that handles retrieving a list of telemetry pings to upload from the given
- * {@link TelemetryPingStore}, uploading those payloads to the associated server, and reporting
- * back to the Store which uploads were a success.
- */
-public class TelemetryUploadService extends IntentService {
- private static final String LOGTAG = StringUtils.safeSubstring("Gecko" + TelemetryUploadService.class.getSimpleName(), 0, 23);
- private static final String WORKER_THREAD_NAME = LOGTAG + "Worker";
-
- public static final String ACTION_UPLOAD = "upload";
- public static final String EXTRA_STORE = "store";
-
- // TelemetryUploadService can run in a background thread so for future proofing, we set it volatile.
- private static volatile boolean isDisabled = false;
-
- public static void setDisabled(final boolean isDisabled) {
- TelemetryUploadService.isDisabled = isDisabled;
- if (isDisabled) {
- Log.d(LOGTAG, "Telemetry upload disabled (env var?");
- }
- }
-
- public TelemetryUploadService() {
- super(WORKER_THREAD_NAME);
-
- // Intent redelivery can fail hard (e.g. we OOM as we try to upload, the Intent gets redelivered, repeat)
- // so for simplicity, we avoid it. We expect the upload service to eventually get called again by the caller.
- setIntentRedelivery(false);
- }
-
- /**
- * Handles a ping with the mandatory extras:
- * * EXTRA_STORE: A {@link TelemetryPingStore} where the pings to upload are located
- */
- @Override
- public void onHandleIntent(final Intent intent) {
- Log.d(LOGTAG, "Service started");
-
- if (!isReadyToUpload(this, intent)) {
- return;
- }
-
- final TelemetryPingStore store = intent.getParcelableExtra(EXTRA_STORE);
- final boolean wereAllUploadsSuccessful = uploadPendingPingsFromStore(this, store);
- store.maybePrunePings();
- Log.d(LOGTAG, "Service finished: upload and prune attempts completed");
-
- if (!wereAllUploadsSuccessful) {
- // If we had an upload failure, we should stop the IntentService and drop any
- // pending Intents in the queue so we don't waste resources (e.g. battery)
- // trying to upload when there's likely to be another connection failure.
- Log.d(LOGTAG, "Clearing Intent queue due to connection failures");
- stopSelf();
- }
- }
-
- /**
- * @return true if all pings were uploaded successfully, false otherwise.
- */
- private static boolean uploadPendingPingsFromStore(final Context context, final TelemetryPingStore store) {
- final List<TelemetryPing> pingsToUpload = store.getAllPings();
- if (pingsToUpload.isEmpty()) {
- return true;
- }
-
- final String serverSchemeHostPort = TelemetryPreferences.getServerSchemeHostPort(context, store.getProfileName());
- final HashSet<String> successfulUploadIDs = new HashSet<>(pingsToUpload.size()); // used for side effects.
- final PingResultDelegate delegate = new PingResultDelegate(successfulUploadIDs);
- for (final TelemetryPing ping : pingsToUpload) {
- // TODO: It'd be great to re-use the same HTTP connection for each upload request.
- delegate.setDocID(ping.getDocID());
- final String url = serverSchemeHostPort + "/" + ping.getURLPath();
- uploadPayload(url, ping.getPayload(), delegate);
-
- // There are minimal gains in trying to upload if we already failed one attempt.
- if (delegate.hadConnectionError()) {
- break;
- }
- }
-
- final boolean wereAllUploadsSuccessful = !delegate.hadConnectionError();
- if (wereAllUploadsSuccessful) {
- // We don't log individual successful uploads to avoid log spam.
- Log.d(LOGTAG, "Telemetry upload success!");
- }
- store.onUploadAttemptComplete(successfulUploadIDs);
- return wereAllUploadsSuccessful;
- }
-
- private static void uploadPayload(final String url, final ExtendedJSONObject payload, final ResultDelegate delegate) {
- final BaseResource resource;
- try {
- resource = new BaseResource(url);
- } catch (final URISyntaxException e) {
- Log.w(LOGTAG, "URISyntaxException for server URL when creating BaseResource: returning.");
- return;
- }
-
- delegate.setResource(resource);
- resource.delegate = delegate;
- resource.setShouldCompressUploadedEntity(true);
- resource.setShouldChunkUploadsHint(false); // Telemetry servers don't support chunking.
-
- // We're in a background thread so we don't have any reason to do this asynchronously.
- // If we tried, onStartCommand would return and IntentService might stop itself before we finish.
- resource.postBlocking(payload);
- }
-
- private static boolean isReadyToUpload(final Context context, final Intent intent) {
- // Sanity check: is upload enabled? Generally, the caller should check this before starting the service.
- // Since we don't have the profile here, we rely on the caller to check the enabled state for the profile.
- if (!isUploadEnabledByAppConfig(context)) {
- Log.w(LOGTAG, "Upload is not available by configuration; returning");
- return false;
- }
-
- if (!NetworkUtils.isConnected(context)) {
- Log.w(LOGTAG, "Network is not connected; returning");
- return false;
- }
-
- if (!isIntentValid(intent)) {
- Log.w(LOGTAG, "Received invalid Intent; returning");
- return false;
- }
-
- if (!ACTION_UPLOAD.equals(intent.getAction())) {
- Log.w(LOGTAG, "Unknown action: " + intent.getAction() + ". Returning");
- return false;
- }
-
- return true;
- }
-
- /**
- * Determines if the telemetry upload feature is enabled via the application configuration. Prefer to use
- * {@link #isUploadEnabledByProfileConfig(Context, GeckoProfile)} if the profile is available as it takes into
- * account more information.
- *
- * You may wish to also check if the network is connected when calling this method.
- *
- * Note that this method logs debug statements when upload is disabled.
- */
- public static boolean isUploadEnabledByAppConfig(final Context context) {
- if (!TelemetryConstants.UPLOAD_ENABLED) {
- Log.d(LOGTAG, "Telemetry upload feature is compile-time disabled");
- return false;
- }
-
- if (isDisabled) {
- Log.d(LOGTAG, "Telemetry upload feature is disabled by intent (in testing?)");
- return false;
- }
-
- if (!GeckoPreferences.getBooleanPref(context, GeckoPreferences.PREFS_HEALTHREPORT_UPLOAD_ENABLED, true)) {
- Log.d(LOGTAG, "Telemetry upload opt-out");
- return false;
- }
-
- if (Restrictions.isRestrictedProfile(context) &&
- !Restrictions.isAllowed(context, Restrictable.HEALTH_REPORT)) {
- Log.d(LOGTAG, "Telemetry upload feature disabled by admin profile");
- return false;
- }
-
- return true;
- }
-
- /**
- * Determines if the telemetry upload feature is enabled via profile & application level configurations. This is the
- * preferred method.
- *
- * You may wish to also check if the network is connected when calling this method.
- *
- * Note that this method logs debug statements when upload is disabled.
- */
- public static boolean isUploadEnabledByProfileConfig(final Context context, final GeckoProfile profile) {
- if (profile.inGuestMode()) {
- Log.d(LOGTAG, "Profile is in guest mode");
- return false;
- }
-
- return isUploadEnabledByAppConfig(context);
- }
-
- private static boolean isIntentValid(final Intent intent) {
- // Intent can be null. Bug 1025937.
- if (intent == null) {
- Log.d(LOGTAG, "Received null intent");
- return false;
- }
-
- if (intent.getParcelableExtra(EXTRA_STORE) == null) {
- Log.d(LOGTAG, "Received invalid store in Intent");
- return false;
- }
-
- return true;
- }
-
- /**
- * Logs on success & failure and appends the set ID to the given Set on success.
- *
- * Note: you *must* set the ping ID before attempting upload or we'll throw!
- *
- * We use mutation on the set ID and the successful upload array to avoid object allocation.
- */
- private static class PingResultDelegate extends ResultDelegate {
- // We persist pings and don't need to worry about losing data so we keep these
- // durations short to save resources (e.g. battery).
- private static final int SOCKET_TIMEOUT_MILLIS = (int) TimeUnit.SECONDS.toMillis(30);
- private static final int CONNECTION_TIMEOUT_MILLIS = (int) TimeUnit.SECONDS.toMillis(30);
-
- /** The store ID of the ping currently being uploaded. Use {@link #getDocID()} to access it. */
- private String docID = null;
- private final Set<String> successfulUploadIDs;
-
- private boolean hadConnectionError = false;
-
- public PingResultDelegate(final Set<String> successfulUploadIDs) {
- super();
- this.successfulUploadIDs = successfulUploadIDs;
- }
-
- @Override
- public int socketTimeout() {
- return SOCKET_TIMEOUT_MILLIS;
- }
-
- @Override
- public int connectionTimeout() {
- return CONNECTION_TIMEOUT_MILLIS;
- }
-
- private String getDocID() {
- if (docID == null) {
- throw new IllegalStateException("Expected ping ID to have been updated before retrieval");
- }
- return docID;
- }
-
- public void setDocID(final String id) {
- docID = id;
- }
-
- @Override
- public String getUserAgent() {
- return TelemetryConstants.USER_AGENT;
- }
-
- @Override
- public void handleHttpResponse(final HttpResponse response) {
- final int status = response.getStatusLine().getStatusCode();
- switch (status) {
- case 200:
- case 201:
- successfulUploadIDs.add(getDocID());
- break;
- default:
- Log.w(LOGTAG, "Telemetry upload failure. HTTP status: " + status);
- hadConnectionError = true;
- }
- }
-
- @Override
- public void handleHttpProtocolException(final ClientProtocolException e) {
- // We don't log the exception to prevent leaking user data.
- Log.w(LOGTAG, "HttpProtocolException when trying to upload telemetry");
- hadConnectionError = true;
- }
-
- @Override
- public void handleHttpIOException(final IOException e) {
- // We don't log the exception to prevent leaking user data.
- Log.w(LOGTAG, "HttpIOException when trying to upload telemetry");
- hadConnectionError = true;
- }
-
- @Override
- public void handleTransportException(final GeneralSecurityException e) {
- // We don't log the exception to prevent leaking user data.
- Log.w(LOGTAG, "Transport exception when trying to upload telemetry");
- hadConnectionError = true;
- }
-
- private boolean hadConnectionError() {
- return hadConnectionError;
- }
-
- @Override
- public void addHeaders(final HttpRequestBase request, final DefaultHttpClient client) {
- super.addHeaders(request, client);
- request.addHeader(HttpHeaders.DATE, DateUtil.getDateInHTTPFormat(Calendar.getInstance().getTime()));
- }
- }
-
- /**
- * A hack because I want to set the resource after the Delegate is constructed.
- * Be sure to call {@link #setResource(Resource)}!
- */
- private static abstract class ResultDelegate extends BaseResourceDelegate {
- public ResultDelegate() {
- super(null);
- }
-
- protected void setResource(final Resource resource) {
- this.resource = resource;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/telemetry/measurements/CampaignIdMeasurements.java b/mobile/android/base/java/org/mozilla/gecko/telemetry/measurements/CampaignIdMeasurements.java
deleted file mode 100644
index 61229b21b..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/measurements/CampaignIdMeasurements.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, you can obtain one at http://mozilla.org/MPL/2.0/.
- */
-
-package org.mozilla.gecko.telemetry.measurements;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.text.TextUtils;
-
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.adjust.AttributionHelperListener;
-
-/**
- * A class to retrieve and store the campaign Id pref that is used when the Adjust SDK gives us
- * new attribution from the {@link AttributionHelperListener}.
- */
-public class CampaignIdMeasurements {
- private static final String PREF_CAMPAIGN_ID = "measurements-campaignId";
-
- public static String getCampaignIdFromPrefs(@NonNull final Context context) {
- return GeckoSharedPrefs.forProfile(context)
- .getString(PREF_CAMPAIGN_ID, null);
- }
-
- public static void updateCampaignIdPref(@NonNull final Context context, @NonNull final String campaignId) {
- if (TextUtils.isEmpty(campaignId)) {
- return;
- }
- GeckoSharedPrefs.forProfile(context)
- .edit()
- .putString(PREF_CAMPAIGN_ID, campaignId)
- .apply();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/telemetry/measurements/SearchCountMeasurements.java b/mobile/android/base/java/org/mozilla/gecko/telemetry/measurements/SearchCountMeasurements.java
deleted file mode 100644
index c08ad6c02..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/measurements/SearchCountMeasurements.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, you can obtain one at http://mozilla.org/MPL/2.0/.
- */
-
-package org.mozilla.gecko.telemetry.measurements;
-
-import android.content.SharedPreferences;
-import android.support.annotation.NonNull;
-import android.support.annotation.VisibleForTesting;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * A place to store and retrieve the number of times a user has searched with a specific engine from a
- * specific location. This is designed for use as a telemetry core ping measurement.
- *
- * The implementation works by storing a preference for each engine-location pair and incrementing them
- * each time {@link #incrementSearch(SharedPreferences, String, String)} is called. In order to
- * retrieve the full set of keys later, we store all the available key names in another preference.
- *
- * When we retrieve the keys in {@link #getAndZeroSearch(SharedPreferences)} (using the set of keys
- * preference), the values saved to the preferences are returned and the preferences are removed
- * (i.e. zeroed) from Shared Preferences. The reason we remove the preferences (rather than actually
- * zeroing them) is to avoid bloating shared preferences if 1) the set of engines ever changes or
- * 2) we remove this feature.
- *
- * Since we increment a value on each successive search, which doesn't take up more space, we don't
- * have to worry about using excess disk space if the measurements are never zeroed (e.g. telemetry
- * upload is disabled). In the worst case, we overflow the integer and may return negative values.
- *
- * This class is thread-safe by locking access to its public methods. When this class was written, incrementing &
- * retrieval were called from multiple threads so rather than enforcing the callers keep their threads straight, it
- * was simpler to lock all access.
- */
-public class SearchCountMeasurements {
- /** The set of "engine + where" keys we've stored; used for retrieving stored engines. */
- @VisibleForTesting static final String PREF_SEARCH_KEYSET = "measurements-search-count-keyset";
- private static final String PREF_SEARCH_PREFIX = "measurements-search-count-engine-"; // + "engine.where"
-
- private SearchCountMeasurements() {}
-
- public static synchronized void incrementSearch(@NonNull final SharedPreferences prefs,
- @NonNull final String engineIdentifier, @NonNull final String where) {
- final String engineWhereStr = engineIdentifier + "." + where;
- final String key = getEngineSearchCountKey(engineWhereStr);
-
- final int count = prefs.getInt(key, 0);
- prefs.edit().putInt(key, count + 1).apply();
-
- unionKeyToSearchKeyset(prefs, engineWhereStr);
- }
-
- /**
- * @param key Engine of the form, "engine.where"
- */
- private static void unionKeyToSearchKeyset(@NonNull final SharedPreferences prefs, @NonNull final String key) {
- final Set<String> keysFromPrefs = prefs.getStringSet(PREF_SEARCH_KEYSET, Collections.<String>emptySet());
- if (keysFromPrefs.contains(key)) {
- return;
- }
-
- // String set returned by shared prefs cannot be modified so we copy.
- final Set<String> keysToSave = new HashSet<>(keysFromPrefs);
- keysToSave.add(key);
- prefs.edit().putStringSet(PREF_SEARCH_KEYSET, keysToSave).apply();
- }
-
- /**
- * Gets and zeroes search counts.
- *
- * We return ExtendedJSONObject for now because that's the format needed by the core telemetry ping.
- */
- public static synchronized ExtendedJSONObject getAndZeroSearch(@NonNull final SharedPreferences prefs) {
- final ExtendedJSONObject out = new ExtendedJSONObject();
- final SharedPreferences.Editor editor = prefs.edit();
-
- final Set<String> keysFromPrefs = prefs.getStringSet(PREF_SEARCH_KEYSET, Collections.<String>emptySet());
- for (final String engineWhereStr : keysFromPrefs) {
- final String key = getEngineSearchCountKey(engineWhereStr);
- out.put(engineWhereStr, prefs.getInt(key, 0));
- editor.remove(key);
- }
- editor.remove(PREF_SEARCH_KEYSET)
- .apply();
- return out;
- }
-
- /**
- * @param engineWhereStr string of the form "engine.where"
- * @return the key for the engines' search counts in shared preferences
- */
- @VisibleForTesting static String getEngineSearchCountKey(final String engineWhereStr) {
- return PREF_SEARCH_PREFIX + engineWhereStr;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/telemetry/measurements/SessionMeasurements.java b/mobile/android/base/java/org/mozilla/gecko/telemetry/measurements/SessionMeasurements.java
deleted file mode 100644
index 6f7d2127a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/measurements/SessionMeasurements.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, you can obtain one at http://mozilla.org/MPL/2.0/.
- */
-
-package org.mozilla.gecko.telemetry.measurements;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.support.annotation.UiThread;
-import android.support.annotation.VisibleForTesting;
-import org.mozilla.gecko.GeckoSharedPrefs;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * A class to measure the number of user sessions & their durations. It was created for use with the
- * telemetry core ping. A session is the time between {@link #recordSessionStart()} and
- * {@link #recordSessionEnd(Context)}.
- *
- * This class is thread-safe, provided the thread annotations are followed. Under the hood, this class uses
- * SharedPreferences & because there is no atomic getAndSet operation, we synchronize access to it.
- */
-public class SessionMeasurements {
- @VisibleForTesting static final String PREF_SESSION_COUNT = "measurements-session-count";
- @VisibleForTesting static final String PREF_SESSION_DURATION = "measurements-session-duration";
-
- private boolean sessionStarted = false;
- private long timeAtSessionStartNano = -1;
-
- @UiThread // we assume this will be called on the same thread as session end so we don't have to synchronize sessionStarted.
- public void recordSessionStart() {
- if (sessionStarted) {
- throw new IllegalStateException("Trying to start session but it is already started");
- }
- sessionStarted = true;
- timeAtSessionStartNano = getSystemTimeNano();
- }
-
- @UiThread // we assume this will be called on the same thread as session start so we don't have to synchronize sessionStarted.
- public void recordSessionEnd(final Context context) {
- if (!sessionStarted) {
- throw new IllegalStateException("Expected session to be started before session end is called");
- }
- sessionStarted = false;
-
- final long sessionElapsedSeconds = TimeUnit.NANOSECONDS.toSeconds(getSystemTimeNano() - timeAtSessionStartNano);
- final SharedPreferences sharedPrefs = getSharedPreferences(context);
- synchronized (this) {
- final int sessionCount = sharedPrefs.getInt(PREF_SESSION_COUNT, 0);
- final long totalElapsedSeconds = sharedPrefs.getLong(PREF_SESSION_DURATION, 0);
- sharedPrefs.edit()
- .putInt(PREF_SESSION_COUNT, sessionCount + 1)
- .putLong(PREF_SESSION_DURATION, totalElapsedSeconds + sessionElapsedSeconds)
- .apply();
- }
- }
-
- /**
- * Gets the session measurements since the last time the measurements were last retrieved.
- */
- public synchronized SessionMeasurementsContainer getAndResetSessionMeasurements(final Context context) {
- final SharedPreferences sharedPrefs = getSharedPreferences(context);
- final int sessionCount = sharedPrefs.getInt(PREF_SESSION_COUNT, 0);
- final long totalElapsedSeconds = sharedPrefs.getLong(PREF_SESSION_DURATION, 0);
- sharedPrefs.edit()
- .putInt(PREF_SESSION_COUNT, 0)
- .putLong(PREF_SESSION_DURATION, 0)
- .apply();
- return new SessionMeasurementsContainer(sessionCount, totalElapsedSeconds);
- }
-
- @VisibleForTesting SharedPreferences getSharedPreferences(final Context context) {
- return GeckoSharedPrefs.forProfile(context);
- }
-
- /**
- * Returns (roughly) the system uptime in nanoseconds. A less coupled implementation would
- * take this value from the caller of recordSession*, however, we do this internally to ensure
- * the caller uses both a time system consistent between the start & end calls and uses the
- * appropriate time system (i.e. not wall time, which can change when the clock is changed).
- */
- @VisibleForTesting long getSystemTimeNano() { // TODO: necessary?
- return System.nanoTime();
- }
-
- public static final class SessionMeasurementsContainer {
- /** The number of sessions. */
- public final int sessionCount;
- /** The number of seconds elapsed in ALL sessions included in {@link #sessionCount}. */
- public final long elapsedSeconds;
-
- private SessionMeasurementsContainer(final int sessionCount, final long elapsedSeconds) {
- this.sessionCount = sessionCount;
- this.elapsedSeconds = elapsedSeconds;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/telemetry/pingbuilders/TelemetryCorePingBuilder.java b/mobile/android/base/java/org/mozilla/gecko/telemetry/pingbuilders/TelemetryCorePingBuilder.java
deleted file mode 100644
index 3f5480f37..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/pingbuilders/TelemetryCorePingBuilder.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
-
-package org.mozilla.gecko.telemetry.pingbuilders;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.WorkerThread;
-import android.text.TextUtils;
-
-import android.util.Log;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.Locales;
-import org.mozilla.gecko.search.SearchEngine;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.telemetry.TelemetryPing;
-import org.mozilla.gecko.util.DateUtil;
-import org.mozilla.gecko.Experiments;
-import org.mozilla.gecko.util.StringUtils;
-
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.Locale;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Builds a {@link TelemetryPing} representing a core ping.
- *
- * See https://gecko.readthedocs.org/en/latest/toolkit/components/telemetry/telemetry/core-ping.html
- * for details on the core ping.
- */
-public class TelemetryCorePingBuilder extends TelemetryPingBuilder {
- private static final String LOGTAG = StringUtils.safeSubstring(TelemetryCorePingBuilder.class.getSimpleName(), 0, 23);
-
- // For legacy reasons, this preference key is not namespaced with "core".
- private static final String PREF_SEQ_COUNT = "telemetry-seqCount";
-
- private static final String NAME = "core";
- private static final int VERSION_VALUE = 7; // For version history, see toolkit/components/telemetry/docs/core-ping.rst
- private static final String OS_VALUE = "Android";
-
- private static final String ARCHITECTURE = "arch";
- private static final String CAMPAIGN_ID = "campaignId";
- private static final String CLIENT_ID = "clientId";
- private static final String DEFAULT_SEARCH_ENGINE = "defaultSearch";
- private static final String DEVICE = "device";
- private static final String DISTRIBUTION_ID = "distributionId";
- private static final String EXPERIMENTS = "experiments";
- private static final String LOCALE = "locale";
- private static final String OS_ATTR = "os";
- private static final String OS_VERSION = "osversion";
- private static final String PING_CREATION_DATE = "created";
- private static final String PROFILE_CREATION_DATE = "profileDate";
- private static final String SEARCH_COUNTS = "searches";
- private static final String SEQ = "seq";
- private static final String SESSION_COUNT = "sessions";
- private static final String SESSION_DURATION = "durations";
- private static final String TIMEZONE_OFFSET = "tz";
- private static final String VERSION_ATTR = "v";
-
- public TelemetryCorePingBuilder(final Context context) {
- initPayloadConstants(context);
- }
-
- private void initPayloadConstants(final Context context) {
- payload.put(VERSION_ATTR, VERSION_VALUE);
- payload.put(OS_ATTR, OS_VALUE);
-
- // We limit the device descriptor to 32 characters because it can get long. We give fewer characters to the
- // manufacturer because we're less likely to have manufacturers with similar names than we are for a
- // manufacturer to have two devices with the similar names (e.g. Galaxy S6 vs. Galaxy Note 6).
- final String deviceDescriptor =
- StringUtils.safeSubstring(Build.MANUFACTURER, 0, 12) + '-' + StringUtils.safeSubstring(Build.MODEL, 0, 19);
-
- final Calendar nowCalendar = Calendar.getInstance();
- final DateFormat pingCreationDateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
-
- payload.put(ARCHITECTURE, AppConstants.ANDROID_CPU_ARCH);
- payload.put(DEVICE, deviceDescriptor);
- payload.put(LOCALE, Locales.getLanguageTag(Locale.getDefault()));
- payload.put(OS_VERSION, Integer.toString(Build.VERSION.SDK_INT)); // A String for cross-platform reasons.
- payload.put(PING_CREATION_DATE, pingCreationDateFormat.format(nowCalendar.getTime()));
- payload.put(TIMEZONE_OFFSET, DateUtil.getTimezoneOffsetInMinutesForGivenDate(nowCalendar));
- payload.putArray(EXPERIMENTS, Experiments.getActiveExperiments(context));
- }
-
- @Override
- public String getDocType() {
- return NAME;
- }
-
- @Override
- public String[] getMandatoryFields() {
- return new String[] {
- ARCHITECTURE,
- CLIENT_ID,
- DEFAULT_SEARCH_ENGINE,
- DEVICE,
- LOCALE,
- OS_ATTR,
- OS_VERSION,
- PING_CREATION_DATE,
- PROFILE_CREATION_DATE,
- SEQ,
- TIMEZONE_OFFSET,
- VERSION_ATTR,
- };
- }
-
- public TelemetryCorePingBuilder setClientID(@NonNull final String clientID) {
- if (clientID == null) {
- throw new IllegalArgumentException("Expected non-null clientID");
- }
- payload.put(CLIENT_ID, clientID);
- return this;
- }
-
- /**
- * @param engine the default search engine identifier, or null if there is an error.
- */
- public TelemetryCorePingBuilder setDefaultSearchEngine(@Nullable final String engine) {
- if (engine != null && engine.isEmpty()) {
- throw new IllegalArgumentException("Received empty string. Expected identifier or null.");
- }
- payload.put(DEFAULT_SEARCH_ENGINE, engine);
- return this;
- }
-
- public TelemetryCorePingBuilder setOptDistributionID(@NonNull final String distributionID) {
- if (distributionID == null) {
- throw new IllegalArgumentException("Expected non-null distribution ID");
- }
- payload.put(DISTRIBUTION_ID, distributionID);
- return this;
- }
-
- /**
- * @param searchCounts non-empty JSON with {"engine.where": <int-count>}
- */
- public TelemetryCorePingBuilder setOptSearchCounts(@NonNull final ExtendedJSONObject searchCounts) {
- if (searchCounts == null) {
- throw new IllegalStateException("Expected non-null search counts");
- } else if (searchCounts.size() == 0) {
- throw new IllegalStateException("Expected non-empty search counts");
- }
-
- payload.put(SEARCH_COUNTS, searchCounts);
- return this;
- }
-
- public TelemetryCorePingBuilder setOptCampaignId(final String campaignId) {
- if (campaignId == null) {
- throw new IllegalStateException("Expected non-null campaign ID.");
- }
- payload.put(CAMPAIGN_ID, campaignId);
- return this;
- }
-
- /**
- * @param date The profile creation date in days to the unix epoch (not millis!), or null if there is an error.
- */
- public TelemetryCorePingBuilder setProfileCreationDate(@Nullable final Long date) {
- if (date != null && date < 0) {
- throw new IllegalArgumentException("Expect positive date value. Received: " + date);
- }
- payload.put(PROFILE_CREATION_DATE, date);
- return this;
- }
-
- /**
- * @param seq a positive sequence number.
- */
- public TelemetryCorePingBuilder setSequenceNumber(final int seq) {
- if (seq < 0) {
- // Since this is an increasing value, it's possible we can overflow into negative values and get into a
- // crash loop so we don't crash on invalid arg - we can investigate if we see negative values on the server.
- Log.w(LOGTAG, "Expected positive sequence number. Received: " + seq);
- }
- payload.put(SEQ, seq);
- return this;
- }
-
- public TelemetryCorePingBuilder setSessionCount(final int sessionCount) {
- if (sessionCount < 0) {
- // Since this is an increasing value, it's possible we can overflow into negative values and get into a
- // crash loop so we don't crash on invalid arg - we can investigate if we see negative values on the server.
- Log.w(LOGTAG, "Expected positive session count. Received: " + sessionCount);
- }
- payload.put(SESSION_COUNT, sessionCount);
- return this;
- }
-
- public TelemetryCorePingBuilder setSessionDuration(final long sessionDuration) {
- if (sessionDuration < 0) {
- // Since this is an increasing value, it's possible we can overflow into negative values and get into a
- // crash loop so we don't crash on invalid arg - we can investigate if we see negative values on the server.
- Log.w(LOGTAG, "Expected positive session duration. Received: " + sessionDuration);
- }
- payload.put(SESSION_DURATION, sessionDuration);
- return this;
- }
-
- /**
- * Gets the sequence number from shared preferences and increments it in the prefs. This method
- * is not thread safe.
- */
- @WorkerThread // synchronous shared prefs write.
- public static int getAndIncrementSequenceNumber(final SharedPreferences sharedPrefsForProfile) {
- final int seq = sharedPrefsForProfile.getInt(PREF_SEQ_COUNT, 1);
-
- sharedPrefsForProfile.edit().putInt(PREF_SEQ_COUNT, seq + 1).apply();
- return seq;
- }
-
- /**
- * @return the profile creation date in the format expected by
- * {@link TelemetryCorePingBuilder#setProfileCreationDate(Long)}.
- */
- @WorkerThread
- public static Long getProfileCreationDate(final Context context, final GeckoProfile profile) {
- final long profileMillis = profile.getAndPersistProfileCreationDate(context);
- if (profileMillis < 0) {
- return null;
- }
- return (long) Math.floor((double) profileMillis / TimeUnit.DAYS.toMillis(1));
- }
-
- /**
- * @return the search engine identifier in the format expected by the core ping.
- */
- @Nullable
- public static String getEngineIdentifier(@Nullable final SearchEngine searchEngine) {
- if (searchEngine == null) {
- return null;
- }
- final String identifier = searchEngine.getIdentifier();
- return TextUtils.isEmpty(identifier) ? null : identifier;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/telemetry/pingbuilders/TelemetryPingBuilder.java b/mobile/android/base/java/org/mozilla/gecko/telemetry/pingbuilders/TelemetryPingBuilder.java
deleted file mode 100644
index 57fa0fd8b..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/pingbuilders/TelemetryPingBuilder.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
-
-package org.mozilla.gecko.telemetry.pingbuilders;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.telemetry.TelemetryPing;
-
-import java.util.Set;
-import java.util.UUID;
-
-/**
- * A generic Builder for {@link TelemetryPing} instances. Each overriding class is
- * expected to create a specific type of ping (e.g. "core").
- *
- * This base class handles the common ping operations under the hood:
- * * Validating mandatory fields
- * * Forming the server url
- */
-abstract class TelemetryPingBuilder {
- // In the server url, the initial path directly after the "scheme://host:port/"
- private static final String SERVER_INITIAL_PATH = "submit/telemetry";
-
- private final String serverPath;
- protected final ExtendedJSONObject payload;
- private final String docID;
-
- public TelemetryPingBuilder() {
- docID = UUID.randomUUID().toString();
- serverPath = getTelemetryServerPath(getDocType(), docID);
- payload = new ExtendedJSONObject();
- }
-
- /**
- * @return the name of the ping (e.g. "core")
- */
- public abstract String getDocType();
-
- /**
- * @return the fields that are mandatory for the resultant ping to be uploaded to
- * the server. These will be validated before the ping is built.
- */
- public abstract String[] getMandatoryFields();
-
- public TelemetryPing build() {
- validatePayload();
- return new TelemetryPing(serverPath, payload, docID);
- }
-
- private void validatePayload() {
- final Set<String> keySet = payload.keySet();
- for (final String mandatoryField : getMandatoryFields()) {
- if (!keySet.contains(mandatoryField)) {
- throw new IllegalArgumentException("Builder does not contain mandatory field: " +
- mandatoryField);
- }
- }
- }
-
- /**
- * Returns a url of the format:
- * http://hostname/submit/telemetry/docId/docType/appName/appVersion/appUpdateChannel/appBuildID
- *
- * @param docType The name of the ping (e.g. "main")
- * @return a url at which to POST the telemetry data to
- */
- private static String getTelemetryServerPath(final String docType, final String docID) {
- final String appName = AppConstants.MOZ_APP_BASENAME;
- final String appVersion = AppConstants.MOZ_APP_VERSION;
- final String appUpdateChannel = AppConstants.MOZ_UPDATE_CHANNEL;
- final String appBuildId = AppConstants.MOZ_APP_BUILDID;
-
- // The compiler will optimize a single String concatenation into a StringBuilder statement.
- // If you change this `return`, be sure to keep it as a single statement to keep it optimized!
- return SERVER_INITIAL_PATH + '/' +
- docID + '/' +
- docType + '/' +
- appName + '/' +
- appVersion + '/' +
- appUpdateChannel + '/' +
- appBuildId;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/telemetry/schedulers/TelemetryUploadAllPingsImmediatelyScheduler.java b/mobile/android/base/java/org/mozilla/gecko/telemetry/schedulers/TelemetryUploadAllPingsImmediatelyScheduler.java
deleted file mode 100644
index 047a646c3..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/schedulers/TelemetryUploadAllPingsImmediatelyScheduler.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, you can obtain one at http://mozilla.org/MPL/2.0/.
- */
-
-package org.mozilla.gecko.telemetry.schedulers;
-
-import android.content.Context;
-import android.content.Intent;
-import org.mozilla.gecko.telemetry.stores.TelemetryPingStore;
-import org.mozilla.gecko.telemetry.TelemetryUploadService;
-
-/**
- * Schedules an upload with all pings to be sent immediately.
- */
-public class TelemetryUploadAllPingsImmediatelyScheduler implements TelemetryUploadScheduler {
-
- @Override
- public boolean isReadyToUpload(final TelemetryPingStore store) {
- // We're ready since we don't have any conditions to wait on (e.g. on wifi, accumulated X pings).
- return true;
- }
-
- @Override
- public void scheduleUpload(final Context applicationContext, final TelemetryPingStore store) {
- final Intent i = new Intent(TelemetryUploadService.ACTION_UPLOAD);
- i.setClass(applicationContext, TelemetryUploadService.class);
- i.putExtra(TelemetryUploadService.EXTRA_STORE, store);
- applicationContext.startService(i);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/telemetry/schedulers/TelemetryUploadScheduler.java b/mobile/android/base/java/org/mozilla/gecko/telemetry/schedulers/TelemetryUploadScheduler.java
deleted file mode 100644
index 63305aad5..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/schedulers/TelemetryUploadScheduler.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, you can obtain one at http://mozilla.org/MPL/2.0/.
- */
-
-package org.mozilla.gecko.telemetry.schedulers;
-
-import android.content.Context;
-import org.mozilla.gecko.telemetry.stores.TelemetryPingStore;
-
-/**
- * An implementation of this class can investigate the given {@link TelemetryPingStore} to
- * decide if it's ready to upload the pings inside that Store (e.g. on wifi? have we
- * accumulated X pings?) and can schedule that upload. Typically, the upload will be
- * scheduled by sending an {@link android.content.Intent} to the
- * {@link org.mozilla.gecko.telemetry.TelemetryUploadService}, either immediately or
- * via an external scheduler (e.g. {@link android.app.job.JobScheduler}).
- *
- * N.B.: If the Store is not ready to upload, an implementation *should not* try to reschedule
- * the check to see if it's time to upload - this is expected to be handled by the caller.
- */
-public interface TelemetryUploadScheduler {
- boolean isReadyToUpload(TelemetryPingStore store);
- void scheduleUpload(Context applicationContext, TelemetryPingStore store);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/telemetry/stores/TelemetryJSONFilePingStore.java b/mobile/android/base/java/org/mozilla/gecko/telemetry/stores/TelemetryJSONFilePingStore.java
deleted file mode 100644
index d52382146..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/stores/TelemetryJSONFilePingStore.java
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, you can obtain one at http://mozilla.org/MPL/2.0/.
- */
-
-package org.mozilla.gecko.telemetry.stores;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.support.annotation.VisibleForTesting;
-import android.support.annotation.WorkerThread;
-import android.util.Log;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-import org.mozilla.gecko.telemetry.TelemetryPing;
-import org.mozilla.gecko.util.FileUtils;
-import org.mozilla.gecko.util.FileUtils.FileLastModifiedComparator;
-import org.mozilla.gecko.util.FileUtils.FilenameRegexFilter;
-import org.mozilla.gecko.util.FileUtils.FilenameWhitelistFilter;
-import org.mozilla.gecko.util.StringUtils;
-import org.mozilla.gecko.util.UUIDUtil;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.nio.channels.FileLock;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-/**
- * An implementation of TelemetryPingStore that is backed by JSON files.
- *
- * This implementation seeks simplicity. Each ping to upload is stored in its own file with its doc ID
- * as the filename. The doc ID is sent with a ping to be uploaded and is expected to be returned with
- * {@link #onUploadAttemptComplete(Set)} so the associated file can be removed.
- *
- * During prune, the pings with the oldest modified time will be removed first. Different filesystems will
- * handle clock skew (e.g. manual time changes, daylight savings time, changing timezones) in different ways
- * and we accept that these modified times may not be consistent - newer data is not more important than
- * older data and the choice to delete the oldest data first is largely arbitrary so we don't care if
- * the timestamps are occasionally inconsistent.
- *
- * Using separate files for this store allows for less restrictive concurrency:
- * * requires locking: {@link #storePing(TelemetryPing)} writes a new file
- * * requires locking: {@link #getAllPings()} reads all files, including those potentially being written,
- * hence locking
- * * no locking: {@link #maybePrunePings()} deletes the least recently written pings, none of which should
- * be currently written
- * * no locking: {@link #onUploadAttemptComplete(Set)} deletes the given pings, none of which should be
- * currently written
- */
-public class TelemetryJSONFilePingStore extends TelemetryPingStore {
- private static final String LOGTAG = StringUtils.safeSubstring(
- "Gecko" + TelemetryJSONFilePingStore.class.getSimpleName(), 0, 23);
-
- @VisibleForTesting static final int MAX_PING_COUNT = 40; // TODO: value.
-
- // We keep the key names short to reduce storage size impact.
- @VisibleForTesting static final String KEY_PAYLOAD = "p";
- @VisibleForTesting static final String KEY_URL_PATH = "u";
-
- private final File storeDir;
- private final FilenameFilter uuidFilenameFilter;
- private final FileLastModifiedComparator fileLastModifiedComparator = new FileLastModifiedComparator();
-
- @WorkerThread // Writes to disk
- public TelemetryJSONFilePingStore(final File storeDir, final String profileName) {
- super(profileName);
- if (storeDir.exists() && !storeDir.isDirectory()) {
- // An alternative is to create a new directory, but we wouldn't
- // be able to access it later so it's better to throw.
- throw new IllegalStateException("Store dir unexpectedly exists & is not a directory - cannot continue");
- }
-
- this.storeDir = storeDir;
- this.storeDir.mkdirs();
- uuidFilenameFilter = new FilenameRegexFilter(UUIDUtil.UUID_PATTERN);
-
- if (!this.storeDir.canRead() || !this.storeDir.canWrite() || !this.storeDir.canExecute()) {
- throw new IllegalStateException("Cannot read, write, or execute store dir: " +
- this.storeDir.canRead() + " " + this.storeDir.canWrite() + " " + this.storeDir.canExecute());
- }
- }
-
- @VisibleForTesting File getPingFile(final String docID) {
- return new File(storeDir, docID);
- }
-
- @Override
- public void storePing(final TelemetryPing ping) throws IOException {
- final String output;
- try {
- output = new JSONObject()
- .put(KEY_PAYLOAD, ping.getPayload())
- .put(KEY_URL_PATH, ping.getURLPath())
- .toString();
- } catch (final JSONException e) {
- // Do not log the exception to avoid leaking personal data.
- throw new IOException("Unable to create JSON to store to disk");
- }
-
- final FileOutputStream outputStream = new FileOutputStream(getPingFile(ping.getDocID()), false);
- blockForLockAndWriteFileAndCloseStream(outputStream, output);
- }
-
- @Override
- public void maybePrunePings() {
- final File[] files = storeDir.listFiles(uuidFilenameFilter);
- if (files == null) {
- return;
- }
-
- if (files.length < MAX_PING_COUNT) {
- return;
- }
-
- // It's possible that multiple files will have the same timestamp: in this case they are treated
- // as equal by the fileLastModifiedComparator. We therefore have to use a sorted list (as
- // opposed to a set, or map).
- final ArrayList<File> sortedFiles = new ArrayList<>(Arrays.asList(files));
- Collections.sort(sortedFiles, fileLastModifiedComparator);
- deleteSmallestFiles(sortedFiles, files.length - MAX_PING_COUNT);
- }
-
- private void deleteSmallestFiles(final ArrayList<File> files, final int numFilesToRemove) {
- final Iterator<File> it = files.iterator();
- int i = 0;
-
- while (i < numFilesToRemove) {
- i += 1;
-
- // Sorted list so we're iterating over ascending files.
- final File file = it.next(); // file count > files to remove so this should not throw.
- file.delete();
- }
- }
-
- @Override
- public ArrayList<TelemetryPing> getAllPings() {
- final File[] fileArray = storeDir.listFiles(uuidFilenameFilter);
- if (fileArray == null) {
- // Intentionally don't log all info for the store directory to prevent leaking the path.
- Log.w(LOGTAG, "listFiles unexpectedly returned null - unable to retrieve pings. Debug: exists? " +
- storeDir.exists() + "; directory? " + storeDir.isDirectory());
- return new ArrayList<>(1);
- }
-
- final List<File> files = Arrays.asList(fileArray);
- Collections.sort(files, fileLastModifiedComparator); // oldest to newest
- final ArrayList<TelemetryPing> out = new ArrayList<>(files.size());
- for (final File file : files) {
- final JSONObject obj = lockAndReadJSONFromFile(file);
- if (obj == null) {
- // We log in the method to get the JSONObject if we return null.
- continue;
- }
-
- try {
- final String url = obj.getString(KEY_URL_PATH);
- final ExtendedJSONObject payload = new ExtendedJSONObject(obj.getString(KEY_PAYLOAD));
- out.add(new TelemetryPing(url, payload, file.getName()));
- } catch (final IOException | JSONException | NonObjectJSONException e) {
- Log.w(LOGTAG, "Bad json in ping. Ignoring.");
- continue;
- }
- }
- return out;
- }
-
- /**
- * Logs if there is an error.
- *
- * @return the JSON object from the given file or null if there is an error.
- */
- private JSONObject lockAndReadJSONFromFile(final File file) {
- // lockAndReadFileAndCloseStream doesn't handle file size of 0.
- if (file.length() == 0) {
- Log.w(LOGTAG, "Unexpected empty file: " + file.getName() + ". Ignoring");
- return null;
- }
-
- final FileInputStream inputStream;
- try {
- inputStream = new FileInputStream(file);
- } catch (final FileNotFoundException e) {
- // permission problem might also cause same exception. To get more debug information.
- String fileInfo = String.format("existence: %b, can write: %b, size: %d.",
- file.exists(), file.canWrite(), file.length());
- String msg = String.format(
- "Expected file to exist but got exception in thread: %s. File info - %s",
- Thread.currentThread().getName(), fileInfo);
- throw new IllegalStateException(msg);
- }
-
- final JSONObject obj;
- try {
- // Potential optimization: re-use the same buffer for reading from files.
- obj = lockAndReadFileAndCloseStream(inputStream, (int) file.length());
- } catch (final IOException | JSONException e) {
- // We couldn't read this file so let's just skip it. These potentially
- // corrupted files should be removed when the data is pruned.
- Log.w(LOGTAG, "Error when reading file: " + file.getName() + " Likely corrupted. Ignoring");
- return null;
- }
-
- if (obj == null) {
- Log.d(LOGTAG, "Could not read given file: " + file.getName() + " File is locked. Ignoring");
- }
- return obj;
- }
-
- @Override
- public void onUploadAttemptComplete(final Set<String> successfulRemoveIDs) {
- if (successfulRemoveIDs.isEmpty()) {
- return;
- }
-
- final File[] files = storeDir.listFiles(new FilenameWhitelistFilter(successfulRemoveIDs));
- for (final File file : files) {
- file.delete();
- }
- }
-
- /**
- * Locks the given {@link FileOutputStream} and writes the given String. This method will close the given stream.
- *
- * Note: this method blocks until a file lock can be acquired.
- */
- private static void blockForLockAndWriteFileAndCloseStream(final FileOutputStream outputStream, final String str)
- throws IOException {
- try {
- final FileLock lock = outputStream.getChannel().lock(0, Long.MAX_VALUE, false);
- if (lock != null) {
- // The file lock is released when the stream is closed. If we try to redundantly close it, we get
- // a ClosedChannelException. To be safe, we could catch that every time but there is a performance
- // hit to exception handling so instead we assume the file lock will be closed.
- FileUtils.writeStringToOutputStreamAndCloseStream(outputStream, str);
- }
- } finally {
- outputStream.close(); // redundant: closed when the stream is closed, but let's be safe.
- }
- }
-
- /**
- * Locks the given {@link FileInputStream} and reads the data. This method will close the given stream.
- *
- * Note: this method returns null when a lock could not be acquired.
- */
- private static JSONObject lockAndReadFileAndCloseStream(final FileInputStream inputStream, final int fileSize)
- throws IOException, JSONException {
- try {
- final FileLock lock = inputStream.getChannel().tryLock(0, Long.MAX_VALUE, true); // null when lock not acquired
- if (lock == null) {
- return null;
- }
- // The file lock is released when the stream is closed. If we try to redundantly close it, we get
- // a ClosedChannelException. To be safe, we could catch that every time but there is a performance
- // hit to exception handling so instead we assume the file lock will be closed.
- return new JSONObject(FileUtils.readStringFromInputStreamAndCloseStream(inputStream, fileSize));
- } finally {
- inputStream.close(); // redundant: closed when the stream is closed, but let's be safe.
- }
- }
-
- public static final Parcelable.Creator<TelemetryJSONFilePingStore> CREATOR = new Parcelable.Creator<TelemetryJSONFilePingStore>() {
- @Override
- public TelemetryJSONFilePingStore createFromParcel(final Parcel source) {
- final String storeDirPath = source.readString();
- final String profileName = source.readString();
- return new TelemetryJSONFilePingStore(new File(storeDirPath), profileName);
- }
-
- @Override
- public TelemetryJSONFilePingStore[] newArray(final int size) {
- return new TelemetryJSONFilePingStore[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(final Parcel dest, final int flags) {
- dest.writeString(storeDir.getAbsolutePath());
- dest.writeString(getProfileName());
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/telemetry/stores/TelemetryPingStore.java b/mobile/android/base/java/org/mozilla/gecko/telemetry/stores/TelemetryPingStore.java
deleted file mode 100644
index 7d781cf26..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/telemetry/stores/TelemetryPingStore.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, you can obtain one at http://mozilla.org/MPL/2.0/.
- */
-
-package org.mozilla.gecko.telemetry.stores;
-
-import android.os.Parcelable;
-import org.mozilla.gecko.telemetry.TelemetryPing;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Persistent storage for TelemetryPings that are queued for upload.
- *
- * An implementation of this class is expected to be thread-safe. Additionally,
- * multiple instances can be created and run simultaneously so they must be able
- * to synchronize state (or be stateless!).
- *
- * The pings in {@link #getAllPings()} and {@link #maybePrunePings()} are returned in the
- * same order in order to guarantee consistent results.
- */
-public abstract class TelemetryPingStore implements Parcelable {
- private final String profileName;
-
- public TelemetryPingStore(final String profileName) {
- this.profileName = profileName;
- }
-
- /**
- * @return the profile name associated with this store.
- */
- public String getProfileName() {
- return profileName;
- }
-
- /**
- * @return a list of all the telemetry pings in the store that are ready for upload, ascending oldest to newest.
- */
- public abstract List<TelemetryPing> getAllPings();
-
- /**
- * Save a ping to the store.
- *
- * @param ping the ping to store
- * @throws IOException for underlying store access errors
- */
- public abstract void storePing(TelemetryPing ping) throws IOException;
-
- /**
- * Removes telemetry pings from the store if there are too many pings or they take up too much space.
- * Pings should be removed from oldest to newest.
- */
- public abstract void maybePrunePings();
-
- /**
- * Removes the successfully uploaded pings from the database and performs another other actions necessary
- * for when upload is completed.
- *
- * @param successfulRemoveIDs doc ids of pings that were successfully uploaded
- */
- public abstract void onUploadAttemptComplete(Set<String> successfulRemoveIDs);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/text/FloatingActionModeCallback.java b/mobile/android/base/java/org/mozilla/gecko/text/FloatingActionModeCallback.java
deleted file mode 100644
index 07f17590d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/text/FloatingActionModeCallback.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.text;
-
-import android.annotation.TargetApi;
-import android.graphics.Rect;
-import android.os.Build;
-import android.view.ActionMode;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-
-import org.mozilla.gecko.GeckoAppShell;
-
-import java.util.List;
-
-@TargetApi(Build.VERSION_CODES.M)
-public class FloatingActionModeCallback extends ActionMode.Callback2 {
- private FloatingToolbarTextSelection textSelection;
- private List<TextAction> actions;
-
- public FloatingActionModeCallback(FloatingToolbarTextSelection textSelection, List<TextAction> actions) {
- this.textSelection = textSelection;
- this.actions = actions;
- }
-
- public void updateActions(List<TextAction> actions) {
- this.actions = actions;
- }
-
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- menu.clear();
-
- for (int i = 0; i < actions.size(); i++) {
- final TextAction action = actions.get(i);
- menu.add(Menu.NONE, i, action.getFloatingOrder(), action.getLabel());
- }
-
- return true;
- }
-
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- final TextAction action = actions.get(item.getItemId());
-
- GeckoAppShell.notifyObservers("TextSelection:Action", action.getId());
-
- return true;
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {}
-
- @Override
- public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
- final Rect contentRect = textSelection.contentRect;
- if (contentRect != null) {
- outRect.set(contentRect);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java b/mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java
deleted file mode 100644
index 7a09624d4..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.text;
-
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.graphics.Rect;
-import android.os.Build;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.ActionMode;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.gfx.LayerView;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.util.List;
-
-import ch.boye.httpclientandroidlib.util.TextUtils;
-
-/**
- * Floating toolbar for text selection actions. Only on Android 6+.
- */
-@TargetApi(Build.VERSION_CODES.M)
-public class FloatingToolbarTextSelection implements TextSelection, GeckoEventListener {
- private static final String LOGTAG = "GeckoFloatTextSelection";
-
- // This is an additional offset we add to the height of the selection. This will avoid that the
- // floating toolbar overlays the bottom handle(s).
- private static final int HANDLES_OFFSET_DP = 20;
-
- private final Activity activity;
- private final LayerView layerView;
- private final int[] locationInWindow;
- private final float handlesOffset;
-
- private ActionMode actionMode;
- private FloatingActionModeCallback actionModeCallback;
- private String selectionID;
- /* package-private */ Rect contentRect;
-
- public FloatingToolbarTextSelection(Activity activity, LayerView layerView) {
- this.activity = activity;
- this.layerView = layerView;
- this.locationInWindow = new int[2];
-
- this.handlesOffset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- HANDLES_OFFSET_DP, activity.getResources().getDisplayMetrics());
- }
-
- @Override
- public boolean dismiss() {
- if (finishActionMode()) {
- endTextSelection();
- return true;
- }
-
- return false;
- }
-
- private void endTextSelection() {
- if (TextUtils.isEmpty(selectionID)) {
- return;
- }
-
- final JSONObject args = new JSONObject();
- try {
- args.put("selectionID", selectionID);
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error building JSON arguments for TextSelection:End", e);
- return;
- }
-
- GeckoAppShell.notifyObservers("TextSelection:End", args.toString());
- }
-
- @Override
- public void create() {
- registerForEvents();
- }
-
- @Override
- public void destroy() {
- unregisterFromEvents();
- }
-
- private void registerForEvents() {
- GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
- "TextSelection:ActionbarInit",
- "TextSelection:ActionbarStatus",
- "TextSelection:ActionbarUninit",
- "TextSelection:Update",
- "TextSelection:Visibility");
- }
-
- private void unregisterFromEvents() {
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
- "TextSelection:ActionbarInit",
- "TextSelection:ActionbarStatus",
- "TextSelection:ActionbarUninit",
- "TextSelection:Update",
- "TextSelection:Visibility");
- }
-
- @Override
- public void handleMessage(final String event, final JSONObject message) {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- handleOnMainThread(event, message);
- }
- });
- }
-
- private void handleOnMainThread(final String event, final JSONObject message) {
- if ("TextSelection:ActionbarInit".equals(event)) {
- Telemetry.sendUIEvent(TelemetryContract.Event.SHOW,
- TelemetryContract.Method.CONTENT, "text_selection");
-
- selectionID = message.optString("selectionID");
- } else if ("TextSelection:ActionbarStatus".equals(event)) {
- // Ensure async updates from SearchService for example are valid.
- if (selectionID != message.optString("selectionID")) {
- return;
- }
-
- updateRect(message);
-
- if (!isRectVisible()) {
- finishActionMode();
- } else {
- startActionMode(TextAction.fromEventMessage(message));
- }
- } else if ("TextSelection:ActionbarUninit".equals(event)) {
- finishActionMode();
- } else if ("TextSelection:Update".equals(event)) {
- startActionMode(TextAction.fromEventMessage(message));
- } else if ("TextSelection:Visibility".equals(event)) {
- finishActionMode();
- }
- }
-
- private void startActionMode(List<TextAction> actions) {
- if (actionMode != null) {
- actionModeCallback.updateActions(actions);
- actionMode.invalidate();
- return;
- }
-
- actionModeCallback = new FloatingActionModeCallback(this, actions);
- actionMode = activity.startActionMode(actionModeCallback, ActionMode.TYPE_FLOATING);
- }
-
- private boolean finishActionMode() {
- if (actionMode != null) {
- actionMode.finish();
- actionMode = null;
- actionModeCallback = null;
- return true;
- }
-
- return false;
- }
-
- /**
- * If the content rect is a point (left == right and top == bottom) then this means that the
- * content rect is not in the currently visible part.
- */
- private boolean isRectVisible() {
- // There's another case of an empty rect where just left == right but not top == bottom.
- // That's the rect for a collapsed selection. While technically this rect isn't visible too
- // we are not interested in this case because we do not want to hide the toolbar.
- return contentRect.left != contentRect.right || contentRect.top != contentRect.bottom;
- }
-
- private void updateRect(JSONObject message) {
- try {
- final double x = message.getDouble("x");
- final double y = (int) message.getDouble("y");
- final double width = (int) message.getDouble("width");
- final double height = (int) message.getDouble("height");
-
- final float zoomFactor = layerView.getZoomFactor();
- layerView.getLocationInWindow(locationInWindow);
-
- contentRect = new Rect(
- (int) (x * zoomFactor + locationInWindow[0]),
- (int) (y * zoomFactor + locationInWindow[1]),
- (int) ((x + width) * zoomFactor + locationInWindow[0]),
- (int) ((y + height) * zoomFactor + locationInWindow[1] +
- (height > 0 ? handlesOffset : 0)));
- } catch (JSONException e) {
- Log.w(LOGTAG, "Could not calculate content rect", e);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/text/TextAction.java b/mobile/android/base/java/org/mozilla/gecko/text/TextAction.java
deleted file mode 100644
index 9fcbce4a4..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/text/TextAction.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.text;
-
-import android.util.Log;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Text selection action like "copy", "paste", ..
- */
-public class TextAction {
- private static final String LOGTAG = "GeckoTextAction";
-
- private String id;
- private String label;
- private int order;
- private int floatingOrder;
-
- private TextAction() {}
-
- public static List<TextAction> fromEventMessage(JSONObject message) {
- final List<TextAction> actions = new ArrayList<>();
-
- try {
- final JSONArray array = message.getJSONArray("actions");
-
- for (int i = 0; i < array.length(); i++) {
- final JSONObject object = array.getJSONObject(i);
-
- final TextAction action = new TextAction();
- action.id = object.getString("id");
- action.label = object.getString("label");
- action.order = object.getInt("order");
- action.floatingOrder = object.optInt("floatingOrder", i);
-
- actions.add(action);
- }
- } catch (JSONException e) {
- Log.w(LOGTAG, "Could not parse text actions", e);
- }
-
- return actions;
- }
-
- public String getId() {
- return id;
- }
-
- public String getLabel() {
- return label;
- }
-
- public int getOrder() {
- return order;
- }
-
- public int getFloatingOrder() {
- return floatingOrder;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/text/TextSelection.java b/mobile/android/base/java/org/mozilla/gecko/text/TextSelection.java
deleted file mode 100644
index 29e8e43f5..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/text/TextSelection.java
+++ /dev/null
@@ -1,13 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.text;
-
-public interface TextSelection {
- void create();
-
- boolean dismiss();
-
- void destroy();
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/AutocompleteHandler.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/AutocompleteHandler.java
deleted file mode 100644
index 4a1559823..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/AutocompleteHandler.java
+++ /dev/null
@@ -1,10 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-public interface AutocompleteHandler {
- void onAutocomplete(String res);
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/BackButton.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/BackButton.java
deleted file mode 100644
index 267c95e09..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/BackButton.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import android.content.Context;
-import android.graphics.Path;
-import android.util.AttributeSet;
-
-public class BackButton extends NavButton {
- public BackButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
- super.onSizeChanged(width, height, oldWidth, oldHeight);
-
- mPath.reset();
- mPath.addCircle(width / 2, height / 2, width / 2, Path.Direction.CW);
-
- mBorderPath.reset();
- mBorderPath.addCircle(width / 2, height / 2, (width / 2) - (mBorderWidth / 2), Path.Direction.CW);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
deleted file mode 100644
index b24e3b3ea..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbar.java
+++ /dev/null
@@ -1,960 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.List;
-
-import android.support.annotation.Nullable;
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.SiteIdentity;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.TouchEventInterceptor;
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
-import org.mozilla.gecko.animation.ViewHelper;
-import org.mozilla.gecko.lwt.LightweightTheme;
-import org.mozilla.gecko.lwt.LightweightThemeDrawable;
-import org.mozilla.gecko.menu.GeckoMenu;
-import org.mozilla.gecko.menu.MenuPopup;
-import org.mozilla.gecko.tabs.TabHistoryController;
-import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.OnStopListener;
-import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.OnTitleChangeListener;
-import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.UpdateFlags;
-import org.mozilla.gecko.util.Clipboard;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.MenuUtils;
-import org.mozilla.gecko.widget.themed.ThemedFrameLayout;
-import org.mozilla.gecko.widget.themed.ThemedImageButton;
-import org.mozilla.gecko.widget.themed.ThemedImageView;
-import org.mozilla.gecko.widget.themed.ThemedRelativeLayout;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.StateListDrawable;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.LayoutInflater;
-import android.view.MenuInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import android.widget.PopupWindow;
-import android.support.annotation.NonNull;
-
-/**
-* {@code BrowserToolbar} is single entry point for users of the toolbar
-* subsystem i.e. this should be the only import outside the 'toolbar'
-* package.
-*
-* {@code BrowserToolbar} serves at the single event bus for all
-* sub-components in the toolbar. It tracks tab events and gecko messages
-* and update the state of its inner components accordingly.
-*
-* It has two states, display and edit, which are controlled by
-* ToolbarEditLayout and ToolbarDisplayLayout. In display state, the toolbar
-* displays the current state for the selected tab. In edit state, it shows
-* a text entry for searching bookmarks/history. {@code BrowserToolbar}
-* provides public API to enter, cancel, and commit the edit state as well
-* as a set of listeners to allow {@code BrowserToolbar} users to react
-* to state changes accordingly.
-*/
-public abstract class BrowserToolbar extends ThemedRelativeLayout
- implements Tabs.OnTabsChangedListener,
- GeckoMenu.ActionItemBarPresenter {
- private static final String LOGTAG = "GeckoToolbar";
-
- private static final int LIGHTWEIGHT_THEME_INVERT_ALPHA = 34; // 255 - alpha = invert_alpha
-
- public interface OnActivateListener {
- public void onActivate();
- }
-
- public interface OnCommitListener {
- public void onCommit();
- }
-
- public interface OnDismissListener {
- public void onDismiss();
- }
-
- public interface OnFilterListener {
- public void onFilter(String searchText, AutocompleteHandler handler);
- }
-
- public interface OnStartEditingListener {
- public void onStartEditing();
- }
-
- public interface OnStopEditingListener {
- public void onStopEditing();
- }
-
- protected enum UIMode {
- EDIT,
- DISPLAY
- }
-
- protected final ToolbarDisplayLayout urlDisplayLayout;
- protected final ToolbarEditLayout urlEditLayout;
- protected final View urlBarEntry;
- protected boolean isSwitchingTabs;
- protected final ThemedImageButton tabsButton;
-
- private ToolbarProgressView progressBar;
- protected final TabCounter tabsCounter;
- protected final ThemedFrameLayout menuButton;
- protected final ThemedImageView menuIcon;
- private MenuPopup menuPopup;
- protected final List<View> focusOrder;
-
- private OnActivateListener activateListener;
- private OnFocusChangeListener focusChangeListener;
- private OnStartEditingListener startEditingListener;
- private OnStopEditingListener stopEditingListener;
- private TouchEventInterceptor mTouchEventInterceptor;
-
- protected final BrowserApp activity;
-
- protected UIMode uiMode;
- protected TabHistoryController tabHistoryController;
-
- private final Paint shadowPaint;
- private final int shadowColor;
- private final int shadowPrivateColor;
- private final int shadowSize;
-
- private final ToolbarPrefs prefs;
-
- public abstract boolean isAnimating();
-
- protected abstract boolean isTabsButtonOffscreen();
-
- protected abstract void updateNavigationButtons(Tab tab);
-
- protected abstract void triggerStartEditingTransition(PropertyAnimator animator);
- protected abstract void triggerStopEditingTransition();
- public abstract void triggerTabsPanelTransition(PropertyAnimator animator, boolean areTabsShown);
-
- /**
- * Returns a Drawable overlaid with the theme's bitmap.
- */
- protected Drawable getLWTDefaultStateSetDrawable() {
- return getTheme().getDrawable(this);
- }
-
- public static BrowserToolbar create(final Context context, final AttributeSet attrs) {
- final boolean isLargeResource = context.getResources().getBoolean(R.bool.is_large_resource);
- final BrowserToolbar toolbar;
- if (isLargeResource) {
- toolbar = new BrowserToolbarTablet(context, attrs);
- } else {
- toolbar = new BrowserToolbarPhone(context, attrs);
- }
- return toolbar;
- }
-
- protected BrowserToolbar(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- setWillNotDraw(false);
-
- // BrowserToolbar is attached to BrowserApp only.
- activity = (BrowserApp) context;
-
- LayoutInflater.from(context).inflate(R.layout.browser_toolbar, this);
-
- Tabs.registerOnTabsChangedListener(this);
- isSwitchingTabs = true;
-
- urlDisplayLayout = (ToolbarDisplayLayout) findViewById(R.id.display_layout);
- urlBarEntry = findViewById(R.id.url_bar_entry);
- urlEditLayout = (ToolbarEditLayout) findViewById(R.id.edit_layout);
-
- tabsButton = (ThemedImageButton) findViewById(R.id.tabs);
- tabsCounter = (TabCounter) findViewById(R.id.tabs_counter);
- tabsCounter.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
-
- menuButton = (ThemedFrameLayout) findViewById(R.id.menu);
- menuIcon = (ThemedImageView) findViewById(R.id.menu_icon);
-
- // The focusOrder List should be filled by sub-classes.
- focusOrder = new ArrayList<View>();
-
- final Resources res = getResources();
- shadowSize = res.getDimensionPixelSize(R.dimen.browser_toolbar_shadow_size);
-
- shadowPaint = new Paint();
- shadowColor = ContextCompat.getColor(context, R.color.url_bar_shadow);
- shadowPrivateColor = ContextCompat.getColor(context, R.color.url_bar_shadow_private);
- shadowPaint.setColor(shadowColor);
- shadowPaint.setStrokeWidth(0.0f);
-
- setUIMode(UIMode.DISPLAY);
-
- prefs = new ToolbarPrefs();
- urlDisplayLayout.setToolbarPrefs(prefs);
- urlEditLayout.setToolbarPrefs(prefs);
-
- setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
- @Override
- public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
- // Do not show the context menu while editing
- if (isEditing()) {
- return;
- }
-
- // NOTE: Use MenuUtils.safeSetVisible because some actions might
- // be on the Page menu
- MenuInflater inflater = activity.getMenuInflater();
- inflater.inflate(R.menu.titlebar_contextmenu, menu);
-
- String clipboard = Clipboard.getText();
- if (TextUtils.isEmpty(clipboard)) {
- menu.findItem(R.id.pasteandgo).setVisible(false);
- menu.findItem(R.id.paste).setVisible(false);
- }
-
- Tab tab = Tabs.getInstance().getSelectedTab();
- if (tab != null) {
- String url = tab.getURL();
- if (url == null) {
- menu.findItem(R.id.copyurl).setVisible(false);
- menu.findItem(R.id.add_to_launcher).setVisible(false);
- }
-
- MenuUtils.safeSetVisible(menu, R.id.subscribe, tab.hasFeeds());
- MenuUtils.safeSetVisible(menu, R.id.add_search_engine, tab.hasOpenSearch());
- } else {
- // if there is no tab, remove anything tab dependent
- menu.findItem(R.id.copyurl).setVisible(false);
- menu.findItem(R.id.add_to_launcher).setVisible(false);
- MenuUtils.safeSetVisible(menu, R.id.subscribe, false);
- MenuUtils.safeSetVisible(menu, R.id.add_search_engine, false);
- }
- }
- });
-
- setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (activateListener != null) {
- activateListener.onActivate();
- }
- }
- });
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- prefs.open();
-
- urlDisplayLayout.setOnStopListener(new OnStopListener() {
- @Override
- public Tab onStop() {
- final Tab tab = Tabs.getInstance().getSelectedTab();
- if (tab != null) {
- tab.doStop();
- return tab;
- }
-
- return null;
- }
- });
-
- urlDisplayLayout.setOnTitleChangeListener(new OnTitleChangeListener() {
- @Override
- public void onTitleChange(CharSequence title) {
- final String contentDescription;
- if (title != null) {
- contentDescription = title.toString();
- } else {
- contentDescription = activity.getString(R.string.url_bar_default_text);
- }
-
- // The title and content description should
- // always be sync.
- setContentDescription(contentDescription);
- }
- });
-
- urlEditLayout.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- // This will select the url bar when entering editing mode.
- setSelected(hasFocus);
- if (focusChangeListener != null) {
- focusChangeListener.onFocusChange(v, hasFocus);
- }
- }
- });
-
- tabsButton.setOnClickListener(new Button.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Clear focus so a back press with the tabs
- // panel open does not go to the editing field.
- urlEditLayout.clearFocus();
-
- toggleTabs();
- }
- });
- tabsButton.setImageLevel(0);
-
- menuButton.setOnClickListener(new Button.OnClickListener() {
- @Override
- public void onClick(View view) {
- // Drop the soft keyboard.
- urlEditLayout.clearFocus();
- activity.openOptionsMenu();
- }
- });
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- prefs.close();
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
-
- final int height = getHeight();
- canvas.drawRect(0, height - shadowSize, getWidth(), height, shadowPaint);
- }
-
- public void onParentFocus() {
- urlEditLayout.onParentFocus();
- }
-
- public void setProgressBar(ToolbarProgressView progressBar) {
- this.progressBar = progressBar;
- }
-
- public void setTabHistoryController(TabHistoryController tabHistoryController) {
- this.tabHistoryController = tabHistoryController;
- }
-
- public void refresh() {
- urlDisplayLayout.dismissSiteIdentityPopup();
- }
-
- public boolean onBackPressed() {
- // If we exit editing mode during the animation,
- // we're put into an inconsistent state (bug 1017276).
- if (isEditing() && !isAnimating()) {
- Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL,
- TelemetryContract.Method.BACK);
- cancelEdit();
- return true;
- }
-
- return urlDisplayLayout.dismissSiteIdentityPopup();
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
-
- if (h != oldh) {
- // Post this to happen outside of onSizeChanged, as this may cause
- // a layout change and relayouts within a layout change don't work.
- post(new Runnable() {
- @Override
- public void run() {
- activity.refreshToolbarHeight();
- }
- });
- }
- }
-
- public void saveTabEditingState(final TabEditingState editingState) {
- urlEditLayout.saveTabEditingState(editingState);
- }
-
- public void restoreTabEditingState(final TabEditingState editingState) {
- if (!isEditing()) {
- throw new IllegalStateException("Expected to be editing");
- }
-
- urlEditLayout.restoreTabEditingState(editingState);
- }
-
- @Override
- public void onTabChanged(@Nullable Tab tab, Tabs.TabEvents msg, String data) {
- Log.d(LOGTAG, "onTabChanged: " + msg);
- final Tabs tabs = Tabs.getInstance();
-
- // These conditions are split into three phases:
- // * Always do first
- // * Handling specific to the selected tab
- // * Always do afterwards.
-
- switch (msg) {
- case ADDED:
- case CLOSED:
- updateTabCount(tabs.getDisplayCount());
- break;
- case RESTORED:
- // TabCount fixup after OOM
- case SELECTED:
- urlDisplayLayout.dismissSiteIdentityPopup();
- updateTabCount(tabs.getDisplayCount());
- isSwitchingTabs = true;
- break;
- }
-
- if (tabs.isSelectedTab(tab)) {
- final EnumSet<UpdateFlags> flags = EnumSet.noneOf(UpdateFlags.class);
-
- // Progress-related handling
- switch (msg) {
- case START:
- updateProgressVisibility(tab, Tab.LOAD_PROGRESS_INIT);
- // Fall through.
- case ADDED:
- case LOCATION_CHANGE:
- case LOAD_ERROR:
- case LOADED:
- case STOP:
- flags.add(UpdateFlags.PROGRESS);
- if (progressBar.getVisibility() == View.VISIBLE) {
- progressBar.animateProgress(tab.getLoadProgress());
- }
- break;
-
- case SELECTED:
- flags.add(UpdateFlags.PROGRESS);
- updateProgressVisibility();
- break;
- }
-
- switch (msg) {
- case STOP:
- // Reset the title in case we haven't navigated
- // to a new page yet.
- flags.add(UpdateFlags.TITLE);
- // Fall through.
- case START:
- case CLOSED:
- case ADDED:
- updateNavigationButtons(tab);
- break;
-
- case SELECTED:
- flags.add(UpdateFlags.PRIVATE_MODE);
- setPrivateMode(tab.isPrivate());
- // Fall through.
- case LOAD_ERROR:
- case LOCATION_CHANGE:
- // We're displaying the tab URL in place of the title,
- // so we always need to update our "title" here as well.
- flags.add(UpdateFlags.TITLE);
- flags.add(UpdateFlags.FAVICON);
- flags.add(UpdateFlags.SITE_IDENTITY);
-
- updateNavigationButtons(tab);
- break;
-
- case TITLE:
- flags.add(UpdateFlags.TITLE);
- break;
-
- case FAVICON:
- flags.add(UpdateFlags.FAVICON);
- break;
-
- case SECURITY_CHANGE:
- flags.add(UpdateFlags.SITE_IDENTITY);
- break;
- }
-
- if (!flags.isEmpty() && tab != null) {
- updateDisplayLayout(tab, flags);
- }
- }
-
- switch (msg) {
- case SELECTED:
- case LOAD_ERROR:
- case LOCATION_CHANGE:
- isSwitchingTabs = false;
- }
- }
-
- private void updateProgressVisibility() {
- final Tab selectedTab = Tabs.getInstance().getSelectedTab();
- // The selected tab may be null if GeckoApp (and thus the
- // selected tab) are not yet initialized (bug 1090287).
- if (selectedTab != null) {
- updateProgressVisibility(selectedTab, selectedTab.getLoadProgress());
- }
- }
-
- private void updateProgressVisibility(Tab selectedTab, int progress) {
- if (!isEditing() && selectedTab.getState() == Tab.STATE_LOADING) {
- progressBar.setProgress(progress);
- progressBar.setPrivateMode(selectedTab.isPrivate());
- progressBar.setVisibility(View.VISIBLE);
- } else {
- progressBar.setVisibility(View.GONE);
- }
- }
-
- protected boolean isVisible() {
- return ViewHelper.getTranslationY(this) == 0;
- }
-
- @Override
- public void setNextFocusDownId(int nextId) {
- super.setNextFocusDownId(nextId);
- tabsButton.setNextFocusDownId(nextId);
- urlDisplayLayout.setNextFocusDownId(nextId);
- menuButton.setNextFocusDownId(nextId);
- }
-
- public boolean hideVirtualKeyboard() {
- InputMethodManager imm =
- (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
- return imm.hideSoftInputFromWindow(tabsButton.getWindowToken(), 0);
- }
-
- private void showSelectedTabs() {
- Tab tab = Tabs.getInstance().getSelectedTab();
- if (tab != null) {
- if (!tab.isPrivate())
- activity.showNormalTabs();
- else
- activity.showPrivateTabs();
- }
- }
-
- private void toggleTabs() {
- if (activity.areTabsShown()) {
- return;
- }
-
- if (hideVirtualKeyboard()) {
- getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- getViewTreeObserver().removeGlobalOnLayoutListener(this);
- showSelectedTabs();
- }
- });
- } else {
- showSelectedTabs();
- }
- }
-
- protected void updateTabCount(final int count) {
- // If toolbar is in edit mode on a phone, this means the entry is expanded
- // and the tabs button is translated offscreen. Don't trigger tabs counter
- // updates until the tabs button is back on screen.
- // See stopEditing()
- if (isTabsButtonOffscreen()) {
- return;
- }
-
- // Set TabCounter based on visibility
- if (isVisible() && ViewHelper.getAlpha(tabsCounter) != 0 && !isEditing()) {
- tabsCounter.setCountWithAnimation(count);
- } else {
- tabsCounter.setCount(count);
- }
-
- // Update A11y information
- tabsButton.setContentDescription((count > 1) ?
- activity.getString(R.string.num_tabs, count) :
- activity.getString(R.string.one_tab));
- }
-
- private void updateDisplayLayout(@NonNull Tab tab, EnumSet<UpdateFlags> flags) {
- if (isSwitchingTabs) {
- flags.add(UpdateFlags.DISABLE_ANIMATIONS);
- }
-
- urlDisplayLayout.updateFromTab(tab, flags);
-
- if (flags.contains(UpdateFlags.TITLE)) {
- if (!isEditing()) {
- urlEditLayout.setText(tab.getURL());
- }
- }
-
- if (flags.contains(UpdateFlags.PROGRESS)) {
- updateFocusOrder();
- }
- }
-
- private void updateFocusOrder() {
- if (focusOrder.size() == 0) {
- throw new IllegalStateException("Expected focusOrder to be initialized in subclass");
- }
-
- View prevView = null;
-
- // If the element that has focus becomes disabled or invisible, focus
- // is given to the URL bar.
- boolean needsNewFocus = false;
-
- for (View view : focusOrder) {
- if (view.getVisibility() != View.VISIBLE || !view.isEnabled()) {
- if (view.hasFocus()) {
- needsNewFocus = true;
- }
- continue;
- }
-
- if (view.getId() == R.id.menu_items) {
- final LinearLayout actionItemBar = (LinearLayout) view;
- final int childCount = actionItemBar.getChildCount();
- for (int child = 0; child < childCount; child++) {
- View childView = actionItemBar.getChildAt(child);
- if (prevView != null) {
- childView.setNextFocusLeftId(prevView.getId());
- prevView.setNextFocusRightId(childView.getId());
- }
- prevView = childView;
- }
- } else {
- if (prevView != null) {
- view.setNextFocusLeftId(prevView.getId());
- prevView.setNextFocusRightId(view.getId());
- }
- prevView = view;
- }
- }
-
- if (needsNewFocus) {
- requestFocus();
- }
- }
-
- public void setToolBarButtonsAlpha(float alpha) {
- ViewHelper.setAlpha(tabsCounter, alpha);
- if (!HardwareUtils.isTablet()) {
- ViewHelper.setAlpha(menuIcon, alpha);
- }
- }
-
- public void onEditSuggestion(String suggestion) {
- if (!isEditing()) {
- return;
- }
-
- urlEditLayout.onEditSuggestion(suggestion);
- }
-
- public void setTitle(CharSequence title) {
- urlDisplayLayout.setTitle(title);
- }
-
- public void setOnActivateListener(final OnActivateListener listener) {
- activateListener = listener;
- }
-
- public void setOnCommitListener(OnCommitListener listener) {
- urlEditLayout.setOnCommitListener(listener);
- }
-
- public void setOnDismissListener(OnDismissListener listener) {
- urlEditLayout.setOnDismissListener(listener);
- }
-
- public void setOnFilterListener(OnFilterListener listener) {
- urlEditLayout.setOnFilterListener(listener);
- }
-
- @Override
- public void setOnFocusChangeListener(OnFocusChangeListener listener) {
- focusChangeListener = listener;
- }
-
- public void setOnStartEditingListener(OnStartEditingListener listener) {
- startEditingListener = listener;
- }
-
- public void setOnStopEditingListener(OnStopEditingListener listener) {
- stopEditingListener = listener;
- }
-
- protected void showUrlEditLayout() {
- setUrlEditLayoutVisibility(true, null);
- }
-
- protected void showUrlEditLayout(final PropertyAnimator animator) {
- setUrlEditLayoutVisibility(true, animator);
- }
-
- protected void hideUrlEditLayout() {
- setUrlEditLayoutVisibility(false, null);
- }
-
- protected void hideUrlEditLayout(final PropertyAnimator animator) {
- setUrlEditLayoutVisibility(false, animator);
- }
-
- protected void setUrlEditLayoutVisibility(final boolean showEditLayout, PropertyAnimator animator) {
- if (showEditLayout) {
- urlEditLayout.prepareShowAnimation(animator);
- }
-
- // If this view is GONE, we trigger a measure pass when setting the view to
- // VISIBLE. Since this will occur during the toolbar open animation, it causes jank.
- final int hiddenViewVisibility = View.INVISIBLE;
-
- if (animator == null) {
- final View viewToShow = (showEditLayout ? urlEditLayout : urlDisplayLayout);
- final View viewToHide = (showEditLayout ? urlDisplayLayout : urlEditLayout);
-
- viewToHide.setVisibility(hiddenViewVisibility);
- viewToShow.setVisibility(View.VISIBLE);
- return;
- }
-
- animator.addPropertyAnimationListener(new PropertyAnimationListener() {
- @Override
- public void onPropertyAnimationStart() {
- if (!showEditLayout) {
- urlEditLayout.setVisibility(hiddenViewVisibility);
- urlDisplayLayout.setVisibility(View.VISIBLE);
- }
- }
-
- @Override
- public void onPropertyAnimationEnd() {
- if (showEditLayout) {
- urlDisplayLayout.setVisibility(hiddenViewVisibility);
- urlEditLayout.setVisibility(View.VISIBLE);
- }
- }
- });
- }
-
- private void setUIMode(final UIMode uiMode) {
- this.uiMode = uiMode;
- urlEditLayout.setEnabled(uiMode == UIMode.EDIT);
- }
-
- /**
- * Returns whether or not the URL bar is in editing mode (url bar is expanded, hiding the new
- * tab button). Note that selection state is independent of editing mode.
- */
- public boolean isEditing() {
- return (uiMode == UIMode.EDIT);
- }
-
- public void startEditing(String url, PropertyAnimator animator) {
- if (isEditing()) {
- return;
- }
-
- urlEditLayout.setText(url != null ? url : "");
-
- setUIMode(UIMode.EDIT);
-
- updateProgressVisibility();
-
- if (startEditingListener != null) {
- startEditingListener.onStartEditing();
- }
-
- triggerStartEditingTransition(animator);
- }
-
- /**
- * Exits edit mode without updating the toolbar title.
- *
- * @return the url that was entered
- */
- public String cancelEdit() {
- Telemetry.stopUISession(TelemetryContract.Session.AWESOMESCREEN);
- return stopEditing();
- }
-
- /**
- * Exits edit mode, updating the toolbar title with the url that was just entered.
- *
- * @return the url that was entered
- */
- public String commitEdit() {
- Tab tab = Tabs.getInstance().getSelectedTab();
- if (tab != null) {
- tab.resetSiteIdentity();
- }
-
- final String url = stopEditing();
- if (!TextUtils.isEmpty(url)) {
- setTitle(url);
- }
- return url;
- }
-
- private String stopEditing() {
- final String url = urlEditLayout.getText();
- if (!isEditing()) {
- return url;
- }
- setUIMode(UIMode.DISPLAY);
-
- if (stopEditingListener != null) {
- stopEditingListener.onStopEditing();
- }
-
- updateProgressVisibility();
- triggerStopEditingTransition();
-
- return url;
- }
-
- @Override
- public void setPrivateMode(boolean isPrivate) {
- super.setPrivateMode(isPrivate);
-
- tabsButton.setPrivateMode(isPrivate);
- menuButton.setPrivateMode(isPrivate);
- urlEditLayout.setPrivateMode(isPrivate);
-
- shadowPaint.setColor(isPrivate ? shadowPrivateColor : shadowColor);
- }
-
- public void show() {
- setVisibility(View.VISIBLE);
- }
-
- public void hide() {
- setVisibility(View.GONE);
- }
-
- public View getDoorHangerAnchor() {
- return urlDisplayLayout;
- }
-
- public void onDestroy() {
- Tabs.unregisterOnTabsChangedListener(this);
- urlDisplayLayout.destroy();
- }
-
- public boolean openOptionsMenu() {
- // Initialize the popup.
- if (menuPopup == null) {
- View panel = activity.getMenuPanel();
- menuPopup = new MenuPopup(activity);
- menuPopup.setPanelView(panel);
-
- menuPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
- @Override
- public void onDismiss() {
- activity.onOptionsMenuClosed(null);
- }
- });
- }
-
- GeckoAppShell.getGeckoInterface().invalidateOptionsMenu();
- if (!menuPopup.isShowing()) {
- menuPopup.showAsDropDown(menuButton);
- }
-
- return true;
- }
-
- public boolean closeOptionsMenu() {
- if (menuPopup != null && menuPopup.isShowing()) {
- menuPopup.dismiss();
- }
-
- return true;
- }
-
- @Override
- public void onLightweightThemeChanged() {
- final Drawable drawable = getLWTDefaultStateSetDrawable();
- if (drawable == null) {
- return;
- }
-
- final StateListDrawable stateList = new StateListDrawable();
- stateList.addState(PRIVATE_STATE_SET, getColorDrawable(R.color.tabs_tray_grey_pressed));
- stateList.addState(EMPTY_STATE_SET, drawable);
-
- setBackgroundDrawable(stateList);
- }
-
- public void setTouchEventInterceptor(TouchEventInterceptor interceptor) {
- mTouchEventInterceptor = interceptor;
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- if (mTouchEventInterceptor != null && mTouchEventInterceptor.onInterceptTouchEvent(this, event)) {
- return true;
- }
- return super.onInterceptTouchEvent(event);
- }
-
- @Override
- public void onLightweightThemeReset() {
- setBackgroundResource(R.drawable.url_bar_bg);
- }
-
- public static LightweightThemeDrawable getLightweightThemeDrawable(final View view,
- final LightweightTheme theme, final int colorResID) {
- final int color = ContextCompat.getColor(view.getContext(), colorResID);
-
- final LightweightThemeDrawable drawable = theme.getColorDrawable(view, color);
- if (drawable != null) {
- drawable.setAlpha(LIGHTWEIGHT_THEME_INVERT_ALPHA, LIGHTWEIGHT_THEME_INVERT_ALPHA);
- }
-
- return drawable;
- }
-
- public static class TabEditingState {
- // The edited text from the most recent time this tab was unselected.
- protected String lastEditingText;
- protected int selectionStart;
- protected int selectionEnd;
-
- public boolean isBrowserSearchShown;
-
- public void copyFrom(final TabEditingState s2) {
- lastEditingText = s2.lastEditingText;
- selectionStart = s2.selectionStart;
- selectionEnd = s2.selectionEnd;
-
- isBrowserSearchShown = s2.isBrowserSearchShown;
- }
-
- public boolean isBrowserSearchShown() {
- return isBrowserSearchShown;
- }
-
- public void setIsBrowserSearchShown(final boolean isShown) {
- isBrowserSearchShown = isShown;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbarPhone.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbarPhone.java
deleted file mode 100644
index a5fc57f1a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbarPhone.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
-import org.mozilla.gecko.util.HardwareUtils;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-
-/**
- * A toolbar implementation for phones.
- */
-class BrowserToolbarPhone extends BrowserToolbarPhoneBase {
-
- private final PropertyAnimationListener showEditingListener;
- private final PropertyAnimationListener stopEditingListener;
-
- protected boolean isAnimatingEntry;
-
- protected BrowserToolbarPhone(final Context context, final AttributeSet attrs) {
- super(context, attrs);
-
- // Create these listeners here, once, to avoid constructing new listeners
- // each time they are set on an animator (i.e. each time the url bar is clicked).
- showEditingListener = new PropertyAnimationListener() {
- @Override
- public void onPropertyAnimationStart() { /* Do nothing */ }
-
- @Override
- public void onPropertyAnimationEnd() {
- isAnimatingEntry = false;
- }
- };
-
- stopEditingListener = new PropertyAnimationListener() {
- @Override
- public void onPropertyAnimationStart() { /* Do nothing */ }
-
- @Override
- public void onPropertyAnimationEnd() {
- urlBarTranslatingEdge.setVisibility(View.INVISIBLE);
-
- final PropertyAnimator buttonsAnimator = new PropertyAnimator(300);
- urlDisplayLayout.prepareStopEditingAnimation(buttonsAnimator);
- buttonsAnimator.start();
-
- isAnimatingEntry = false;
-
- // Trigger animation to update the tabs counter once the
- // tabs button is back on screen.
- updateTabCountAndAnimate(Tabs.getInstance().getDisplayCount());
- }
- };
- }
-
- @Override
- public boolean isAnimating() {
- return isAnimatingEntry;
- }
-
- @Override
- protected void triggerStartEditingTransition(final PropertyAnimator animator) {
- if (isAnimatingEntry) {
- return;
- }
-
- // The animation looks cleaner if the text in the URL bar is
- // not selected so clear the selection by clearing focus.
- urlEditLayout.clearFocus();
-
- urlDisplayLayout.prepareStartEditingAnimation();
- addAnimationsForEditing(animator, true);
- showUrlEditLayout(animator);
- urlBarTranslatingEdge.setVisibility(View.VISIBLE);
- animator.addPropertyAnimationListener(showEditingListener);
-
- isAnimatingEntry = true; // To be correct, this should be called last.
- }
-
- @Override
- protected void triggerStopEditingTransition() {
- final PropertyAnimator animator = new PropertyAnimator(250);
- animator.setUseHardwareLayer(false);
-
- addAnimationsForEditing(animator, false);
- hideUrlEditLayout(animator);
- animator.addPropertyAnimationListener(stopEditingListener);
-
- isAnimatingEntry = true;
- animator.start();
- }
-
- private void addAnimationsForEditing(final PropertyAnimator animator, final boolean isEditing) {
- final int curveTranslation;
- final int entryTranslation;
- if (isEditing) {
- curveTranslation = getUrlBarCurveTranslation();
- entryTranslation = getUrlBarEntryTranslation();
- } else {
- curveTranslation = 0;
- entryTranslation = 0;
- }
-
- // Slide toolbar elements.
- animator.attach(urlBarTranslatingEdge,
- PropertyAnimator.Property.TRANSLATION_X,
- entryTranslation);
- animator.attach(tabsButton,
- PropertyAnimator.Property.TRANSLATION_X,
- curveTranslation);
- animator.attach(tabsCounter,
- PropertyAnimator.Property.TRANSLATION_X,
- curveTranslation);
- animator.attach(menuButton,
- PropertyAnimator.Property.TRANSLATION_X,
- curveTranslation);
- animator.attach(menuIcon,
- PropertyAnimator.Property.TRANSLATION_X,
- curveTranslation);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbarPhoneBase.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbarPhoneBase.java
deleted file mode 100644
index 5588ddcd3..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbarPhoneBase.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import java.util.Arrays;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.animation.ViewHelper;
-import org.mozilla.gecko.widget.themed.ThemedImageView;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.Interpolator;
-import android.widget.ImageView;
-
-/**
- * A base implementations of the browser toolbar for phones.
- * This class manages any Views, variables, etc. that are exclusive to phone.
- */
-abstract class BrowserToolbarPhoneBase extends BrowserToolbar {
-
- protected final ImageView urlBarTranslatingEdge;
- protected final ThemedImageView editCancel;
-
- private final Path roundCornerShape;
- private final Paint roundCornerPaint;
-
- private final Interpolator buttonsInterpolator = new AccelerateInterpolator();
-
- public BrowserToolbarPhoneBase(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- final Resources res = context.getResources();
-
- urlBarTranslatingEdge = (ImageView) findViewById(R.id.url_bar_translating_edge);
-
- // This will clip the translating edge's image at 60% of its width
- urlBarTranslatingEdge.getDrawable().setLevel(6000);
-
- editCancel = (ThemedImageView) findViewById(R.id.edit_cancel);
-
- focusOrder.add(this);
- focusOrder.addAll(urlDisplayLayout.getFocusOrder());
- focusOrder.addAll(Arrays.asList(tabsButton, menuButton));
-
- roundCornerShape = new Path();
- roundCornerShape.moveTo(0, 0);
- roundCornerShape.lineTo(30, 0);
- roundCornerShape.cubicTo(0, 0, 0, 0, 0, 30);
- roundCornerShape.lineTo(0, 0);
-
- roundCornerPaint = new Paint();
- roundCornerPaint.setAntiAlias(true);
- roundCornerPaint.setColor(ContextCompat.getColor(context, R.color.text_and_tabs_tray_grey));
- roundCornerPaint.setStrokeWidth(0.0f);
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- editCancel.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- // If we exit editing mode during the animation,
- // we're put into an inconsistent state (bug 1017276).
- if (!isAnimating()) {
- Telemetry.sendUIEvent(TelemetryContract.Event.CANCEL,
- TelemetryContract.Method.ACTIONBAR,
- getResources().getResourceEntryName(editCancel.getId()));
- cancelEdit();
- }
- }
- });
- }
-
- @Override
- public void setPrivateMode(final boolean isPrivate) {
- super.setPrivateMode(isPrivate);
- editCancel.setPrivateMode(isPrivate);
- }
-
- @Override
- protected boolean isTabsButtonOffscreen() {
- return isEditing();
- }
-
- @Override
- public boolean addActionItem(final View actionItem) {
- // We have no action item bar.
- return false;
- }
-
- @Override
- public void removeActionItem(final View actionItem) {
- // We have no action item bar.
- }
-
- @Override
- protected void updateNavigationButtons(final Tab tab) {
- // We have no navigation buttons so do nothing.
- }
-
- @Override
- public void draw(final Canvas canvas) {
- super.draw(canvas);
-
- if (uiMode == UIMode.DISPLAY) {
- canvas.drawPath(roundCornerShape, roundCornerPaint);
- }
- }
-
- @Override
- public void triggerTabsPanelTransition(final PropertyAnimator animator, final boolean areTabsShown) {
- if (areTabsShown) {
- ViewHelper.setAlpha(tabsCounter, 0.0f);
- ViewHelper.setAlpha(menuIcon, 0.0f);
- return;
- }
-
- final PropertyAnimator buttonsAnimator =
- new PropertyAnimator(animator.getDuration(), buttonsInterpolator);
- buttonsAnimator.attach(tabsCounter,
- PropertyAnimator.Property.ALPHA,
- 1.0f);
- buttonsAnimator.attach(menuIcon,
- PropertyAnimator.Property.ALPHA,
- 1.0f);
- buttonsAnimator.start();
- }
-
- /**
- * Returns the number of pixels the url bar translating edge
- * needs to translate to the right to enter its editing mode state.
- * A negative value means the edge must translate to the left.
- */
- protected int getUrlBarEntryTranslation() {
- // Find the distance from the right-edge of the url bar (where we're translating from) to
- // the left-edge of the cancel button (where we're translating to; note that the cancel
- // button must be laid out, i.e. not View.GONE).
- return editCancel.getLeft() - urlBarEntry.getRight();
- }
-
- protected int getUrlBarCurveTranslation() {
- return getWidth() - tabsButton.getLeft();
- }
-
- protected void updateTabCountAndAnimate(final int count) {
- // Don't animate if the toolbar is hidden.
- if (!isVisible()) {
- updateTabCount(count);
- return;
- }
-
- // If toolbar is in edit mode on a phone, this means the entry is expanded
- // and the tabs button is translated offscreen. Don't trigger tabs counter
- // updates until the tabs button is back on screen.
- // See stopEditing()
- if (!isTabsButtonOffscreen()) {
- tabsCounter.setCount(count);
-
- tabsButton.setContentDescription((count > 1) ?
- activity.getString(R.string.num_tabs, count) :
- activity.getString(R.string.one_tab));
- }
- }
-
- @Override
- protected void setUrlEditLayoutVisibility(final boolean showEditLayout,
- final PropertyAnimator animator) {
- super.setUrlEditLayoutVisibility(showEditLayout, animator);
-
- if (animator == null) {
- editCancel.setVisibility(showEditLayout ? View.VISIBLE : View.INVISIBLE);
- return;
- }
-
- animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
- @Override
- public void onPropertyAnimationStart() {
- if (!showEditLayout) {
- editCancel.setVisibility(View.INVISIBLE);
- }
- }
-
- @Override
- public void onPropertyAnimationEnd() {
- if (showEditLayout) {
- editCancel.setVisibility(View.VISIBLE);
- }
- }
- });
- }
-
- @Override
- public void onLightweightThemeChanged() {
- super.onLightweightThemeChanged();
- editCancel.onLightweightThemeChanged();
- }
-
- @Override
- public void onLightweightThemeReset() {
- super.onLightweightThemeReset();
- editCancel.onLightweightThemeReset();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbarTablet.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbarTablet.java
deleted file mode 100644
index 215934161..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbarTablet.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.animation.ViewHelper;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-
-/**
- * The toolbar implementation for tablet.
- */
-class BrowserToolbarTablet extends BrowserToolbarTabletBase {
-
- private static final int FORWARD_ANIMATION_DURATION = 450;
-
- private enum ForwardButtonState {
- HIDDEN,
- DISPLAYED,
- TRANSITIONING,
- }
-
- private final int forwardButtonTranslationWidth;
-
- private ForwardButtonState forwardButtonState;
-
- private boolean backButtonWasEnabledOnStartEditing;
-
- public BrowserToolbarTablet(final Context context, final AttributeSet attrs) {
- super(context, attrs);
-
- forwardButtonTranslationWidth =
- getResources().getDimensionPixelOffset(R.dimen.tablet_nav_button_width);
-
- // The forward button is initially expanded (in the layout file)
- // so translate it for start of the expansion animation; future
- // iterations translate it to this position when hiding and will already be set up.
- ViewHelper.setTranslationX(forwardButton, -forwardButtonTranslationWidth);
-
- // TODO: Move this to *TabletBase when old tablet is removed.
- // We don't want users clicking the forward button in transitions, but we don't want it to
- // look disabled to avoid flickering complications (e.g. disabled in editing mode), so undo
- // the work of the super class' constructor.
- forwardButton.setEnabled(true);
-
- updateForwardButtonState(ForwardButtonState.HIDDEN);
- }
-
- private void updateForwardButtonState(final ForwardButtonState state) {
- forwardButtonState = state;
- forwardButton.setEnabled(forwardButtonState == ForwardButtonState.DISPLAYED);
- }
-
- @Override
- public boolean isAnimating() {
- return false;
- }
-
- @Override
- protected void triggerStartEditingTransition(final PropertyAnimator animator) {
- showUrlEditLayout();
- }
-
- @Override
- protected void triggerStopEditingTransition() {
- hideUrlEditLayout();
- }
-
- @Override
- protected void animateForwardButton(final ForwardButtonAnimation animation) {
- final boolean willShowForward = (animation == ForwardButtonAnimation.SHOW);
- if ((forwardButtonState != ForwardButtonState.HIDDEN && willShowForward) ||
- (forwardButtonState != ForwardButtonState.DISPLAYED && !willShowForward)) {
- return;
- }
- updateForwardButtonState(ForwardButtonState.TRANSITIONING);
-
- // We want the forward button to show immediately when switching tabs
- final PropertyAnimator forwardAnim =
- new PropertyAnimator(isSwitchingTabs ? 10 : FORWARD_ANIMATION_DURATION);
-
- forwardAnim.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
- @Override
- public void onPropertyAnimationStart() {
- if (!willShowForward) {
- // Set the margin before the transition when hiding the forward button. We
- // have to do this so that the favicon isn't clipped during the transition
- MarginLayoutParams layoutParams =
- (MarginLayoutParams) urlDisplayLayout.getLayoutParams();
- layoutParams.leftMargin = 0;
-
- // Do the same on the URL edit container
- layoutParams = (MarginLayoutParams) urlEditLayout.getLayoutParams();
- layoutParams.leftMargin = 0;
-
- requestLayout();
- // Note, we already translated the favicon, site security, and text field
- // in prepareForwardAnimation, so they should appear to have not moved at
- // all at this point.
- }
- }
-
- @Override
- public void onPropertyAnimationEnd() {
- final ForwardButtonState newForwardButtonState;
- if (willShowForward) {
- // Increase the margins to ensure the text does not run outside the View.
- MarginLayoutParams layoutParams =
- (MarginLayoutParams) urlDisplayLayout.getLayoutParams();
- layoutParams.leftMargin = forwardButtonTranslationWidth;
-
- layoutParams = (MarginLayoutParams) urlEditLayout.getLayoutParams();
- layoutParams.leftMargin = forwardButtonTranslationWidth;
-
- newForwardButtonState = ForwardButtonState.DISPLAYED;
- } else {
- newForwardButtonState = ForwardButtonState.HIDDEN;
- }
-
- urlDisplayLayout.finishForwardAnimation();
- updateForwardButtonState(newForwardButtonState);
-
- requestLayout();
- }
- });
-
- prepareForwardAnimation(forwardAnim, animation, forwardButtonTranslationWidth);
- forwardAnim.start();
- }
-
- private void prepareForwardAnimation(PropertyAnimator anim, ForwardButtonAnimation animation, int width) {
- if (animation == ForwardButtonAnimation.HIDE) {
- anim.attach(forwardButton,
- PropertyAnimator.Property.TRANSLATION_X,
- -width);
- anim.attach(forwardButton,
- PropertyAnimator.Property.ALPHA,
- 0);
-
- } else {
- anim.attach(forwardButton,
- PropertyAnimator.Property.TRANSLATION_X,
- 0);
- anim.attach(forwardButton,
- PropertyAnimator.Property.ALPHA,
- 1);
- }
-
- urlDisplayLayout.prepareForwardAnimation(anim, animation, width);
- }
-
- @Override
- public void triggerTabsPanelTransition(final PropertyAnimator animator, final boolean areTabsShown) {
- // Do nothing.
- }
-
- @Override
- public void setToolBarButtonsAlpha(float alpha) {
- // Do nothing.
- }
-
-
- @Override
- public void startEditing(final String url, final PropertyAnimator animator) {
- // We already know the forward button state - no need to store it here.
- backButtonWasEnabledOnStartEditing = backButton.isEnabled();
-
- backButton.setEnabled(false);
- forwardButton.setEnabled(false);
-
- super.startEditing(url, animator);
- }
-
- @Override
- public String commitEdit() {
- stopEditingNewTablet();
- return super.commitEdit();
- }
-
- @Override
- public String cancelEdit() {
- // This can get called when we're not editing but we only want
- // to make these changes when leaving editing mode.
- if (isEditing()) {
- stopEditingNewTablet();
-
- backButton.setEnabled(backButtonWasEnabledOnStartEditing);
- updateForwardButtonState(forwardButtonState);
- }
-
- return super.cancelEdit();
- }
-
- private void stopEditingNewTablet() {
- // Undo the changes caused by calling setEnabled for forwardButton in startEditing.
- // Note that this should be called first so the enabled state of the
- // forward button is set to the proper value.
- forwardButton.setEnabled(true);
- }
-
- @Override
- protected Drawable getLWTDefaultStateSetDrawable() {
- return BrowserToolbar.getLightweightThemeDrawable(this, getTheme(), R.color.toolbar_grey);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbarTabletBase.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbarTabletBase.java
deleted file mode 100644
index e818bb95c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/BrowserToolbarTabletBase.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import java.util.Arrays;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.tabs.TabHistoryController;
-import org.mozilla.gecko.menu.MenuItemActionBar;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.widget.themed.ThemedTextView;
-
-import android.content.Context;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.Button;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-
-/**
- * A base implementations of the browser toolbar for tablets.
- * This class manages any Views, variables, etc. that are exclusive to tablet.
- */
-abstract class BrowserToolbarTabletBase extends BrowserToolbar {
-
- protected enum ForwardButtonAnimation {
- SHOW,
- HIDE
- }
-
- protected final LinearLayout actionItemBar;
-
- protected final BackButton backButton;
- protected final ForwardButton forwardButton;
-
- protected final View menuButtonMarginView;
-
- private final PorterDuffColorFilter privateBrowsingTabletMenuItemColorFilter;
-
- protected abstract void animateForwardButton(ForwardButtonAnimation animation);
-
- public BrowserToolbarTabletBase(final Context context, final AttributeSet attrs) {
- super(context, attrs);
-
- actionItemBar = (LinearLayout) findViewById(R.id.menu_items);
-
- backButton = (BackButton) findViewById(R.id.back);
- backButton.setEnabled(false);
- forwardButton = (ForwardButton) findViewById(R.id.forward);
- forwardButton.setEnabled(false);
- initButtonListeners();
-
- focusOrder.addAll(Arrays.asList(tabsButton, (View) backButton, (View) forwardButton, this));
- focusOrder.addAll(urlDisplayLayout.getFocusOrder());
- focusOrder.addAll(Arrays.asList(actionItemBar, menuButton));
-
- urlDisplayLayout.updateSiteIdentityAnchor(backButton);
-
- privateBrowsingTabletMenuItemColorFilter = new PorterDuffColorFilter(
- ContextCompat.getColor(context, R.color.tabs_tray_icon_grey), PorterDuff.Mode.SRC_IN);
-
- menuButtonMarginView = findViewById(R.id.menu_margin);
- if (menuButtonMarginView != null) {
- menuButtonMarginView.setVisibility(View.VISIBLE);
- }
- }
-
- private void initButtonListeners() {
- backButton.setOnClickListener(new Button.OnClickListener() {
- @Override
- public void onClick(View view) {
- Tabs.getInstance().getSelectedTab().doBack();
- }
- });
- backButton.setOnLongClickListener(new Button.OnLongClickListener() {
- @Override
- public boolean onLongClick(View view) {
- return tabHistoryController.showTabHistory(Tabs.getInstance().getSelectedTab(),
- TabHistoryController.HistoryAction.BACK);
- }
- });
-
- forwardButton.setOnClickListener(new Button.OnClickListener() {
- @Override
- public void onClick(View view) {
- Tabs.getInstance().getSelectedTab().doForward();
- }
- });
- forwardButton.setOnLongClickListener(new Button.OnLongClickListener() {
- @Override
- public boolean onLongClick(View view) {
- return tabHistoryController.showTabHistory(Tabs.getInstance().getSelectedTab(),
- TabHistoryController.HistoryAction.FORWARD);
- }
- });
- }
-
- @Override
- protected boolean isTabsButtonOffscreen() {
- return false;
- }
-
- @Override
- public boolean addActionItem(final View actionItem) {
- actionItemBar.addView(actionItem, LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
- return true;
- }
-
- @Override
- public void removeActionItem(final View actionItem) {
- actionItemBar.removeView(actionItem);
- }
-
- @Override
- protected void updateNavigationButtons(final Tab tab) {
- backButton.setEnabled(canDoBack(tab));
- animateForwardButton(
- canDoForward(tab) ? ForwardButtonAnimation.SHOW : ForwardButtonAnimation.HIDE);
- }
-
- @Override
- public void setNextFocusDownId(int nextId) {
- super.setNextFocusDownId(nextId);
- backButton.setNextFocusDownId(nextId);
- forwardButton.setNextFocusDownId(nextId);
- }
-
- @Override
- public void setPrivateMode(final boolean isPrivate) {
- super.setPrivateMode(isPrivate);
-
- // If we had backgroundTintList, we could remove the colorFilter
- // code in favor of setPrivateMode (bug 1197432).
- final PorterDuffColorFilter colorFilter =
- isPrivate ? privateBrowsingTabletMenuItemColorFilter : null;
- setTabsCounterPrivateMode(isPrivate, colorFilter);
-
- backButton.setPrivateMode(isPrivate);
- forwardButton.setPrivateMode(isPrivate);
- menuIcon.setPrivateMode(isPrivate);
- for (int i = 0; i < actionItemBar.getChildCount(); ++i) {
- final MenuItemActionBar child = (MenuItemActionBar) actionItemBar.getChildAt(i);
- child.setPrivateMode(isPrivate);
- }
- }
-
- private void setTabsCounterPrivateMode(final boolean isPrivate, final PorterDuffColorFilter colorFilter) {
- // The TabsCounter is a TextSwitcher which cycles two views
- // to provide animations, hence looping over these two children.
- for (int i = 0; i < 2; ++i) {
- final ThemedTextView view = (ThemedTextView) tabsCounter.getChildAt(i);
- view.setPrivateMode(isPrivate);
- view.getBackground().mutate().setColorFilter(colorFilter);
- }
-
- // To prevent animation of the background,
- // it is set to a different Drawable.
- tabsCounter.getBackground().mutate().setColorFilter(colorFilter);
- }
-
- @Override
- public View getDoorHangerAnchor() {
- return backButton;
- }
-
- protected boolean canDoBack(final Tab tab) {
- return (tab.canDoBack() && !isEditing());
- }
-
- protected boolean canDoForward(final Tab tab) {
- return (tab.canDoForward() && !isEditing());
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/CanvasDelegate.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/CanvasDelegate.java
deleted file mode 100644
index 55567fba3..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/CanvasDelegate.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import org.mozilla.gecko.AppConstants.Versions;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Shader;
-
-class CanvasDelegate {
- Paint mPaint;
- PorterDuffXfermode mMode;
- DrawManager mDrawManager;
-
- // DrawManager would do a default draw of the background.
- static interface DrawManager {
- public void defaultDraw(Canvas canvas);
- }
-
- CanvasDelegate(DrawManager drawManager, Mode mode, Paint paint) {
- mDrawManager = drawManager;
-
- // DST_IN masks, DST_OUT clips.
- mMode = new PorterDuffXfermode(mode);
-
- mPaint = paint;
- }
-
- void draw(Canvas canvas, Path path, int width, int height) {
- // Save the canvas. All PorterDuff operations should be done in a offscreen bitmap.
- int count = canvas.saveLayer(0, 0, width, height, null,
- Canvas.MATRIX_SAVE_FLAG |
- Canvas.CLIP_SAVE_FLAG |
- Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
- Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
- Canvas.CLIP_TO_LAYER_SAVE_FLAG);
-
- // Do a default draw.
- mDrawManager.defaultDraw(canvas);
-
- if (path != null && !path.isEmpty()) {
- // ICS added double-buffering, which made it easier for drawing the Path directly over the DST.
- // In pre-ICS, drawPath() doesn't seem to use ARGB_8888 mode for performance, hence transparency is not preserved.
- mPaint.setXfermode(mMode);
- canvas.drawPath(path, mPaint);
- }
-
- // Restore the canvas.
- canvas.restoreToCount(count);
- }
-
- void setShader(Shader shader) {
- mPaint.setShader(shader);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/ForwardButton.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/ForwardButton.java
deleted file mode 100644
index f95bb5e8a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/ForwardButton.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-public class ForwardButton extends NavButton {
- public ForwardButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
- super.onSizeChanged(width, height, oldWidth, oldHeight);
-
- mBorderPath.reset();
- mBorderPath.moveTo(width - mBorderWidth, 0);
- mBorderPath.lineTo(width - mBorderWidth, height);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/NavButton.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/NavButton.java
deleted file mode 100644
index 68194e222..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/NavButton.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.StateListDrawable;
-import android.util.AttributeSet;
-
-abstract class NavButton extends ShapedButton {
- protected final Path mBorderPath;
- protected final Paint mBorderPaint;
- protected final float mBorderWidth;
-
- protected final int mBorderColor;
- protected final int mBorderColorPrivate;
-
- public NavButton(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- final Resources res = getResources();
- mBorderColor = ContextCompat.getColor(context, R.color.disabled_grey);
- mBorderColorPrivate = ContextCompat.getColor(context, R.color.toolbar_icon_grey);
- mBorderWidth = res.getDimension(R.dimen.nav_button_border_width);
-
- // Paint to draw the border.
- mBorderPaint = new Paint();
- mBorderPaint.setAntiAlias(true);
- mBorderPaint.setStrokeWidth(mBorderWidth);
- mBorderPaint.setStyle(Paint.Style.STROKE);
-
- // Path is masked.
- mBorderPath = new Path();
-
- setPrivateMode(false);
- }
-
- @Override
- public void setPrivateMode(boolean isPrivate) {
- super.setPrivateMode(isPrivate);
- mBorderPaint.setColor(isPrivate ? mBorderColorPrivate : mBorderColor);
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
-
- // Draw the border on top.
- canvas.drawPath(mBorderPath, mBorderPaint);
- }
-
- // The drawable is constructed as per @drawable/url_bar_nav_button.
- @Override
- public void onLightweightThemeChanged() {
- final Drawable drawable = BrowserToolbar.getLightweightThemeDrawable(this, getTheme(), R.color.toolbar_grey);
-
- if (drawable == null) {
- return;
- }
-
- final StateListDrawable stateList = new StateListDrawable();
- stateList.addState(PRIVATE_PRESSED_STATE_SET, getColorDrawable(R.color.placeholder_active_grey));
- stateList.addState(PRESSED_ENABLED_STATE_SET, getColorDrawable(R.color.toolbar_grey_pressed));
- stateList.addState(PRIVATE_FOCUSED_STATE_SET, getColorDrawable(R.color.text_and_tabs_tray_grey));
- stateList.addState(FOCUSED_STATE_SET, getColorDrawable(R.color.tablet_highlight_focused));
- stateList.addState(PRIVATE_STATE_SET, getColorDrawable(R.color.tabs_tray_grey_pressed));
- stateList.addState(EMPTY_STATE_SET, drawable);
-
- setBackgroundDrawable(stateList);
- }
-
- @Override
- public void onLightweightThemeReset() {
- setBackgroundResource(R.drawable.url_bar_nav_button);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/PageActionLayout.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/PageActionLayout.java
deleted file mode 100644
index 9361d5907..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/PageActionLayout.java
+++ /dev/null
@@ -1,371 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.ResourceDrawableUtils;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.NativeEventListener;
-import org.mozilla.gecko.util.NativeJSObject;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.widget.GeckoPopupMenu;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import java.util.Iterator;
-import java.util.List;
-import java.util.UUID;
-import java.util.ArrayList;
-
-public class PageActionLayout extends LinearLayout implements NativeEventListener,
- View.OnClickListener,
- View.OnLongClickListener {
- private static final String MENU_BUTTON_KEY = "MENU_BUTTON_KEY";
- private static final int DEFAULT_PAGE_ACTIONS_SHOWN = 2;
-
- private final Context mContext;
- private final LinearLayout mLayout;
- private final List<PageAction> mPageActionList;
-
- private GeckoPopupMenu mPageActionsMenu;
-
- // By default it's two, can be changed by calling setNumberShown(int)
- private int mMaxVisiblePageActions;
-
- public PageActionLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- mLayout = this;
-
- mPageActionList = new ArrayList<PageAction>();
- setNumberShown(DEFAULT_PAGE_ACTIONS_SHOWN);
- refreshPageActionIcons();
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
- "PageActions:Add",
- "PageActions:Remove");
- }
-
- @Override
- protected void onDetachedFromWindow() {
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
- "PageActions:Add",
- "PageActions:Remove");
-
- super.onDetachedFromWindow();
- }
-
- private void setNumberShown(int count) {
- ThreadUtils.assertOnUiThread();
-
- mMaxVisiblePageActions = count;
-
- for (int index = 0; index < count; index++) {
- if ((getChildCount() - 1) < index) {
- mLayout.addView(createImageButton());
- }
- }
- }
-
- @Override
- public void handleMessage(final String event, final NativeJSObject message, final EventCallback callback) {
- // NativeJSObject cannot be used off of the Gecko thread, so convert it to a Bundle.
- final Bundle bundle = message.toBundle();
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- handleUiMessage(event, bundle);
- }
- });
- }
-
- private void handleUiMessage(final String event, final Bundle message) {
- ThreadUtils.assertOnUiThread();
-
- if (event.equals("PageActions:Add")) {
- final String id = message.getString("id");
- final String title = message.getString("title");
- final String imageURL = message.getString("icon");
- final boolean important = message.getBoolean("important");
-
- addPageAction(id, title, imageURL, new OnPageActionClickListeners() {
- @Override
- public void onClick(String id) {
- GeckoAppShell.notifyObservers("PageActions:Clicked", id);
- }
-
- @Override
- public boolean onLongClick(String id) {
- GeckoAppShell.notifyObservers("PageActions:LongClicked", id);
- return true;
- }
- }, important);
- } else if (event.equals("PageActions:Remove")) {
- final String id = message.getString("id");
-
- removePageAction(id);
- }
- }
-
- private void addPageAction(final String id, final String title, final String imageData,
- final OnPageActionClickListeners onPageActionClickListeners, boolean important) {
- ThreadUtils.assertOnUiThread();
-
- final PageAction pageAction = new PageAction(id, title, null, onPageActionClickListeners, important);
-
- int insertAt = mPageActionList.size();
- while (insertAt > 0 && mPageActionList.get(insertAt - 1).isImportant()) {
- insertAt--;
- }
- mPageActionList.add(insertAt, pageAction);
-
- ResourceDrawableUtils.getDrawable(mContext, imageData, new ResourceDrawableUtils.BitmapLoader() {
- @Override
- public void onBitmapFound(final Drawable d) {
- if (mPageActionList.contains(pageAction)) {
- pageAction.setDrawable(d);
- refreshPageActionIcons();
- }
- }
- });
- }
-
- private void removePageAction(String id) {
- ThreadUtils.assertOnUiThread();
-
- final Iterator<PageAction> iter = mPageActionList.iterator();
- while (iter.hasNext()) {
- final PageAction pageAction = iter.next();
- if (pageAction.getID().equals(id)) {
- iter.remove();
- refreshPageActionIcons();
- return;
- }
- }
- }
-
- private ImageButton createImageButton() {
- ThreadUtils.assertOnUiThread();
-
- final int width = mContext.getResources().getDimensionPixelSize(R.dimen.page_action_button_width);
- ImageButton imageButton = new ImageButton(mContext, null, R.style.UrlBar_ImageButton);
- imageButton.setLayoutParams(new LayoutParams(width, LayoutParams.MATCH_PARENT));
- imageButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
- imageButton.setOnClickListener(this);
- imageButton.setOnLongClickListener(this);
- return imageButton;
- }
-
- @Override
- public void onClick(View v) {
- String buttonClickedId = (String)v.getTag();
- if (buttonClickedId != null) {
- if (buttonClickedId.equals(MENU_BUTTON_KEY)) {
- showMenu(v, mPageActionList.size() - mMaxVisiblePageActions + 1);
- } else {
- getPageActionWithId(buttonClickedId).onClick();
- }
- }
- }
-
- @Override
- public boolean onLongClick(View v) {
- String buttonClickedId = (String)v.getTag();
- if (buttonClickedId.equals(MENU_BUTTON_KEY)) {
- showMenu(v, mPageActionList.size() - mMaxVisiblePageActions + 1);
- return true;
- } else {
- return getPageActionWithId(buttonClickedId).onLongClick();
- }
- }
-
- private void setActionForView(final ImageButton view, final PageAction pageAction) {
- ThreadUtils.assertOnUiThread();
-
- if (pageAction == null) {
- view.setTag(null);
- view.setImageDrawable(null);
- view.setVisibility(View.GONE);
- view.setContentDescription(null);
- return;
- }
-
- view.setTag(pageAction.getID());
- view.setImageDrawable(pageAction.getDrawable());
- view.setVisibility(View.VISIBLE);
- view.setContentDescription(pageAction.getTitle());
- }
-
- private void refreshPageActionIcons() {
- ThreadUtils.assertOnUiThread();
-
- final Resources resources = mContext.getResources();
- for (int i = 0; i < this.getChildCount(); i++) {
- final ImageButton v = (ImageButton) this.getChildAt(i);
- final PageAction pageAction = getPageActionForViewAt(i);
-
- // If there are more page actions than buttons, set the menu icon.
- // Otherwise, set the page action's icon if there is a page action.
- if ((i == this.getChildCount() - 1) && (mPageActionList.size() > mMaxVisiblePageActions)) {
- v.setTag(MENU_BUTTON_KEY);
- v.setImageDrawable(resources.getDrawable(R.drawable.icon_pageaction));
- v.setVisibility((pageAction != null) ? View.VISIBLE : View.GONE);
- v.setContentDescription(resources.getString(R.string.page_action_dropmarker_description));
- } else {
- setActionForView(v, pageAction);
- }
- }
- }
-
- private PageAction getPageActionForViewAt(int index) {
- ThreadUtils.assertOnUiThread();
-
- /**
- * We show the user the most recent pageaction added since this keeps the user aware of any new page actions being added
- * Also, the order of the pageAction is important i.e. if a page action is added, instead of shifting the pagactions to the
- * left to make space for the new one, it would be more visually appealing to have the pageaction appear in the blank space.
- *
- * buttonIndex is needed for this reason because every new View added to PageActionLayout gets added to the right of its neighbouring View.
- * Hence the button on the very leftmost has the index 0. We want our pageactions to start from the rightmost
- * and hence we maintain the insertion order of the child Views which is essentially the reverse of their index
- */
-
- final int buttonIndex = (this.getChildCount() - 1) - index;
-
- if (mPageActionList.size() > buttonIndex) {
- // Return the pageactions starting from the end of the list for the number of visible pageactions.
- final int buttonCount = Math.min(mPageActionList.size(), getChildCount());
- return mPageActionList.get((mPageActionList.size() - buttonCount) + buttonIndex);
- }
- return null;
- }
-
- private PageAction getPageActionWithId(String id) {
- ThreadUtils.assertOnUiThread();
-
- for (PageAction pageAction : mPageActionList) {
- if (pageAction.getID().equals(id)) {
- return pageAction;
- }
- }
- return null;
- }
-
- private void showMenu(View pageActionButton, int toShow) {
- ThreadUtils.assertOnUiThread();
-
- if (mPageActionsMenu == null) {
- mPageActionsMenu = new GeckoPopupMenu(pageActionButton.getContext(), pageActionButton);
- mPageActionsMenu.inflate(0);
- mPageActionsMenu.setOnMenuItemClickListener(new GeckoPopupMenu.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- int id = item.getItemId();
- for (int i = 0; i < mPageActionList.size(); i++) {
- PageAction pageAction = mPageActionList.get(i);
- if (pageAction.key() == id) {
- pageAction.onClick();
- return true;
- }
- }
- return false;
- }
- });
- }
- Menu menu = mPageActionsMenu.getMenu();
- menu.clear();
-
- for (int i = 0; i < mPageActionList.size() && i < toShow; i++) {
- PageAction pageAction = mPageActionList.get(i);
- MenuItem item = menu.add(Menu.NONE, pageAction.key(), Menu.NONE, pageAction.getTitle());
- item.setIcon(pageAction.getDrawable());
- }
- mPageActionsMenu.show();
- }
-
- private static interface OnPageActionClickListeners {
- public void onClick(String id);
- public boolean onLongClick(String id);
- }
-
- private static class PageAction {
- private final OnPageActionClickListeners mOnPageActionClickListeners;
- private Drawable mDrawable;
- private final String mTitle;
- private final String mId;
- private final int key;
- private final boolean mImportant;
-
- public PageAction(String id,
- String title,
- Drawable image,
- OnPageActionClickListeners onPageActionClickListeners,
- boolean important) {
- mId = id;
- mTitle = title;
- mDrawable = image;
- mOnPageActionClickListeners = onPageActionClickListeners;
- mImportant = important;
-
- key = UUID.fromString(mId.subSequence(1, mId.length() - 2).toString()).hashCode();
- }
-
- public Drawable getDrawable() {
- return mDrawable;
- }
-
- public void setDrawable(Drawable d) {
- mDrawable = d;
- }
-
- public String getTitle() {
- return mTitle;
- }
-
- public String getID() {
- return mId;
- }
-
- public int key() {
- return key;
- }
-
- public boolean isImportant() {
- return mImportant;
- }
-
- public void onClick() {
- if (mOnPageActionClickListeners != null) {
- mOnPageActionClickListeners.onClick(mId);
- }
- }
-
- public boolean onLongClick() {
- if (mOnPageActionClickListeners != null) {
- return mOnPageActionClickListeners.onLongClick(mId);
- }
- return false;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/PhoneTabsButton.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/PhoneTabsButton.java
deleted file mode 100644
index 416485494..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/PhoneTabsButton.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-import org.mozilla.gecko.tabs.TabCurve;
-
-public class PhoneTabsButton extends ShapedButton {
- public PhoneTabsButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
- super.onSizeChanged(width, height, oldWidth, oldHeight);
-
- mPath.reset();
-
- mPath.moveTo(0, 0);
- TabCurve.drawFromTop(mPath, 0, height, TabCurve.Direction.RIGHT);
- mPath.lineTo(width, height);
- mPath.lineTo(width, 0);
- mPath.lineTo(0, 0);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/ShapedButton.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/ShapedButton.java
deleted file mode 100644
index 003dada2d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/ShapedButton.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.lwt.LightweightThemeDrawable;
-import org.mozilla.gecko.widget.themed.ThemedImageButton;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.StateListDrawable;
-import android.util.AttributeSet;
-
-/**
- * A ImageButton with a custom drawn path and lightweight theme support. Note that {@link ShapedButtonFrameLayout}
- * copies the lwt support so if you change it here, you should probably change it there.
- */
-public class ShapedButton extends ThemedImageButton
- implements CanvasDelegate.DrawManager {
-
- protected final Path mPath;
- protected final CanvasDelegate mCanvasDelegate;
-
- public ShapedButton(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- // Path is clipped.
- mPath = new Path();
-
- final Paint paint = new Paint();
- paint.setAntiAlias(true);
- paint.setColor(ContextCompat.getColor(context, R.color.canvas_delegate_paint));
- paint.setStrokeWidth(0.0f);
- mCanvasDelegate = new CanvasDelegate(this, Mode.DST_IN, paint);
-
- setWillNotDraw(false);
- }
-
- @Override
- @SuppressLint("MissingSuperCall") // Super gets called from defaultDraw().
- // It is intentionally not called in the other case.
- public void draw(Canvas canvas) {
- if (mCanvasDelegate != null)
- mCanvasDelegate.draw(canvas, mPath, getWidth(), getHeight());
- else
- defaultDraw(canvas);
- }
-
- @Override
- public void defaultDraw(Canvas canvas) {
- super.draw(canvas);
- }
-
- // The drawable is constructed as per @drawable/shaped_button.
- @Override
- public void onLightweightThemeChanged() {
- final int background = ContextCompat.getColor(getContext(), R.color.text_and_tabs_tray_grey);
- final LightweightThemeDrawable lightWeight = getTheme().getColorDrawable(this, background);
-
- if (lightWeight == null)
- return;
-
- lightWeight.setAlpha(34, 34);
-
- final StateListDrawable stateList = new StateListDrawable();
- stateList.addState(PRESSED_ENABLED_STATE_SET, getColorDrawable(R.color.highlight_shaped));
- stateList.addState(FOCUSED_STATE_SET, getColorDrawable(R.color.highlight_shaped_focused));
- stateList.addState(PRIVATE_STATE_SET, getColorDrawable(R.color.text_and_tabs_tray_grey));
- stateList.addState(EMPTY_STATE_SET, lightWeight);
-
- setBackgroundDrawable(stateList);
- }
-
- @Override
- public void onLightweightThemeReset() {
- setBackgroundResource(R.drawable.shaped_button);
- }
-
- @Override
- public void setBackgroundDrawable(Drawable drawable) {
- if (getBackground() == null || drawable == null) {
- super.setBackgroundDrawable(drawable);
- return;
- }
-
- int[] padding = new int[] { getPaddingLeft(),
- getPaddingTop(),
- getPaddingRight(),
- getPaddingBottom()
- };
- drawable.setLevel(getBackground().getLevel());
- super.setBackgroundDrawable(drawable);
-
- setPadding(padding[0], padding[1], padding[2], padding[3]);
- }
-
- @Override
- public void setBackgroundResource(int resId) {
- setBackgroundDrawable(getResources().getDrawable(resId));
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/ShapedButtonFrameLayout.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/ShapedButtonFrameLayout.java
deleted file mode 100644
index c14829aec..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/ShapedButtonFrameLayout.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.lwt.LightweightThemeDrawable;
-import org.mozilla.gecko.widget.themed.ThemedFrameLayout;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.StateListDrawable;
-import android.util.AttributeSet;
-
-/** A FrameLayout with lightweight theme support. Note that {@link ShapedButton}'s lwt support is basically the same so
- * if you change it here, you should probably change it there. Note also that this doesn't have ShapedButton's path code
- * so shouldn't have "ShapedButton" in the name, but I wanted to make the connection apparent so I left it.
- */
-public class ShapedButtonFrameLayout extends ThemedFrameLayout {
-
- public ShapedButtonFrameLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- // The drawable is constructed as per @drawable/shaped_button.
- @Override
- public void onLightweightThemeChanged() {
- final int background = ContextCompat.getColor(getContext(), R.color.text_and_tabs_tray_grey);
- final LightweightThemeDrawable lightWeight = getTheme().getColorDrawable(this, background);
-
- if (lightWeight == null)
- return;
-
- lightWeight.setAlpha(34, 34);
-
- final StateListDrawable stateList = new StateListDrawable();
- stateList.addState(PRESSED_ENABLED_STATE_SET, getColorDrawable(R.color.highlight_shaped));
- stateList.addState(FOCUSED_STATE_SET, getColorDrawable(R.color.highlight_shaped_focused));
- stateList.addState(PRIVATE_STATE_SET, getColorDrawable(R.color.text_and_tabs_tray_grey));
- stateList.addState(EMPTY_STATE_SET, lightWeight);
-
- setBackgroundDrawable(stateList);
- }
-
- @Override
- public void onLightweightThemeReset() {
- setBackgroundResource(R.drawable.shaped_button);
- }
-
- @Override
- public void setBackgroundDrawable(Drawable drawable) {
- if (getBackground() == null || drawable == null) {
- super.setBackgroundDrawable(drawable);
- return;
- }
-
- int[] padding = new int[] { getPaddingLeft(),
- getPaddingTop(),
- getPaddingRight(),
- getPaddingBottom()
- };
- drawable.setLevel(getBackground().getLevel());
- super.setBackgroundDrawable(drawable);
-
- setPadding(padding[0], padding[1], padding[2], padding[3]);
- }
-
- @Override
- public void setBackgroundResource(int resId) {
- setBackgroundDrawable(getResources().getDrawable(resId));
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/SiteIdentityPopup.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/SiteIdentityPopup.java
deleted file mode 100644
index 14230a2ec..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/SiteIdentityPopup.java
+++ /dev/null
@@ -1,571 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.support.design.widget.Snackbar;
-import android.support.v4.content.ContextCompat;
-import android.widget.ImageView;
-import android.widget.Toast;
-import org.json.JSONException;
-import org.json.JSONArray;
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.SiteIdentity;
-import org.mozilla.gecko.SiteIdentity.SecurityMode;
-import org.mozilla.gecko.SiteIdentity.MixedMode;
-import org.mozilla.gecko.SiteIdentity.TrackingMode;
-import org.mozilla.gecko.SnackbarBuilder;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.util.GeckoEventListener;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.widget.AnchoredPopup;
-import org.mozilla.gecko.widget.DoorHanger;
-import org.mozilla.gecko.widget.DoorHanger.OnButtonClickListener;
-import org.json.JSONObject;
-
-import android.app.Activity;
-import android.content.Context;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import org.mozilla.gecko.widget.DoorhangerConfig;
-import org.mozilla.gecko.widget.SiteLogins;
-
-/**
- * SiteIdentityPopup is a singleton class that displays site identity data in
- * an arrow panel popup hanging from the lock icon in the browser toolbar.
- *
- * A site identity icon may be displayed in the url, and is set in <code>ToolbarDisplayLayout</code>.
- */
-public class SiteIdentityPopup extends AnchoredPopup implements GeckoEventListener {
-
- public static enum ButtonType { DISABLE, ENABLE, KEEP_BLOCKING, CANCEL, COPY }
-
- private static final String LOGTAG = "GeckoSiteIdentityPopup";
-
- private static final String MIXED_CONTENT_SUPPORT_URL =
- "https://support.mozilla.org/kb/how-does-insecure-content-affect-safety-android";
- private static final String TRACKING_CONTENT_SUPPORT_URL =
- "https://support.mozilla.org/kb/firefox-android-tracking-protection";
-
- // Placeholder string.
- private final static String FORMAT_S = "%s";
-
- private final Resources mResources;
- private SiteIdentity mSiteIdentity;
-
- private LinearLayout mIdentity;
-
- private LinearLayout mIdentityKnownContainer;
-
- private ImageView mIcon;
- private TextView mTitle;
- private TextView mSecurityState;
- private TextView mMixedContentActivity;
- private TextView mOwner;
- private TextView mOwnerSupplemental;
- private TextView mVerifier;
- private TextView mLink;
- private TextView mSiteSettingsLink;
-
- private View mDivider;
-
- private DoorHanger mTrackingContentNotification;
- private DoorHanger mSelectLoginDoorhanger;
-
- private final OnButtonClickListener mContentButtonClickListener;
-
- public SiteIdentityPopup(Context context) {
- super(context);
-
- mResources = mContext.getResources();
-
- mContentButtonClickListener = new ContentNotificationButtonListener();
-
- GeckoApp.getEventDispatcher().registerGeckoThreadListener(this,
- "Doorhanger:Logins",
- "Permissions:CheckResult");
- }
-
- @Override
- protected void init() {
- super.init();
-
- // Make the popup focusable so it doesn't inadvertently trigger click events elsewhere
- // which may reshow the popup (see bug 785156)
- setFocusable(true);
-
- LayoutInflater inflater = LayoutInflater.from(mContext);
- mIdentity = (LinearLayout) inflater.inflate(R.layout.site_identity, null);
- mContent.addView(mIdentity);
-
- mIdentityKnownContainer =
- (LinearLayout) mIdentity.findViewById(R.id.site_identity_known_container);
-
- mIcon = (ImageView) mIdentity.findViewById(R.id.site_identity_icon);
- mTitle = (TextView) mIdentity.findViewById(R.id.site_identity_title);
- mSecurityState = (TextView) mIdentity.findViewById(R.id.site_identity_state);
- mMixedContentActivity = (TextView) mIdentity.findViewById(R.id.mixed_content_activity);
-
- mOwner = (TextView) mIdentityKnownContainer.findViewById(R.id.owner);
- mOwnerSupplemental = (TextView) mIdentityKnownContainer.findViewById(R.id.owner_supplemental);
- mVerifier = (TextView) mIdentityKnownContainer.findViewById(R.id.verifier);
- mDivider = mIdentity.findViewById(R.id.divider_doorhanger);
-
- mLink = (TextView) mIdentity.findViewById(R.id.site_identity_link);
- mLink.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- Tabs.getInstance().loadUrlInTab(MIXED_CONTENT_SUPPORT_URL);
- }
- });
-
- mSiteSettingsLink = (TextView) mIdentity.findViewById(R.id.site_settings_link);
- }
-
- private void updateIdentity(final SiteIdentity siteIdentity) {
- if (!mInflated) {
- init();
- }
-
- final boolean isIdentityKnown = (siteIdentity.getSecurityMode() == SecurityMode.IDENTIFIED ||
- siteIdentity.getSecurityMode() == SecurityMode.VERIFIED);
- updateConnectionState(siteIdentity);
- toggleIdentityKnownContainerVisibility(isIdentityKnown);
-
- if (isIdentityKnown) {
- updateIdentityInformation(siteIdentity);
- }
-
- GeckoAppShell.notifyObservers("Permissions:Check", null);
- }
-
- @Override
- public void handleMessage(String event, JSONObject geckoObject) {
- if ("Doorhanger:Logins".equals(event)) {
- try {
- final Tab selectedTab = Tabs.getInstance().getSelectedTab();
- if (selectedTab != null) {
- final JSONObject data = geckoObject.getJSONObject("data");
- addLoginsToTab(data);
- }
- if (isShowing()) {
- addSelectLoginDoorhanger(selectedTab);
- }
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error accessing logins in Doorhanger:Logins message", e);
- }
- } else if ("Permissions:CheckResult".equals(event)) {
- final boolean hasPermissions = geckoObject.optBoolean("hasPermissions", false);
- if (hasPermissions) {
- mSiteSettingsLink.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- GeckoAppShell.notifyObservers("Permissions:Get", null);
- dismiss();
- }
- });
- }
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- mSiteSettingsLink.setVisibility(hasPermissions ? View.VISIBLE : View.GONE);
- }
- });
- }
- }
-
- private void addLoginsToTab(JSONObject data) throws JSONException {
- final JSONArray logins = data.getJSONArray("logins");
-
- final SiteLogins siteLogins = new SiteLogins(logins);
- Tabs.getInstance().getSelectedTab().setSiteLogins(siteLogins);
- }
-
- private void addSelectLoginDoorhanger(Tab tab) throws JSONException {
- final SiteLogins siteLogins = tab.getSiteLogins();
- if (siteLogins == null) {
- return;
- }
-
- final JSONArray logins = siteLogins.getLogins();
- if (logins.length() == 0) {
- return;
- }
-
- final JSONObject login = (JSONObject) logins.get(0);
-
- // Create button click listener for copying a password to the clipboard.
- final OnButtonClickListener buttonClickListener = new OnButtonClickListener() {
- Activity activity = (Activity) mContext;
- @Override
- public void onButtonClick(JSONObject response, DoorHanger doorhanger) {
- try {
- final int buttonId = response.getInt("callback");
- if (buttonId == ButtonType.COPY.ordinal()) {
- final ClipboardManager manager = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
- String password;
- if (response.has("password")) {
- // Click listener being called from List Dialog.
- password = response.optString("password");
- } else {
- password = login.getString("password");
- }
-
- manager.setPrimaryClip(ClipData.newPlainText("password", password));
-
- SnackbarBuilder.builder(activity)
- .message(R.string.doorhanger_login_select_toast_copy)
- .duration(Snackbar.LENGTH_SHORT)
- .buildAndShow();
- }
- dismiss();
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error handling Select login button click", e);
- SnackbarBuilder.builder(activity)
- .message(R.string.doorhanger_login_select_toast_copy_error)
- .duration(Snackbar.LENGTH_SHORT)
- .buildAndShow();
- }
- }
- };
-
- final DoorhangerConfig config = new DoorhangerConfig(DoorHanger.Type.LOGIN, buttonClickListener);
-
- // Set buttons.
- config.setButton(mContext.getString(R.string.button_cancel), ButtonType.CANCEL.ordinal(), false);
- config.setButton(mContext.getString(R.string.button_copy), ButtonType.COPY.ordinal(), true);
-
- // Set message.
- String username = ((JSONObject) logins.get(0)).getString("username");
- if (TextUtils.isEmpty(username)) {
- username = mContext.getString(R.string.doorhanger_login_no_username);
- }
-
- final String message = mContext.getString(R.string.doorhanger_login_select_message).replace(FORMAT_S, username);
- config.setMessage(message);
-
- // Set options.
- final JSONObject options = new JSONObject();
-
- // Add action text only if there are other logins to select.
- if (logins.length() > 1) {
-
- final JSONObject actionText = new JSONObject();
- actionText.put("type", "SELECT");
-
- final JSONObject bundle = new JSONObject();
- bundle.put("logins", logins);
-
- actionText.put("bundle", bundle);
- options.put("actionText", actionText);
- }
-
- config.setOptions(options);
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- if (!mInflated) {
- init();
- }
-
- removeSelectLoginDoorhanger();
-
- mSelectLoginDoorhanger = DoorHanger.Get(mContext, config);
- mContent.addView(mSelectLoginDoorhanger);
- mDivider.setVisibility(View.VISIBLE);
- }
- });
- }
-
- private void removeSelectLoginDoorhanger() {
- if (mSelectLoginDoorhanger != null) {
- mContent.removeView(mSelectLoginDoorhanger);
- mSelectLoginDoorhanger = null;
- }
- }
-
- private void toggleIdentityKnownContainerVisibility(final boolean isIdentityKnown) {
- final int identityInfoVisibility = isIdentityKnown ? View.VISIBLE : View.GONE;
- mIdentityKnownContainer.setVisibility(identityInfoVisibility);
- }
-
- /**
- * Update the Site Identity content to reflect connection state.
- *
- * The connection state should reflect the combination of:
- * a) Connection encryption
- * b) Mixed Content state (Active/Display Mixed content, loaded, blocked, none, etc)
- * and update the icons and strings to inform the user of that state.
- *
- * @param siteIdentity SiteIdentity information about the connection.
- */
- private void updateConnectionState(final SiteIdentity siteIdentity) {
- if (siteIdentity.getSecurityMode() == SecurityMode.CHROMEUI) {
- mSecurityState.setText(R.string.identity_connection_chromeui);
- mSecurityState.setTextColor(ContextCompat.getColor(mContext, R.color.placeholder_active_grey));
-
- mIcon.setImageResource(R.drawable.icon);
- clearSecurityStateIcon();
-
- mMixedContentActivity.setVisibility(View.GONE);
- mLink.setVisibility(View.GONE);
- } else if (!siteIdentity.isSecure()) {
- if (siteIdentity.getMixedModeActive() == MixedMode.MIXED_CONTENT_LOADED) {
- // Active Mixed Content loaded because user has disabled blocking.
- mIcon.setImageResource(R.drawable.lock_disabled);
- clearSecurityStateIcon();
- mMixedContentActivity.setVisibility(View.VISIBLE);
- mMixedContentActivity.setText(R.string.mixed_content_protection_disabled);
-
- mLink.setVisibility(View.VISIBLE);
- } else if (siteIdentity.getMixedModeDisplay() == MixedMode.MIXED_CONTENT_LOADED) {
- // Passive Mixed Content loaded.
- mIcon.setImageResource(R.drawable.lock_inactive);
- setSecurityStateIcon(R.drawable.warning_major, 1);
- mMixedContentActivity.setVisibility(View.VISIBLE);
- if (siteIdentity.getMixedModeActive() == MixedMode.MIXED_CONTENT_BLOCKED) {
- mMixedContentActivity.setText(R.string.mixed_content_blocked_some);
- } else {
- mMixedContentActivity.setText(R.string.mixed_content_display_loaded);
- }
- mLink.setVisibility(View.VISIBLE);
-
- } else {
- // Unencrypted connection with no mixed content.
- mIcon.setImageResource(R.drawable.globe_light);
- clearSecurityStateIcon();
-
- mMixedContentActivity.setVisibility(View.GONE);
- mLink.setVisibility(View.GONE);
- }
-
- mSecurityState.setText(R.string.identity_connection_insecure);
- mSecurityState.setTextColor(ContextCompat.getColor(mContext, R.color.placeholder_active_grey));
- } else {
- // Connection is secure.
- mIcon.setImageResource(R.drawable.lock_secure);
-
- setSecurityStateIcon(R.drawable.img_check, 2);
- mSecurityState.setTextColor(ContextCompat.getColor(mContext, R.color.affirmative_green));
- mSecurityState.setText(R.string.identity_connection_secure);
-
- // Mixed content has been blocked, if present.
- if (siteIdentity.getMixedModeActive() == MixedMode.MIXED_CONTENT_BLOCKED ||
- siteIdentity.getMixedModeDisplay() == MixedMode.MIXED_CONTENT_BLOCKED) {
- mMixedContentActivity.setVisibility(View.VISIBLE);
- mMixedContentActivity.setText(R.string.mixed_content_blocked_all);
- mLink.setVisibility(View.VISIBLE);
- } else {
- mMixedContentActivity.setVisibility(View.GONE);
- mLink.setVisibility(View.GONE);
- }
- }
- }
-
- private void clearSecurityStateIcon() {
- mSecurityState.setCompoundDrawablePadding(0);
- mSecurityState.setCompoundDrawables(null, null, null, null);
- }
-
- private void setSecurityStateIcon(int resource, int factor) {
- final Drawable stateIcon = ContextCompat.getDrawable(mContext, resource);
- stateIcon.setBounds(0, 0, stateIcon.getIntrinsicWidth() / factor, stateIcon.getIntrinsicHeight() / factor);
- mSecurityState.setCompoundDrawables(stateIcon, null, null, null);
- mSecurityState.setCompoundDrawablePadding((int) mResources.getDimension(R.dimen.doorhanger_drawable_padding));
- }
- private void updateIdentityInformation(final SiteIdentity siteIdentity) {
- String owner = siteIdentity.getOwner();
- if (owner == null) {
- mOwner.setVisibility(View.GONE);
- mOwnerSupplemental.setVisibility(View.GONE);
- } else {
- mOwner.setVisibility(View.VISIBLE);
- mOwner.setText(owner);
-
- // Supplemental data is optional.
- final String supplemental = siteIdentity.getSupplemental();
- if (!TextUtils.isEmpty(supplemental)) {
- mOwnerSupplemental.setText(supplemental);
- mOwnerSupplemental.setVisibility(View.VISIBLE);
- } else {
- mOwnerSupplemental.setVisibility(View.GONE);
- }
- }
-
- final String verifier = siteIdentity.getVerifier();
- mVerifier.setText(verifier);
- }
-
- private void addTrackingContentNotification(boolean blocked) {
- // Remove any existing tracking content notification.
- removeTrackingContentNotification();
-
- final DoorhangerConfig config = new DoorhangerConfig(DoorHanger.Type.TRACKING, mContentButtonClickListener);
-
- final int icon = blocked ? R.drawable.shield_enabled : R.drawable.shield_disabled;
-
- final JSONObject options = new JSONObject();
- final JSONObject tracking = new JSONObject();
- try {
- tracking.put("enabled", blocked);
- options.put("tracking_protection", tracking);
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error adding tracking protection options", e);
- }
- config.setOptions(options);
-
- config.setLink(mContext.getString(R.string.learn_more), TRACKING_CONTENT_SUPPORT_URL);
-
- addNotificationButtons(config, blocked);
-
- mTrackingContentNotification = DoorHanger.Get(mContext, config);
-
- mTrackingContentNotification.setIcon(icon);
-
- mContent.addView(mTrackingContentNotification);
- mDivider.setVisibility(View.VISIBLE);
- }
-
- private void removeTrackingContentNotification() {
- if (mTrackingContentNotification != null) {
- mContent.removeView(mTrackingContentNotification);
- mTrackingContentNotification = null;
- }
- }
-
- private void addNotificationButtons(DoorhangerConfig config, boolean blocked) {
- if (blocked) {
- config.setButton(mContext.getString(R.string.disable_protection), ButtonType.DISABLE.ordinal(), false);
- } else {
- config.setButton(mContext.getString(R.string.enable_protection), ButtonType.ENABLE.ordinal(), true);
- }
- }
-
- /*
- * @param identityData A JSONObject that holds the current tab's identity data.
- */
- void setSiteIdentity(SiteIdentity siteIdentity) {
- mSiteIdentity = siteIdentity;
- }
-
- @Override
- public void show() {
- if (mSiteIdentity == null) {
- Log.e(LOGTAG, "Can't show site identity popup for undefined state");
- return;
- }
-
- // Verified about: pages have the CHROMEUI SiteIdentity, however there can also
- // be unverified about: pages for which "This site's identity is unknown" or
- // "This is a secure Firefox page" are both misleading, so don't show a popup.
- final Tab selectedTab = Tabs.getInstance().getSelectedTab();
- if (selectedTab != null &&
- AboutPages.isAboutPage(selectedTab.getURL()) &&
- mSiteIdentity.getSecurityMode() != SecurityMode.CHROMEUI) {
- Log.d(LOGTAG, "We don't show site identity popups for unverified about: pages");
- return;
- }
-
- updateIdentity(mSiteIdentity);
-
- final TrackingMode trackingMode = mSiteIdentity.getTrackingMode();
- if (trackingMode != TrackingMode.UNKNOWN) {
- addTrackingContentNotification(trackingMode == TrackingMode.TRACKING_CONTENT_BLOCKED);
- }
-
- try {
- addSelectLoginDoorhanger(selectedTab);
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error adding selectLogin doorhanger", e);
- }
-
- if (mSiteIdentity.getSecurityMode() == SecurityMode.CHROMEUI) {
- // For about: pages we display the product icon in place of the verified/globe
- // image, hence we don't also set the favicon (for most about pages the
- // favicon is the product icon, hence we'd be showing the same icon twice).
- mTitle.setText(R.string.moz_app_displayname);
- } else {
- mTitle.setText(selectedTab.getBaseDomain());
-
- final Bitmap favicon = selectedTab.getFavicon();
- if (favicon != null) {
- final Drawable faviconDrawable = new BitmapDrawable(mResources, favicon);
- final int dimen = (int) mResources.getDimension(R.dimen.browser_toolbar_favicon_size);
- faviconDrawable.setBounds(0, 0, dimen, dimen);
-
- mTitle.setCompoundDrawables(faviconDrawable, null, null, null);
- mTitle.setCompoundDrawablePadding((int) mContext.getResources().getDimension(R.dimen.doorhanger_drawable_padding));
- }
- }
-
- showDividers();
-
- super.show();
- }
-
- // Show the right dividers
- private void showDividers() {
- final int count = mContent.getChildCount();
- DoorHanger lastVisibleDoorHanger = null;
-
- for (int i = 0; i < count; i++) {
- final View child = mContent.getChildAt(i);
-
- if (!(child instanceof DoorHanger)) {
- continue;
- }
-
- DoorHanger dh = (DoorHanger) child;
- dh.showDivider();
- if (dh.getVisibility() == View.VISIBLE) {
- lastVisibleDoorHanger = dh;
- }
- }
-
- if (lastVisibleDoorHanger != null) {
- lastVisibleDoorHanger.hideDivider();
- }
- }
-
- void destroy() {
- GeckoApp.getEventDispatcher().unregisterGeckoThreadListener(this,
- "Doorhanger:Logins",
- "Permissions:CheckResult");
- }
-
- @Override
- public void dismiss() {
- super.dismiss();
- removeTrackingContentNotification();
- removeSelectLoginDoorhanger();
- mTitle.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
- mDivider.setVisibility(View.GONE);
- }
-
- private class ContentNotificationButtonListener implements OnButtonClickListener {
- @Override
- public void onButtonClick(JSONObject response, DoorHanger doorhanger) {
- GeckoAppShell.notifyObservers("Session:Reload", response.toString());
- dismiss();
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/TabCounter.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/TabCounter.java
deleted file mode 100644
index 1e0ca516b..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/TabCounter.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.animation.Rotate3DAnimation;
-import org.mozilla.gecko.widget.themed.ThemedTextSwitcher;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.AnimationSet;
-import android.widget.ViewSwitcher;
-
-public class TabCounter extends ThemedTextSwitcher
- implements ViewSwitcher.ViewFactory {
-
- private static final float CENTER_X = 0.5f;
- private static final float CENTER_Y = 1.25f;
- private static final int DURATION = 500;
- private static final float Z_DISTANCE = 200;
-
- private final AnimationSet mFlipInForward;
- private final AnimationSet mFlipInBackward;
- private final AnimationSet mFlipOutForward;
- private final AnimationSet mFlipOutBackward;
- private final LayoutInflater mInflater;
- private final int mLayoutId;
-
- private int mCount;
- public static final int MAX_VISIBLE_TABS = 99;
- public static final String SO_MANY_TABS_OPEN = "∞";
-
- private enum FadeMode {
- FADE_IN,
- FADE_OUT
- }
-
- public TabCounter(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabCounter);
- mLayoutId = a.getResourceId(R.styleable.TabCounter_android_layout, R.layout.tabs_counter);
- a.recycle();
-
- mInflater = LayoutInflater.from(context);
-
- mFlipInForward = createAnimation(-90, 0, FadeMode.FADE_IN, -1 * Z_DISTANCE, false);
- mFlipInBackward = createAnimation(90, 0, FadeMode.FADE_IN, Z_DISTANCE, false);
- mFlipOutForward = createAnimation(0, -90, FadeMode.FADE_OUT, -1 * Z_DISTANCE, true);
- mFlipOutBackward = createAnimation(0, 90, FadeMode.FADE_OUT, Z_DISTANCE, true);
-
- removeAllViews();
- setFactory(this);
-
- if (Versions.feature16Plus) {
- // This adds the TextSwitcher to the a11y node tree, where we in turn
- // could make it return an empty info node. If we don't do this the
- // TextSwitcher's child TextViews get picked up, and we don't want
- // that since the tabs ImageButton is already properly labeled for
- // accessibility.
- setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- setAccessibilityDelegate(new View.AccessibilityDelegate() {
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {}
- });
- }
- }
-
- void setCountWithAnimation(int count) {
- // Don't animate from initial state
- if (mCount == 0) {
- setCount(count);
- return;
- }
-
- if (mCount == count) {
- return;
- }
-
- // don't animate if there are still over MAX_VISIBLE_TABS tabs open
- if (mCount > MAX_VISIBLE_TABS && count > MAX_VISIBLE_TABS) {
- mCount = count;
- return;
- }
-
- if (count < mCount) {
- setInAnimation(mFlipInBackward);
- setOutAnimation(mFlipOutForward);
- } else {
- setInAnimation(mFlipInForward);
- setOutAnimation(mFlipOutBackward);
- }
-
- // Eliminate screen artifact. Set explicit In/Out animation pair order. This will always
- // animate pair in In->Out child order, prevent alternating use of the Out->In case.
- setDisplayedChild(0);
-
- // Set In value, trigger animation to Out value
- setCurrentText(formatForDisplay(mCount));
- setText(formatForDisplay(count));
-
- mCount = count;
- }
-
- private String formatForDisplay(int count) {
- if (count > MAX_VISIBLE_TABS) {
- return SO_MANY_TABS_OPEN;
- }
- return String.valueOf(count);
- }
-
- void setCount(int count) {
- setCurrentText(formatForDisplay(count));
- mCount = count;
- }
-
- // Alpha animations in editing mode cause action bar corruption on the
- // Nexus 7 (bug 961749). As a workaround, skip these animations in editing
- // mode.
- void onEnterEditingMode() {
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- getChildAt(i).clearAnimation();
- }
- }
-
- private AnimationSet createAnimation(float startAngle, float endAngle,
- FadeMode fadeMode,
- float zEnd, boolean reverse) {
- final Context context = getContext();
- AnimationSet set = new AnimationSet(context, null);
- set.addAnimation(new Rotate3DAnimation(startAngle, endAngle, CENTER_X, CENTER_Y, zEnd, reverse));
- set.addAnimation(fadeMode == FadeMode.FADE_IN ? new AlphaAnimation(0.0f, 1.0f) :
- new AlphaAnimation(1.0f, 0.0f));
- set.setDuration(DURATION);
- set.setInterpolator(context, android.R.anim.accelerate_interpolator);
- return set;
- }
-
- @Override
- public View makeView() {
- return mInflater.inflate(mLayoutId, null);
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
deleted file mode 100644
index 163ed4a51..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
+++ /dev/null
@@ -1,530 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import java.util.Arrays;
-import java.util.EnumSet;
-import java.util.List;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.reader.ReaderModeUtils;
-import org.mozilla.gecko.SiteIdentity;
-import org.mozilla.gecko.SiteIdentity.MixedMode;
-import org.mozilla.gecko.SiteIdentity.SecurityMode;
-import org.mozilla.gecko.SiteIdentity.TrackingMode;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.animation.ViewHelper;
-import org.mozilla.gecko.toolbar.BrowserToolbarTabletBase.ForwardButtonAnimation;
-import org.mozilla.gecko.Experiments;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.StringUtils;
-import org.mozilla.gecko.widget.themed.ThemedLinearLayout;
-import org.mozilla.gecko.widget.themed.ThemedTextView;
-
-import android.content.Context;
-import android.os.SystemClock;
-import android.support.annotation.NonNull;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-import android.text.style.ForegroundColorSpan;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.Button;
-import android.widget.ImageButton;
-
-import com.keepsafe.switchboard.SwitchBoard;
-
-/**
-* {@code ToolbarDisplayLayout} is the UI for when the toolbar is in
-* display state. It's used to display the state of the currently selected
-* tab. It should always be updated through a single entry point
-* (updateFromTab) and should never track any tab events or gecko messages
-* on its own to keep it as dumb as possible.
-*
-* The UI has two possible modes: progress and display which are triggered
-* when UpdateFlags.PROGRESS is used depending on the current tab state.
-* The progress mode is triggered when the tab is loading a page. Display mode
-* is used otherwise.
-*
-* {@code ToolbarDisplayLayout} is meant to be owned by {@code BrowserToolbar}
-* which is the main event bus for the toolbar subsystem.
-*/
-public class ToolbarDisplayLayout extends ThemedLinearLayout {
-
- private static final String LOGTAG = "GeckoToolbarDisplayLayout";
- private boolean mTrackingProtectionEnabled;
-
- // To be used with updateFromTab() to allow the caller
- // to give enough context for the requested state change.
- enum UpdateFlags {
- TITLE,
- FAVICON,
- PROGRESS,
- SITE_IDENTITY,
- PRIVATE_MODE,
-
- // Disable any animation that might be
- // triggered from this state change. Mostly
- // used on tab switches, see BrowserToolbar.
- DISABLE_ANIMATIONS
- }
-
- private enum UIMode {
- PROGRESS,
- DISPLAY
- }
-
- interface OnStopListener {
- Tab onStop();
- }
-
- interface OnTitleChangeListener {
- void onTitleChange(CharSequence title);
- }
-
- private final BrowserApp mActivity;
-
- private UIMode mUiMode;
-
- private boolean mIsAttached;
-
- private final ThemedTextView mTitle;
- private final int mTitlePadding;
- private ToolbarPrefs mPrefs;
- private OnTitleChangeListener mTitleChangeListener;
-
- private final ImageButton mSiteSecurity;
-
- private final ImageButton mStop;
- private OnStopListener mStopListener;
-
- private final PageActionLayout mPageActionLayout;
-
- private final SiteIdentityPopup mSiteIdentityPopup;
- private int mSecurityImageLevel;
-
- // Security level constants, which map to the icons / levels defined in:
- // http://dxr.mozilla.org/mozilla-central/source/mobile/android/base/java/org/mozilla/gecko/resources/drawable/site_security_level.xml
- // Default level (unverified pages) - globe icon:
- private static final int LEVEL_DEFAULT_GLOBE = 0;
- // Levels for displaying Mixed Content state icons.
- private static final int LEVEL_WARNING_MINOR = 3;
- private static final int LEVEL_LOCK_DISABLED = 4;
- // Levels for displaying Tracking Protection state icons.
- private static final int LEVEL_SHIELD_ENABLED = 5;
- private static final int LEVEL_SHIELD_DISABLED = 6;
- // Icon used for about:home
- private static final int LEVEL_SEARCH_ICON = 999;
-
- private final ForegroundColorSpan mUrlColorSpan;
- private final ForegroundColorSpan mPrivateUrlColorSpan;
- private final ForegroundColorSpan mBlockedColorSpan;
- private final ForegroundColorSpan mDomainColorSpan;
- private final ForegroundColorSpan mPrivateDomainColorSpan;
- private final ForegroundColorSpan mCertificateOwnerColorSpan;
-
- public ToolbarDisplayLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- setOrientation(HORIZONTAL);
-
- mActivity = (BrowserApp) context;
-
- LayoutInflater.from(context).inflate(R.layout.toolbar_display_layout, this);
-
- mTitle = (ThemedTextView) findViewById(R.id.url_bar_title);
- mTitlePadding = mTitle.getPaddingRight();
-
- mUrlColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_urltext));
- mPrivateUrlColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_urltext_private));
- mBlockedColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_blockedtext));
- mDomainColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_domaintext));
- mPrivateDomainColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.url_bar_domaintext_private));
- mCertificateOwnerColorSpan = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.affirmative_green));
-
- mSiteSecurity = (ImageButton) findViewById(R.id.site_security);
-
- mSiteIdentityPopup = new SiteIdentityPopup(mActivity);
- mSiteIdentityPopup.setAnchor(this);
- mSiteIdentityPopup.setOnVisibilityChangeListener(mActivity);
-
- mStop = (ImageButton) findViewById(R.id.stop);
- mPageActionLayout = (PageActionLayout) findViewById(R.id.page_action_layout);
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- mIsAttached = true;
-
- mSiteSecurity.setOnClickListener(new Button.OnClickListener() {
- @Override
- public void onClick(View view) {
- mSiteIdentityPopup.show();
- }
- });
-
- mStop.setOnClickListener(new Button.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mStopListener != null) {
- // Force toolbar to switch to Display mode
- // immediately based on the stopped tab.
- final Tab tab = mStopListener.onStop();
- if (tab != null) {
- updateUiMode(UIMode.DISPLAY);
- }
- }
- }
- });
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mIsAttached = false;
- }
-
- @Override
- public void setNextFocusDownId(int nextId) {
- mStop.setNextFocusDownId(nextId);
- mSiteSecurity.setNextFocusDownId(nextId);
- mPageActionLayout.setNextFocusDownId(nextId);
- }
-
- void setToolbarPrefs(final ToolbarPrefs prefs) {
- mPrefs = prefs;
- }
-
- void updateFromTab(@NonNull Tab tab, EnumSet<UpdateFlags> flags) {
- // Several parts of ToolbarDisplayLayout's state depends
- // on the views being attached to the view tree.
- if (!mIsAttached) {
- return;
- }
-
- if (flags.contains(UpdateFlags.TITLE)) {
- updateTitle(tab);
- }
-
- if (flags.contains(UpdateFlags.SITE_IDENTITY)) {
- updateSiteIdentity(tab);
- }
-
- if (flags.contains(UpdateFlags.PROGRESS)) {
- updateProgress(tab);
- }
-
- if (flags.contains(UpdateFlags.PRIVATE_MODE)) {
- mTitle.setPrivateMode(tab.isPrivate());
- }
- }
-
- void setTitle(CharSequence title) {
- mTitle.setText(title);
-
- if (mTitleChangeListener != null) {
- mTitleChangeListener.onTitleChange(title);
- }
- }
-
- private void updateTitle(@NonNull Tab tab) {
- // Keep the title unchanged if there's no selected tab,
- // or if the tab is entering reader mode.
- if (tab.isEnteringReaderMode()) {
- return;
- }
-
- final String url = tab.getURL();
-
- // Setting a null title will ensure we just see the
- // "Enter Search or Address" placeholder text.
- if (AboutPages.isTitlelessAboutPage(url)) {
- setTitle(null);
- setContentDescription(null);
- return;
- }
-
- // Show the about:blocked page title in red, regardless of prefs
- if (tab.getErrorType() == Tab.ErrorType.BLOCKED) {
- final String title = tab.getDisplayTitle();
-
- final SpannableStringBuilder builder = new SpannableStringBuilder(title);
- builder.setSpan(mBlockedColorSpan, 0, title.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
-
- setTitle(builder);
- setContentDescription(null);
- return;
- }
-
- final String baseDomain = tab.getBaseDomain();
-
- String strippedURL = stripAboutReaderURL(url);
-
- final boolean isHttpOrHttps = StringUtils.isHttpOrHttps(strippedURL);
-
- if (mPrefs.shouldTrimUrls()) {
- strippedURL = StringUtils.stripCommonSubdomains(StringUtils.stripScheme(strippedURL));
- }
-
- // The URL bar does not support RTL currently (See bug 928688 and meta bug 702845).
- // Displaying a URL using RTL (or mixed) characters can lead to an undesired reordering
- // of elements of the URL. That's why we are forcing the URL to use LTR (bug 1284372).
- strippedURL = StringUtils.forceLTR(strippedURL);
-
- // This value is not visible to screen readers but we rely on it when running UI tests. Screen
- // readers will instead focus BrowserToolbar and read the "base domain" from there. UI tests
- // will read the content description to obtain the full URL for performing assertions.
- setContentDescription(strippedURL);
-
- final SiteIdentity siteIdentity = tab.getSiteIdentity();
- if (siteIdentity.hasOwner() && SwitchBoard.isInExperiment(mActivity, Experiments.URLBAR_SHOW_EV_CERT_OWNER)) {
- // Show Owner of EV certificate as title
- updateTitleFromSiteIdentity(siteIdentity);
- } else if (isHttpOrHttps && !HardwareUtils.isTablet() && !TextUtils.isEmpty(baseDomain)
- && SwitchBoard.isInExperiment(mActivity, Experiments.URLBAR_SHOW_ORIGIN_ONLY)) {
- // Show just the base domain as title
- setTitle(baseDomain);
- } else {
- // Display full URL with base domain highlighted as title
- updateAndColorTitleFromFullURL(strippedURL, baseDomain, tab.isPrivate());
- }
- }
-
- private void updateTitleFromSiteIdentity(SiteIdentity siteIdentity) {
- final String title;
-
- if (siteIdentity.hasCountry()) {
- title = String.format("%s (%s)", siteIdentity.getOwner(), siteIdentity.getCountry());
- } else {
- title = siteIdentity.getOwner();
- }
-
- final SpannableString spannable = new SpannableString(title);
- spannable.setSpan(mCertificateOwnerColorSpan, 0, title.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-
- setTitle(spannable);
- }
-
- private void updateAndColorTitleFromFullURL(String url, String baseDomain, boolean isPrivate) {
- if (TextUtils.isEmpty(baseDomain)) {
- setTitle(url);
- return;
- }
-
- int index = url.indexOf(baseDomain);
- if (index == -1) {
- setTitle(url);
- return;
- }
-
- final SpannableStringBuilder builder = new SpannableStringBuilder(url);
-
- builder.setSpan(isPrivate ? mPrivateUrlColorSpan : mUrlColorSpan, 0, url.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
- builder.setSpan(isPrivate ? mPrivateDomainColorSpan : mDomainColorSpan,
- index, index + baseDomain.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
-
- setTitle(builder);
- }
-
- private String stripAboutReaderURL(final String url) {
- if (!AboutPages.isAboutReader(url)) {
- return url;
- }
-
- return ReaderModeUtils.stripAboutReaderUrl(url);
- }
-
- private void updateSiteIdentity(@NonNull Tab tab) {
- final SiteIdentity siteIdentity = tab.getSiteIdentity();
-
- mSiteIdentityPopup.setSiteIdentity(siteIdentity);
-
- final SecurityMode securityMode;
- final MixedMode activeMixedMode;
- final MixedMode displayMixedMode;
- final TrackingMode trackingMode;
- if (siteIdentity == null) {
- securityMode = SecurityMode.UNKNOWN;
- activeMixedMode = MixedMode.UNKNOWN;
- displayMixedMode = MixedMode.UNKNOWN;
- trackingMode = TrackingMode.UNKNOWN;
- } else {
- securityMode = siteIdentity.getSecurityMode();
- activeMixedMode = siteIdentity.getMixedModeActive();
- displayMixedMode = siteIdentity.getMixedModeDisplay();
- trackingMode = siteIdentity.getTrackingMode();
- }
-
- // This is a bit tricky, but we have one icon and three potential indicators.
- // Default to the identity level
- int imageLevel = securityMode.ordinal();
-
- // about: pages should default to having no icon too (the same as SecurityMode.UNKNOWN), however
- // SecurityMode.CHROMEUI has a different ordinal - hence we need to manually reset it here.
- // (We then continue and process the tracking / mixed content icons as usual, even for about: pages, as they
- // can still load external sites.)
- if (securityMode == SecurityMode.CHROMEUI) {
- imageLevel = LEVEL_DEFAULT_GLOBE; // == SecurityMode.UNKNOWN.ordinal()
- }
-
- // Check to see if any protection was overridden first
- if (AboutPages.isTitlelessAboutPage(tab.getURL())) {
- // We always want to just show a search icon on about:home
- imageLevel = LEVEL_SEARCH_ICON;
- } else if (trackingMode == TrackingMode.TRACKING_CONTENT_LOADED) {
- imageLevel = LEVEL_SHIELD_DISABLED;
- } else if (trackingMode == TrackingMode.TRACKING_CONTENT_BLOCKED) {
- imageLevel = LEVEL_SHIELD_ENABLED;
- } else if (activeMixedMode == MixedMode.MIXED_CONTENT_LOADED) {
- imageLevel = LEVEL_LOCK_DISABLED;
- } else if (displayMixedMode == MixedMode.MIXED_CONTENT_LOADED) {
- imageLevel = LEVEL_WARNING_MINOR;
- }
-
- if (mSecurityImageLevel != imageLevel) {
- mSecurityImageLevel = imageLevel;
- mSiteSecurity.setImageLevel(mSecurityImageLevel);
- updatePageActions();
- }
-
- mTrackingProtectionEnabled = trackingMode == TrackingMode.TRACKING_CONTENT_BLOCKED;
- }
-
- private void updateProgress(@NonNull Tab tab) {
- final boolean shouldShowThrobber = tab.getState() == Tab.STATE_LOADING;
-
- updateUiMode(shouldShowThrobber ? UIMode.PROGRESS : UIMode.DISPLAY);
-
- if (Tab.STATE_SUCCESS == tab.getState() && mTrackingProtectionEnabled) {
- mActivity.showTrackingProtectionPromptIfApplicable();
- }
- }
-
- private void updateUiMode(UIMode uiMode) {
- if (mUiMode == uiMode) {
- return;
- }
-
- mUiMode = uiMode;
-
- // The "Throbber start" and "Throbber stop" log messages in this method
- // are needed by S1/S2 tests (http://mrcote.info/phonedash/#).
- // See discussion in Bug 804457. Bug 805124 tracks paring these down.
- if (mUiMode == UIMode.PROGRESS) {
- Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber start");
- } else {
- Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - Throbber stop");
- }
-
- updatePageActions();
- }
-
- private void updatePageActions() {
- final boolean isShowingProgress = (mUiMode == UIMode.PROGRESS);
-
- mStop.setVisibility(isShowingProgress ? View.VISIBLE : View.GONE);
- mPageActionLayout.setVisibility(!isShowingProgress ? View.VISIBLE : View.GONE);
-
- // We want title to fill the whole space available for it when there are icons
- // being shown on the right side of the toolbar as the icons already have some
- // padding in them. This is just to avoid wasting space when icons are shown.
- mTitle.setPadding(0, 0, (!isShowingProgress ? mTitlePadding : 0), 0);
- }
-
- List<View> getFocusOrder() {
- return Arrays.asList(mSiteSecurity, mPageActionLayout, mStop);
- }
-
- void setOnStopListener(OnStopListener listener) {
- mStopListener = listener;
- }
-
- void setOnTitleChangeListener(OnTitleChangeListener listener) {
- mTitleChangeListener = listener;
- }
-
- /**
- * Update the Site Identity popup anchor.
- *
- * Tablet UI has a tablet-specific doorhanger anchor, so update it after all the views
- * are inflated.
- * @param view View to use as the anchor for the Site Identity popup.
- */
- void updateSiteIdentityAnchor(View view) {
- mSiteIdentityPopup.setAnchor(view);
- }
-
- void prepareForwardAnimation(PropertyAnimator anim, ForwardButtonAnimation animation, int width) {
- if (animation == ForwardButtonAnimation.HIDE) {
- // We animate these items individually, rather than this entire view,
- // so that we don't animate certain views, e.g. the stop button.
- anim.attach(mTitle,
- PropertyAnimator.Property.TRANSLATION_X,
- 0);
- anim.attach(mSiteSecurity,
- PropertyAnimator.Property.TRANSLATION_X,
- 0);
-
- // We're hiding the forward button. We're going to reset the margin before
- // the animation starts, so we shift these items to the right so that they don't
- // appear to move initially.
- ViewHelper.setTranslationX(mTitle, width);
- ViewHelper.setTranslationX(mSiteSecurity, width);
- } else {
- anim.attach(mTitle,
- PropertyAnimator.Property.TRANSLATION_X,
- width);
- anim.attach(mSiteSecurity,
- PropertyAnimator.Property.TRANSLATION_X,
- width);
- }
- }
-
- void finishForwardAnimation() {
- ViewHelper.setTranslationX(mTitle, 0);
- ViewHelper.setTranslationX(mSiteSecurity, 0);
- }
-
- void prepareStartEditingAnimation() {
- // Hide page actions/stop buttons immediately
- ViewHelper.setAlpha(mPageActionLayout, 0);
- ViewHelper.setAlpha(mStop, 0);
- }
-
- void prepareStopEditingAnimation(PropertyAnimator anim) {
- // Fade toolbar buttons (page actions, stop) after the entry
- // is shrunk back to its original size.
- anim.attach(mPageActionLayout,
- PropertyAnimator.Property.ALPHA,
- 1);
-
- anim.attach(mStop,
- PropertyAnimator.Property.ALPHA,
- 1);
- }
-
- boolean dismissSiteIdentityPopup() {
- if (mSiteIdentityPopup != null && mSiteIdentityPopup.isShowing()) {
- mSiteIdentityPopup.dismiss();
- return true;
- }
-
- return false;
- }
-
- void destroy() {
- mSiteIdentityPopup.destroy();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarEditLayout.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarEditLayout.java
deleted file mode 100644
index c9731a401..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarEditLayout.java
+++ /dev/null
@@ -1,348 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.speech.RecognizerIntent;
-import android.widget.Button;
-import android.widget.ImageButton;
-import org.mozilla.gecko.ActivityHandlerHelper;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.toolbar.BrowserToolbar.OnCommitListener;
-import org.mozilla.gecko.toolbar.BrowserToolbar.OnDismissListener;
-import org.mozilla.gecko.toolbar.BrowserToolbar.OnFilterListener;
-import org.mozilla.gecko.toolbar.BrowserToolbar.TabEditingState;
-import org.mozilla.gecko.util.ActivityResultHandler;
-import org.mozilla.gecko.util.DrawableUtil;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.StringUtils;
-import org.mozilla.gecko.util.InputOptionsUtils;
-import org.mozilla.gecko.widget.themed.ThemedLinearLayout;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.ImageView;
-
-import java.util.List;
-
-/**
-* {@code ToolbarEditLayout} is the UI for when the toolbar is in
-* edit state. It controls a text entry ({@code ToolbarEditText})
-* and its matching 'go' button which changes depending on the
-* current type of text in the entry.
-*/
-public class ToolbarEditLayout extends ThemedLinearLayout {
-
- public interface OnSearchStateChangeListener {
- public void onSearchStateChange(boolean isActive);
- }
-
- private final ImageView mSearchIcon;
-
- private final ToolbarEditText mEditText;
-
- private final ImageButton mVoiceInput;
- private final ImageButton mQrCode;
-
- private OnFocusChangeListener mFocusChangeListener;
-
- private boolean showKeyboardOnFocus = false; // Indicates if we need to show the keyboard after the app resumes
-
- public ToolbarEditLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- setOrientation(HORIZONTAL);
-
- LayoutInflater.from(context).inflate(R.layout.toolbar_edit_layout, this);
- mSearchIcon = (ImageView) findViewById(R.id.search_icon);
-
- mEditText = (ToolbarEditText) findViewById(R.id.url_edit_text);
-
- mVoiceInput = (ImageButton) findViewById(R.id.mic);
- mQrCode = (ImageButton) findViewById(R.id.qrcode);
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- if (HardwareUtils.isTablet()) {
- mSearchIcon.setVisibility(View.VISIBLE);
- }
-
- mEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (mFocusChangeListener != null) {
- mFocusChangeListener.onFocusChange(ToolbarEditLayout.this, hasFocus);
-
- // Checking if voice and QR code input are enabled each time the user taps on the URL bar
- if (hasFocus) {
- if (voiceIsEnabled(getContext(), getResources().getString(R.string.voicesearch_prompt))) {
- mVoiceInput.setVisibility(View.VISIBLE);
- } else {
- mVoiceInput.setVisibility(View.GONE);
- }
-
- if (qrCodeIsEnabled(getContext())) {
- mQrCode.setVisibility(View.VISIBLE);
- } else {
- mQrCode.setVisibility(View.GONE);
- }
- }
- }
- }
- });
-
- mEditText.setOnSearchStateChangeListener(new OnSearchStateChangeListener() {
- @Override
- public void onSearchStateChange(boolean isActive) {
- updateSearchIcon(isActive);
- }
- });
-
- mVoiceInput.setOnClickListener(new Button.OnClickListener() {
- @Override
- public void onClick(View v) {
- launchVoiceRecognizer();
- }
- });
-
- mQrCode.setOnClickListener(new Button.OnClickListener() {
- @Override
- public void onClick(View v) {
- launchQRCodeReader();
- }
- });
-
- // Set an inactive search icon on tablet devices when in editing mode
- updateSearchIcon(false);
- }
-
- /**
- * Update the search icon at the left of the edittext based
- * on its state.
- *
- * @param isActive The state of the edittext. Active is when the initialized
- * text has changed and is not empty.
- */
- void updateSearchIcon(boolean isActive) {
- if (!HardwareUtils.isTablet()) {
- return;
- }
-
- // When on tablet show a magnifying glass in editing mode
- final int searchDrawableId = R.drawable.search_icon_active;
- final Drawable searchDrawable;
- if (!isActive) {
- searchDrawable = DrawableUtil.tintDrawableWithColorRes(getContext(), searchDrawableId, R.color.placeholder_grey);
- } else {
- if (isPrivateMode()) {
- searchDrawable = DrawableUtil.tintDrawableWithColorRes(getContext(), searchDrawableId, R.color.tabs_tray_icon_grey);
- } else {
- searchDrawable = getResources().getDrawable(searchDrawableId);
- }
- }
-
- mSearchIcon.setImageDrawable(searchDrawable);
- }
-
- @Override
- public void setOnFocusChangeListener(OnFocusChangeListener listener) {
- mFocusChangeListener = listener;
- }
-
- @Override
- public void setEnabled(boolean enabled) {
- super.setEnabled(enabled);
- mEditText.setEnabled(enabled);
- }
-
- @Override
- public void setPrivateMode(boolean isPrivate) {
- super.setPrivateMode(isPrivate);
- mEditText.setPrivateMode(isPrivate);
- }
-
- /**
- * Called when the parent gains focus (on app launch and resume)
- */
- public void onParentFocus() {
- if (showKeyboardOnFocus) {
- showKeyboardOnFocus = false;
-
- Activity activity = GeckoAppShell.getGeckoInterface().getActivity();
- activity.runOnUiThread(new Runnable() {
- public void run() {
- mEditText.requestFocus();
- showSoftInput();
- }
- });
- }
-
- // Checking if qr code is supported after resuming the app
- if (qrCodeIsEnabled(getContext())) {
- mQrCode.setVisibility(View.VISIBLE);
- } else {
- mQrCode.setVisibility(View.GONE);
- }
- }
-
- void setToolbarPrefs(final ToolbarPrefs prefs) {
- mEditText.setToolbarPrefs(prefs);
- }
-
- private void showSoftInput() {
- InputMethodManager imm =
- (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT);
- }
-
- void prepareShowAnimation(final PropertyAnimator animator) {
- if (animator == null) {
- mEditText.requestFocus();
- showSoftInput();
- return;
- }
-
- animator.addPropertyAnimationListener(new PropertyAnimationListener() {
- @Override
- public void onPropertyAnimationStart() {
- mEditText.requestFocus();
- }
-
- @Override
- public void onPropertyAnimationEnd() {
- showSoftInput();
- }
- });
- }
-
- void setOnCommitListener(OnCommitListener listener) {
- mEditText.setOnCommitListener(listener);
- }
-
- void setOnDismissListener(OnDismissListener listener) {
- mEditText.setOnDismissListener(listener);
- }
-
- void setOnFilterListener(OnFilterListener listener) {
- mEditText.setOnFilterListener(listener);
- }
-
- void onEditSuggestion(String suggestion) {
- mEditText.setText(suggestion);
- mEditText.setSelection(mEditText.getText().length());
- mEditText.requestFocus();
-
- showSoftInput();
- }
-
- void setText(String text) {
- mEditText.setText(text);
- }
-
- String getText() {
- return mEditText.getText().toString();
- }
-
- protected void saveTabEditingState(final TabEditingState editingState) {
- editingState.lastEditingText = mEditText.getNonAutocompleteText();
- editingState.selectionStart = mEditText.getSelectionStart();
- editingState.selectionEnd = mEditText.getSelectionEnd();
- }
-
- protected void restoreTabEditingState(final TabEditingState editingState) {
- mEditText.setText(editingState.lastEditingText);
- mEditText.setSelection(editingState.selectionStart, editingState.selectionEnd);
- }
-
- private boolean voiceIsEnabled(Context context, String prompt) {
- final boolean voiceIsSupported = InputOptionsUtils.supportsVoiceRecognizer(context, prompt);
- if (!voiceIsSupported) {
- return false;
- }
- return GeckoSharedPrefs.forApp(context)
- .getBoolean(GeckoPreferences.PREFS_VOICE_INPUT_ENABLED, true);
- }
-
- private void launchVoiceRecognizer() {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "voice_input_launch");
- final Intent intent = InputOptionsUtils.createVoiceRecognizerIntent(getResources().getString(R.string.voicesearch_prompt));
-
- Activity activity = GeckoAppShell.getGeckoInterface().getActivity();
- ActivityHandlerHelper.startIntentForActivity(activity, intent, new ActivityResultHandler() {
- @Override
- public void onActivityResult(int resultCode, Intent data) {
- if (resultCode != Activity.RESULT_OK) {
- return;
- }
-
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "voice_input_success");
- // We have RESULT_OK, not RESULT_NO_MATCH so it should be safe to assume that
- // we have at least one match. We only need one: this will be
- // used for showing the user search engines with this search term in it.
- List<String> voiceStrings = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
- String text = voiceStrings.get(0);
- mEditText.setText(text);
- mEditText.setSelection(0, text.length());
-
- final InputMethodManager imm =
- (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT);
- }
- });
- }
-
- private boolean qrCodeIsEnabled(Context context) {
- final boolean qrCodeIsSupported = InputOptionsUtils.supportsQrCodeReader(context);
- if (!qrCodeIsSupported) {
- return false;
- }
- return GeckoSharedPrefs.forApp(context)
- .getBoolean(GeckoPreferences.PREFS_QRCODE_ENABLED, true);
- }
-
- private void launchQRCodeReader() {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "qrcode_input_launch");
- final Intent intent = InputOptionsUtils.createQRCodeReaderIntent();
-
- Activity activity = GeckoAppShell.getGeckoInterface().getActivity();
- ActivityHandlerHelper.startIntentForActivity(activity, intent, new ActivityResultHandler() {
- @Override
- public void onActivityResult(int resultCode, Intent intent) {
- if (resultCode == Activity.RESULT_OK) {
- String text = intent.getStringExtra("SCAN_RESULT");
- if (!StringUtils.isSearchQuery(text, false)) {
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "qrcode_input_success");
- mEditText.setText(text);
- mEditText.selectAll();
-
- // Queuing up the keyboard show action.
- // At this point the app has not resumed yet, and trying to show
- // the keyboard will fail.
- showKeyboardOnFocus = true;
- }
- }
- // We can get the SCAN_RESULT_FORMAT, SCAN_RESULT_BYTES,
- // SCAN_RESULT_ORIENTATION and SCAN_RESULT_ERROR_CORRECTION_LEVEL
- // as well as the actual result, which may hold a URL.
- }
- });
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarEditText.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarEditText.java
deleted file mode 100644
index b385f815a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarEditText.java
+++ /dev/null
@@ -1,630 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import org.mozilla.gecko.AboutPages;
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.CustomEditText;
-import org.mozilla.gecko.InputMethods;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.toolbar.BrowserToolbar.OnCommitListener;
-import org.mozilla.gecko.toolbar.BrowserToolbar.OnDismissListener;
-import org.mozilla.gecko.toolbar.BrowserToolbar.OnFilterListener;
-import org.mozilla.gecko.toolbar.ToolbarEditLayout.OnSearchStateChangeListener;
-import org.mozilla.gecko.util.GamepadUtils;
-import org.mozilla.gecko.util.StringUtils;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.text.Editable;
-import android.text.NoCopySpan;
-import android.text.Selection;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.text.style.BackgroundColorSpan;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.inputmethod.BaseInputConnection;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputConnectionWrapper;
-import android.view.inputmethod.InputMethodManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.TextView;
-
-/**
-* {@code ToolbarEditText} is the text entry used when the toolbar
-* is in edit state. It handles all the necessary input method machinery.
-* It's meant to be owned by {@code ToolbarEditLayout}.
-*/
-public class ToolbarEditText extends CustomEditText
- implements AutocompleteHandler {
-
- private static final String LOGTAG = "GeckoToolbarEditText";
- private static final NoCopySpan AUTOCOMPLETE_SPAN = new NoCopySpan.Concrete();
-
- private final Context mContext;
-
- private OnCommitListener mCommitListener;
- private OnDismissListener mDismissListener;
- private OnFilterListener mFilterListener;
- private OnSearchStateChangeListener mSearchStateChangeListener;
-
- private ToolbarPrefs mPrefs;
-
- // The previous autocomplete result returned to us
- private String mAutoCompleteResult = "";
- // Length of the user-typed portion of the result
- private int mAutoCompletePrefixLength;
- // If text change is due to us setting autocomplete
- private boolean mSettingAutoComplete;
- // Spans used for marking the autocomplete text
- private Object[] mAutoCompleteSpans;
- // Do not process autocomplete result
- private boolean mDiscardAutoCompleteResult;
-
- public ToolbarEditText(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- }
-
- void setOnCommitListener(OnCommitListener listener) {
- mCommitListener = listener;
- }
-
- void setOnDismissListener(OnDismissListener listener) {
- mDismissListener = listener;
- }
-
- void setOnFilterListener(OnFilterListener listener) {
- mFilterListener = listener;
- }
-
- void setOnSearchStateChangeListener(OnSearchStateChangeListener listener) {
- mSearchStateChangeListener = listener;
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- setOnKeyListener(new KeyListener());
- setOnKeyPreImeListener(new KeyPreImeListener());
- setOnSelectionChangedListener(new SelectionChangeListener());
- addTextChangedListener(new TextChangeListener());
- }
-
- @Override
- public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
-
- // Make search icon inactive when edit toolbar search term isn't a user entered
- // search term
- final boolean isActive = !TextUtils.isEmpty(getText());
- if (mSearchStateChangeListener != null) {
- mSearchStateChangeListener.onSearchStateChange(isActive);
- }
-
- if (gainFocus) {
- resetAutocompleteState();
- return;
- }
-
- removeAutocomplete(getText());
-
- final InputMethodManager imm = InputMethods.getInputMethodManager(mContext);
- try {
- imm.restartInput(this);
- imm.hideSoftInputFromWindow(getWindowToken(), 0);
- } catch (NullPointerException e) {
- Log.e(LOGTAG, "InputMethodManagerService, why are you throwing"
- + " a NullPointerException? See bug 782096", e);
- }
- }
-
- @Override
- public void setText(final CharSequence text, final TextView.BufferType type) {
- final String textString = (text == null) ? "" : text.toString();
-
- // If we're on the home or private browsing page, we don't set the "about" url.
- final CharSequence finalText;
- if (AboutPages.isAboutHome(textString) || AboutPages.isAboutPrivateBrowsing(textString)) {
- finalText = "";
- } else {
- finalText = text;
- }
-
- super.setText(finalText, type);
-
- // Any autocomplete text would have been overwritten, so reset our autocomplete states.
- resetAutocompleteState();
- }
-
- @Override
- public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
- // We need to bypass the isShown() check in the default implementation
- // for TYPE_VIEW_TEXT_SELECTION_CHANGED events so that accessibility
- // services could detect a url change.
- if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED &&
- getParent() != null && !isShown()) {
- onInitializeAccessibilityEvent(event);
- dispatchPopulateAccessibilityEvent(event);
- getParent().requestSendAccessibilityEvent(this, event);
- } else {
- super.sendAccessibilityEventUnchecked(event);
- }
- }
-
- void setToolbarPrefs(final ToolbarPrefs prefs) {
- mPrefs = prefs;
- }
-
- /**
- * Mark the start of autocomplete changes so our text change
- * listener does not react to changes in autocomplete text
- */
- private void beginSettingAutocomplete() {
- beginBatchEdit();
- mSettingAutoComplete = true;
- }
-
- /**
- * Mark the end of autocomplete changes
- */
- private void endSettingAutocomplete() {
- mSettingAutoComplete = false;
- endBatchEdit();
- }
-
- /**
- * Reset autocomplete states to their initial values
- */
- private void resetAutocompleteState() {
- mAutoCompleteSpans = new Object[] {
- // Span to mark the autocomplete text
- AUTOCOMPLETE_SPAN,
- // Span to change the autocomplete text color
- new BackgroundColorSpan(getHighlightColor())
- };
-
- mAutoCompleteResult = "";
-
- // Pretend we already autocompleted the existing text,
- // so that actions like backspacing don't trigger autocompletion.
- mAutoCompletePrefixLength = getText().length();
-
- // Show the cursor.
- setCursorVisible(true);
- }
-
- protected String getNonAutocompleteText() {
- return getNonAutocompleteText(getText());
- }
-
- /**
- * Get the portion of text that is not marked as autocomplete text.
- *
- * @param text Current text content that may include autocomplete text
- */
- private static String getNonAutocompleteText(final Editable text) {
- final int start = text.getSpanStart(AUTOCOMPLETE_SPAN);
- if (start < 0) {
- // No autocomplete text; return the whole string.
- return text.toString();
- }
-
- // Only return the portion that's not autocomplete text
- return TextUtils.substring(text, 0, start);
- }
-
- /**
- * Remove any autocomplete text
- *
- * @param text Current text content that may include autocomplete text
- */
- private boolean removeAutocomplete(final Editable text) {
- final int start = text.getSpanStart(AUTOCOMPLETE_SPAN);
- if (start < 0) {
- // No autocomplete text
- return false;
- }
-
- beginSettingAutocomplete();
-
- // When we call delete() here, the autocomplete spans we set are removed as well.
- text.delete(start, text.length());
-
- // Keep mAutoCompletePrefixLength the same because the prefix has not changed.
- // Clear mAutoCompleteResult to make sure we get fresh autocomplete text next time.
- mAutoCompleteResult = "";
-
- // Reshow the cursor.
- setCursorVisible(true);
-
- endSettingAutocomplete();
- return true;
- }
-
- /**
- * Convert any autocomplete text to regular text
- *
- * @param text Current text content that may include autocomplete text
- */
- private boolean commitAutocomplete(final Editable text) {
- final int start = text.getSpanStart(AUTOCOMPLETE_SPAN);
- if (start < 0) {
- // No autocomplete text
- return false;
- }
-
- beginSettingAutocomplete();
-
- // Remove all spans here to convert from autocomplete text to regular text
- for (final Object span : mAutoCompleteSpans) {
- text.removeSpan(span);
- }
-
- // Keep mAutoCompleteResult the same because the result has not changed.
- // Reset mAutoCompletePrefixLength because the prefix now includes the autocomplete text.
- mAutoCompletePrefixLength = text.length();
-
- // Reshow the cursor.
- setCursorVisible(true);
-
- endSettingAutocomplete();
-
- // Filter on the new text
- if (mFilterListener != null) {
- mFilterListener.onFilter(text.toString(), null);
- }
- return true;
- }
-
- /**
- * Add autocomplete text based on the result URI.
- *
- * @param result Result URI to be turned into autocomplete text
- */
- @Override
- public final void onAutocomplete(final String result) {
- // If mDiscardAutoCompleteResult is true, we temporarily disabled
- // autocomplete (due to backspacing, etc.) and we should bail early.
- if (mDiscardAutoCompleteResult) {
- return;
- }
-
- if (!isEnabled() || result == null) {
- mAutoCompleteResult = "";
- return;
- }
-
- final Editable text = getText();
- final int textLength = text.length();
- final int resultLength = result.length();
- final int autoCompleteStart = text.getSpanStart(AUTOCOMPLETE_SPAN);
- mAutoCompleteResult = result;
-
- if (autoCompleteStart > -1) {
- // Autocomplete text already exists; we should replace existing autocomplete text.
-
- // If the result and the current text don't have the same prefixes,
- // the result is stale and we should wait for the another result to come in.
- if (!TextUtils.regionMatches(result, 0, text, 0, autoCompleteStart)) {
- return;
- }
-
- beginSettingAutocomplete();
-
- // Replace the existing autocomplete text with new one.
- // replace() preserves the autocomplete spans that we set before.
- text.replace(autoCompleteStart, textLength, result, autoCompleteStart, resultLength);
-
- // Reshow the cursor if there is no longer any autocomplete text.
- if (autoCompleteStart == resultLength) {
- setCursorVisible(true);
- }
-
- endSettingAutocomplete();
-
- } else {
- // No autocomplete text yet; we should add autocomplete text
-
- // If the result prefix doesn't match the current text,
- // the result is stale and we should wait for the another result to come in.
- if (resultLength <= textLength ||
- !TextUtils.regionMatches(result, 0, text, 0, textLength)) {
- return;
- }
-
- final Object[] spans = text.getSpans(textLength, textLength, Object.class);
- final int[] spanStarts = new int[spans.length];
- final int[] spanEnds = new int[spans.length];
- final int[] spanFlags = new int[spans.length];
-
- // Save selection/composing span bounds so we can restore them later.
- for (int i = 0; i < spans.length; i++) {
- final Object span = spans[i];
- final int spanFlag = text.getSpanFlags(span);
-
- // We don't care about spans that are not selection or composing spans.
- // For those spans, spanFlag[i] will be 0 and we don't restore them.
- if ((spanFlag & Spanned.SPAN_COMPOSING) == 0 &&
- (span != Selection.SELECTION_START) &&
- (span != Selection.SELECTION_END)) {
- continue;
- }
-
- spanStarts[i] = text.getSpanStart(span);
- spanEnds[i] = text.getSpanEnd(span);
- spanFlags[i] = spanFlag;
- }
-
- beginSettingAutocomplete();
-
- // First add trailing text.
- text.append(result, textLength, resultLength);
-
- // Restore selection/composing spans.
- for (int i = 0; i < spans.length; i++) {
- final int spanFlag = spanFlags[i];
- if (spanFlag == 0) {
- // Skip if the span was ignored before.
- continue;
- }
- text.setSpan(spans[i], spanStarts[i], spanEnds[i], spanFlag);
- }
-
- // Mark added text as autocomplete text.
- for (final Object span : mAutoCompleteSpans) {
- text.setSpan(span, textLength, resultLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
-
- // Hide the cursor.
- setCursorVisible(false);
-
- // Make sure the autocomplete text is visible. If the autocomplete text is too
- // long, it would appear the cursor will be scrolled out of view. However, this
- // is not the case in practice, because EditText still makes sure the cursor is
- // still in view.
- bringPointIntoView(resultLength);
-
- endSettingAutocomplete();
- }
- }
-
- private static boolean hasCompositionString(Editable content) {
- Object[] spans = content.getSpans(0, content.length(), Object.class);
-
- if (spans != null) {
- for (Object span : spans) {
- if ((content.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) {
- // Found composition string.
- return true;
- }
- }
- }
-
- return false;
- }
-
- /**
- * Code to handle deleting autocomplete first when backspacing.
- * If there is no autocomplete text, both removeAutocomplete() and commitAutocomplete()
- * are no-ops and return false. Therefore we can use them here without checking explicitly
- * if we have autocomplete text or not.
- */
- @Override
- public InputConnection onCreateInputConnection(final EditorInfo outAttrs) {
- final InputConnection ic = super.onCreateInputConnection(outAttrs);
- if (ic == null) {
- return null;
- }
-
- return new InputConnectionWrapper(ic, false) {
- @Override
- public boolean deleteSurroundingText(final int beforeLength, final int afterLength) {
- if (removeAutocomplete(getText())) {
- // If we have autocomplete text, the cursor is at the boundary between
- // regular and autocomplete text. So regardless of which direction we
- // are deleting, we should delete the autocomplete text first.
- // Make the IME aware that we interrupted the deleteSurroundingText call,
- // by restarting the IME.
- final InputMethodManager imm = InputMethods.getInputMethodManager(mContext);
- if (imm != null) {
- imm.restartInput(ToolbarEditText.this);
- }
- return false;
- }
- return super.deleteSurroundingText(beforeLength, afterLength);
- }
-
- private boolean removeAutocompleteOnComposing(final CharSequence text) {
- final Editable editable = getText();
- final int composingStart = BaseInputConnection.getComposingSpanStart(editable);
- final int composingEnd = BaseInputConnection.getComposingSpanEnd(editable);
- // We only delete the autocomplete text when the user is backspacing,
- // i.e. when the composing text is getting shorter.
- if (composingStart >= 0 &&
- composingEnd >= 0 &&
- (composingEnd - composingStart) > text.length() &&
- removeAutocomplete(editable)) {
- // Make the IME aware that we interrupted the setComposingText call,
- // by having finishComposingText() send change notifications to the IME.
- finishComposingText();
- setComposingRegion(composingStart, composingEnd);
- return true;
- }
- return false;
- }
-
- @Override
- public boolean commitText(CharSequence text, int newCursorPosition) {
- if (removeAutocompleteOnComposing(text)) {
- return false;
- }
- return super.commitText(text, newCursorPosition);
- }
-
- @Override
- public boolean setComposingText(final CharSequence text, final int newCursorPosition) {
- if (removeAutocompleteOnComposing(text)) {
- return false;
- }
- return super.setComposingText(text, newCursorPosition);
- }
- };
- }
-
- private class SelectionChangeListener implements OnSelectionChangedListener {
- @Override
- public void onSelectionChanged(final int selStart, final int selEnd) {
- // The user has repositioned the cursor somewhere. We need to adjust
- // the autocomplete text depending on where the new cursor is.
-
- final Editable text = getText();
- final int start = text.getSpanStart(AUTOCOMPLETE_SPAN);
-
- if (mSettingAutoComplete || start < 0 || (start == selStart && start == selEnd)) {
- // Do not commit autocomplete text if there is no autocomplete text
- // or if selection is still at start of autocomplete text
- return;
- }
-
- if (selStart <= start && selEnd <= start) {
- // The cursor is in user-typed text; remove any autocomplete text.
- removeAutocomplete(text);
- } else {
- // The cursor is in the autocomplete text; commit it so it becomes regular text.
- commitAutocomplete(text);
- }
- }
- }
-
- private class TextChangeListener implements TextWatcher {
- @Override
- public void afterTextChanged(final Editable editable) {
- if (!isEnabled() || mSettingAutoComplete) {
- return;
- }
-
- final String text = getNonAutocompleteText(editable);
- final int textLength = text.length();
- boolean doAutocomplete = mPrefs.shouldAutocomplete();
-
- if (StringUtils.isSearchQuery(text, false)) {
- doAutocomplete = false;
- } else if (mAutoCompletePrefixLength > textLength) {
- // If you're hitting backspace (the string is getting smaller), don't autocomplete
- doAutocomplete = false;
- }
-
- mAutoCompletePrefixLength = textLength;
-
- // If we are not autocompleting, we set mDiscardAutoCompleteResult to true
- // to discard any autocomplete results that are in-flight, and vice versa.
- mDiscardAutoCompleteResult = !doAutocomplete;
-
- if (doAutocomplete && mAutoCompleteResult.startsWith(text)) {
- // If this text already matches our autocomplete text, autocomplete likely
- // won't change. Just reuse the old autocomplete value.
- onAutocomplete(mAutoCompleteResult);
- doAutocomplete = false;
- } else {
- // Otherwise, remove the old autocomplete text
- // until any new autocomplete text gets added.
- removeAutocomplete(editable);
- }
-
- // Update search icon with an active state since user is typing
- if (mSearchStateChangeListener != null) {
- mSearchStateChangeListener.onSearchStateChange(textLength > 0);
- }
-
- if (mFilterListener != null) {
- mFilterListener.onFilter(text, doAutocomplete ? ToolbarEditText.this : null);
- }
- }
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count,
- int after) {
- // do nothing
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before,
- int count) {
- // do nothing
- }
- }
-
- private class KeyPreImeListener implements OnKeyPreImeListener {
- @Override
- public boolean onKeyPreIme(View v, int keyCode, KeyEvent event) {
- // We only want to process one event per tap
- if (event.getAction() != KeyEvent.ACTION_DOWN) {
- return false;
- }
-
- if (keyCode == KeyEvent.KEYCODE_ENTER) {
- // If the edit text has a composition string, don't submit the text yet.
- // ENTER is needed to commit the composition string.
- final Editable content = getText();
- if (!hasCompositionString(content)) {
- if (mCommitListener != null) {
- mCommitListener.onCommit();
- }
-
- return true;
- }
- }
-
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- // Drop the virtual keyboard.
- clearFocus();
- return true;
- }
-
- return false;
- }
- }
-
- private class KeyListener implements View.OnKeyListener {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_ENTER || GamepadUtils.isActionKey(event)) {
- if (event.getAction() != KeyEvent.ACTION_DOWN) {
- return true;
- }
-
- if (mCommitListener != null) {
- mCommitListener.onCommit();
- }
-
- return true;
- }
-
- if (GamepadUtils.isBackKey(event)) {
- if (mDismissListener != null) {
- mDismissListener.onDismiss();
- }
-
- return true;
- }
-
- if ((keyCode == KeyEvent.KEYCODE_DEL ||
- (keyCode == KeyEvent.KEYCODE_FORWARD_DEL)) &&
- removeAutocomplete(getText())) {
- // Delete autocomplete text when backspacing or forward deleting.
- return true;
- }
-
- return false;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarPrefs.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarPrefs.java
deleted file mode 100644
index f881de154..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarPrefs.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.toolbar;
-
-import org.mozilla.gecko.PrefsHelper;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.util.ThreadUtils;
-
-class ToolbarPrefs {
- private static final String PREF_AUTOCOMPLETE_ENABLED = "browser.urlbar.autocomplete.enabled";
- private static final String PREF_TRIM_URLS = "browser.urlbar.trimURLs";
-
- private static final String[] PREFS = {
- PREF_AUTOCOMPLETE_ENABLED,
- PREF_TRIM_URLS
- };
-
- private final TitlePrefsHandler HANDLER = new TitlePrefsHandler();
-
- private volatile boolean enableAutocomplete;
- private volatile boolean trimUrls;
-
- ToolbarPrefs() {
- // Skip autocompletion while Gecko is loading.
- // We will get the correct pref value once Gecko is loaded.
- enableAutocomplete = false;
- trimUrls = true;
- }
-
- boolean shouldAutocomplete() {
- return enableAutocomplete;
- }
-
- boolean shouldTrimUrls() {
- return trimUrls;
- }
-
- void open() {
- PrefsHelper.addObserver(PREFS, HANDLER);
- }
-
- void close() {
- PrefsHelper.removeObserver(HANDLER);
- }
-
- private void triggerTitleChangeListener() {
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- final Tabs tabs = Tabs.getInstance();
- final Tab tab = tabs.getSelectedTab();
- if (tab != null) {
- tabs.notifyListeners(tab, Tabs.TabEvents.TITLE);
- }
- }
- });
- }
-
- private class TitlePrefsHandler extends PrefsHelper.PrefHandlerBase {
- @Override
- public void prefValue(String pref, boolean value) {
- if (PREF_AUTOCOMPLETE_ENABLED.equals(pref)) {
- enableAutocomplete = value;
-
- } else if (PREF_TRIM_URLS.equals(pref)) {
- // Handles PREF_TRIM_URLS, which should usually be a boolean.
- if (value != trimUrls) {
- trimUrls = value;
- triggerTitleChangeListener();
- }
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarProgressView.java b/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarProgressView.java
deleted file mode 100644
index 43181cbef..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarProgressView.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.mozilla.gecko.toolbar;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.widget.themed.ThemedImageView;
-import org.mozilla.gecko.util.WeakReferenceHandler;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Message;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.animation.Animation;
-
-/**
- * Progress view used for page loads.
- *
- * Because we're given limited information about the page load progress, the
- * bar also includes incremental animation between each step to improve
- * perceived performance.
- */
-public class ToolbarProgressView extends ThemedImageView {
- private static final int MAX_PROGRESS = 10000;
- private static final int MSG_UPDATE = 0;
- private static final int MSG_HIDE = 1;
- private static final int STEPS = 10;
- private static final int DELAY = 40;
-
- private int mTargetProgress;
- private int mIncrement;
- private Rect mBounds;
- private Handler mHandler;
- private int mCurrentProgress;
-
- private PorterDuffColorFilter mPrivateBrowsingColorFilter;
-
- public ToolbarProgressView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init(context);
- }
-
- public ToolbarProgressView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init(context);
- }
-
- private void init(Context ctx) {
- mBounds = new Rect(0, 0, 0, 0);
- mTargetProgress = 0;
-
- mPrivateBrowsingColorFilter = new PorterDuffColorFilter(
- ContextCompat.getColor(ctx, R.color.private_browsing_purple), PorterDuff.Mode.SRC_IN);
-
- mHandler = new ToolbarProgressHandler(this);
- }
-
- @Override
- public void onLayout(boolean f, int l, int t, int r, int b) {
- mBounds.left = 0;
- mBounds.right = (r - l) * mCurrentProgress / MAX_PROGRESS;
- mBounds.top = 0;
- mBounds.bottom = b - t;
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- final Drawable d = getDrawable();
- d.setBounds(mBounds);
- d.draw(canvas);
- }
-
- /**
- * Immediately sets the progress bar to the given progress percentage.
- *
- * @param progress Percentage (0-100) to which progress bar should be set
- */
- void setProgress(int progressPercentage) {
- mCurrentProgress = mTargetProgress = getAbsoluteProgress(progressPercentage);
- updateBounds();
-
- clearMessages();
- }
-
- /**
- * Animates the progress bar from the current progress value to the given
- * progress percentage.
- *
- * @param progress Percentage (0-100) to which progress bar should be animated
- */
- void animateProgress(int progressPercentage) {
- final int absoluteProgress = getAbsoluteProgress(progressPercentage);
- if (absoluteProgress <= mTargetProgress) {
- // After we manually click stop, we can still receive page load
- // events (e.g., DOMContentLoaded). Updating for other updates
- // after a STOP event can freeze the progress bar, so guard against
- // that here.
- return;
- }
-
- mTargetProgress = absoluteProgress;
- mIncrement = (mTargetProgress - mCurrentProgress) / STEPS;
-
- clearMessages();
- mHandler.sendEmptyMessage(MSG_UPDATE);
- }
-
- private void clearMessages() {
- mHandler.removeMessages(MSG_UPDATE);
- mHandler.removeMessages(MSG_HIDE);
- }
-
- private int getAbsoluteProgress(int progressPercentage) {
- if (progressPercentage < 0) {
- return 0;
- }
-
- if (progressPercentage > 100) {
- return 100;
- }
-
- return progressPercentage * MAX_PROGRESS / 100;
- }
-
- private void updateBounds() {
- mBounds.right = getWidth() * mCurrentProgress / MAX_PROGRESS;
- invalidate();
- }
-
- @Override
- public void setPrivateMode(final boolean isPrivate) {
- super.setPrivateMode(isPrivate);
-
- // Note: android:tint is better but ColorStateLists are not supported until API 21.
- if (isPrivate) {
- setColorFilter(mPrivateBrowsingColorFilter);
- } else {
- clearColorFilter();
- }
- }
-
- private static class ToolbarProgressHandler extends WeakReferenceHandler<ToolbarProgressView> {
- public ToolbarProgressHandler(final ToolbarProgressView that) {
- super(that);
- }
-
- @Override
- public void handleMessage(Message msg) {
- final ToolbarProgressView that = mTarget.get();
- if (that == null) {
- return;
- }
-
- switch (msg.what) {
- case MSG_UPDATE:
- that.mCurrentProgress = Math.min(that.mTargetProgress, that.mCurrentProgress + that.mIncrement);
-
- that.updateBounds();
-
- if (that.mCurrentProgress < that.mTargetProgress) {
- final int delay = (that.mTargetProgress < MAX_PROGRESS) ? DELAY : DELAY / 4;
- sendMessageDelayed(that.mHandler.obtainMessage(msg.what), delay);
- } else if (that.mCurrentProgress == MAX_PROGRESS) {
- sendMessageDelayed(that.mHandler.obtainMessage(MSG_HIDE), DELAY);
- }
- break;
-
- case MSG_HIDE:
- that.setVisibility(View.GONE);
- break;
- }
- }
- };
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/trackingprotection/TrackingProtectionPrompt.java b/mobile/android/base/java/org/mozilla/gecko/trackingprotection/TrackingProtectionPrompt.java
deleted file mode 100644
index dcc62b6d4..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/trackingprotection/TrackingProtectionPrompt.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.trackingprotection;
-
-import org.mozilla.gecko.Locales;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.util.HardwareUtils;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.MotionEvent;
-import android.view.View;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-
-public class TrackingProtectionPrompt extends Locales.LocaleAwareActivity {
- public static final String LOGTAG = "Gecko" + TrackingProtectionPrompt.class.getSimpleName();
-
- // Flag set during animation to prevent animation multiple-start.
- private boolean isAnimating;
-
- private View containerView;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- showPrompt();
- }
-
- private void showPrompt() {
- setContentView(R.layout.tracking_protection_prompt);
-
- findViewById(R.id.ok_button).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- onConfirmButtonPressed();
- }
- });
- findViewById(R.id.link_text).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- slideOut();
- final Intent settingsIntent = new Intent(TrackingProtectionPrompt.this, GeckoPreferences.class);
- GeckoPreferences.setResourceToOpen(settingsIntent, "preferences_privacy");
- startActivity(settingsIntent);
-
- // Don't use a transition to settings if we're on a device where that
- // would look bad.
- if (HardwareUtils.IS_KINDLE_DEVICE) {
- overridePendingTransition(0, 0);
- }
- }
- });
-
- containerView = findViewById(R.id.tracking_protection_inner_container);
-
- containerView.setTranslationY(500);
- containerView.setAlpha(0);
-
- final Animator translateAnimator = ObjectAnimator.ofFloat(containerView, "translationY", 0);
- translateAnimator.setDuration(400);
-
- final Animator alphaAnimator = ObjectAnimator.ofFloat(containerView, "alpha", 1);
- alphaAnimator.setStartDelay(200);
- alphaAnimator.setDuration(600);
-
- final AnimatorSet set = new AnimatorSet();
- set.playTogether(alphaAnimator, translateAnimator);
- set.setStartDelay(400);
-
- set.start();
- }
-
- @Override
- public void finish() {
- super.finish();
-
- // Don't perform an activity-dismiss animation.
- overridePendingTransition(0, 0);
- }
-
- private void onConfirmButtonPressed() {
- slideOut();
- }
-
- /**
- * Slide the overlay down off the screen and destroy it.
- */
- private void slideOut() {
- if (isAnimating) {
- return;
- }
-
- isAnimating = true;
-
- ObjectAnimator animator = ObjectAnimator.ofFloat(containerView, "translationY", containerView.getHeight());
- animator.addListener(new AnimatorListenerAdapter() {
-
- @Override
- public void onAnimationEnd(Animator animation) {
- finish();
- }
-
- });
- animator.start();
- }
-
- /**
- * Close the dialog if back is pressed.
- */
- @Override
- public void onBackPressed() {
- slideOut();
- }
-
- /**
- * Close the dialog if the anything that isn't a button is tapped.
- */
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- slideOut();
- return true;
- }
- }
diff --git a/mobile/android/base/java/org/mozilla/gecko/updater/PostUpdateHandler.java b/mobile/android/base/java/org/mozilla/gecko/updater/PostUpdateHandler.java
deleted file mode 100644
index f0ad78e77..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/updater/PostUpdateHandler.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.updater;
-
-import android.content.Context;
-import android.content.res.AssetManager;
-import android.content.SharedPreferences;
-import android.util.Log;
-
-import com.keepsafe.switchboard.SwitchBoard;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.BrowserApp;
-import org.mozilla.gecko.delegates.BrowserAppDelegateWithReference;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.util.IOUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.Enumeration;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-/**
- * Perform tasks in the background after the app has been installed/updated.
- */
-public class PostUpdateHandler extends BrowserAppDelegateWithReference {
- private static final String LOGTAG = "PostUpdateHandler";
-
- @Override
- public void onStart(final BrowserApp browserApp) {
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(browserApp);
-
- // Check if this is a new installation or if the app has been updated since the last start.
- if (!AppConstants.MOZ_APP_BUILDID.equals(prefs.getString(GeckoPreferences.PREFS_APP_UPDATE_LAST_BUILD_ID, null))) {
- Log.d(LOGTAG, "Build ID changed since last start: '" + AppConstants.MOZ_APP_BUILDID + "', '" + prefs.getString(GeckoPreferences.PREFS_APP_UPDATE_LAST_BUILD_ID, null) + "'");
-
- // Copy the bundled system add-ons from the APK to the data directory.
- copyFeaturesFromAPK(browserApp);
- }
- }
- });
- }
-
- /**
- * Copies the /assets/features folder out of the APK and into the app's data directory.
- */
- private void copyFeaturesFromAPK(BrowserApp browserApp) {
- Log.d(LOGTAG, "Copying system add-ons from APK to dataDir");
-
- final String dataDir = browserApp.getApplicationInfo().dataDir;
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(browserApp);
- final AssetManager assetManager = browserApp.getContext().getAssets();
-
- try {
- final String[] assetNames = assetManager.list("features");
-
- for (int i = 0; i < assetNames.length; i++) {
- final String assetPath = "features/" + assetNames[i];
-
- Log.d(LOGTAG, "Copying '" + assetPath + "' from APK to dataDir");
-
- final InputStream assetStream = assetManager.open(assetPath);
- final File outFile = getDataFile(dataDir, assetPath);
-
- if (outFile == null) {
- continue;
- }
-
- final OutputStream outStream = new FileOutputStream(outFile);
-
- try {
- IOUtils.copy(assetStream, outStream);
- } catch (IOException e) {
- Log.e(LOGTAG, "Error copying '" + assetPath + "' from APK to dataDir");
- } finally {
- outStream.close();
- }
- }
- } catch (IOException e) {
- Log.e(LOGTAG, "Error retrieving packaged system add-ons from APK", e);
- }
-
- // Save the Build ID so we don't perform post-update operations again until the app is updated.
- prefs.edit().putString(GeckoPreferences.PREFS_APP_UPDATE_LAST_BUILD_ID, AppConstants.MOZ_APP_BUILDID).apply();
- }
-
- /**
- * Return a File instance in the data directory, ensuring
- * that the parent exists.
- *
- * @return null if the parents could not be created.
- */
- private File getDataFile(final String dataDir, final String name) {
- File outFile = new File(dataDir, name);
- File dir = outFile.getParentFile();
-
- if (!dir.exists()) {
- Log.d(LOGTAG, "Creating " + dir.getAbsolutePath());
- if (!dir.mkdirs()) {
- Log.e(LOGTAG, "Unable to create directories: " + dir.getAbsolutePath());
- return null;
- }
- }
-
- return outFile;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/updater/UpdateService.java b/mobile/android/base/java/org/mozilla/gecko/updater/UpdateService.java
deleted file mode 100644
index 7ccc43e28..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/updater/UpdateService.java
+++ /dev/null
@@ -1,795 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.updater;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.CrashHandler;
-import org.mozilla.gecko.R;
-
-import org.mozilla.apache.commons.codec.binary.Hex;
-
-import org.mozilla.gecko.permissions.Permissions;
-import org.mozilla.gecko.util.ProxySelector;
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import android.Manifest;
-import android.app.AlarmManager;
-import android.app.IntentService;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.Uri;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiManager.WifiLock;
-import android.os.Environment;
-import android.provider.Settings;
-import android.support.v4.app.NotificationManagerCompat;
-import android.support.v4.content.ContextCompat;
-import android.support.v4.net.ConnectivityManagerCompat;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationCompat.Builder;
-import android.util.Log;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URI;
-import java.net.URL;
-import java.net.URLConnection;
-import java.security.MessageDigest;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-import java.util.List;
-import java.util.TimeZone;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-
-public class UpdateService extends IntentService {
- private static final int BUFSIZE = 8192;
- private static final int NOTIFICATION_ID = 0x3e40ddbd;
-
- private static final String LOGTAG = "UpdateService";
-
- private static final int INTERVAL_LONG = 86400000; // in milliseconds
- private static final int INTERVAL_SHORT = 14400000; // again, in milliseconds
- private static final int INTERVAL_RETRY = 3600000;
-
- private static final String PREFS_NAME = "UpdateService";
- private static final String KEY_LAST_BUILDID = "UpdateService.lastBuildID";
- private static final String KEY_LAST_HASH_FUNCTION = "UpdateService.lastHashFunction";
- private static final String KEY_LAST_HASH_VALUE = "UpdateService.lastHashValue";
- private static final String KEY_LAST_FILE_NAME = "UpdateService.lastFileName";
- private static final String KEY_LAST_ATTEMPT_DATE = "UpdateService.lastAttemptDate";
- private static final String KEY_AUTODOWNLOAD_POLICY = "UpdateService.autoDownloadPolicy";
- private static final String KEY_UPDATE_URL = "UpdateService.updateUrl";
-
- private SharedPreferences mPrefs;
-
- private NotificationManagerCompat mNotificationManager;
- private ConnectivityManager mConnectivityManager;
- private Builder mBuilder;
-
- private volatile WifiLock mWifiLock;
-
- private boolean mDownloading;
- private boolean mCancelDownload;
- private boolean mApplyImmediately;
-
- private CrashHandler mCrashHandler;
-
- public enum AutoDownloadPolicy {
- NONE(-1),
- WIFI(0),
- DISABLED(1),
- ENABLED(2);
-
- public final int value;
-
- private AutoDownloadPolicy(int value) {
- this.value = value;
- }
-
- private final static AutoDownloadPolicy[] sValues = AutoDownloadPolicy.values();
-
- public static AutoDownloadPolicy get(int value) {
- for (AutoDownloadPolicy id: sValues) {
- if (id.value == value) {
- return id;
- }
- }
- return NONE;
- }
-
- public static AutoDownloadPolicy get(String name) {
- for (AutoDownloadPolicy id: sValues) {
- if (name.equalsIgnoreCase(id.toString())) {
- return id;
- }
- }
- return NONE;
- }
- }
-
- private enum CheckUpdateResult {
- // Keep these in sync with mobile/android/chrome/content/about.xhtml
- NOT_AVAILABLE,
- AVAILABLE,
- DOWNLOADING,
- DOWNLOADED
- }
-
-
- public UpdateService() {
- super("updater");
- }
-
- @Override
- public void onCreate () {
- mCrashHandler = CrashHandler.createDefaultCrashHandler(getApplicationContext());
-
- super.onCreate();
-
- mPrefs = getSharedPreferences(PREFS_NAME, 0);
- mNotificationManager = NotificationManagerCompat.from(this);
- mConnectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
- mWifiLock = ((WifiManager)getSystemService(Context.WIFI_SERVICE))
- .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, PREFS_NAME);
- mCancelDownload = false;
- }
-
- @Override
- public void onDestroy() {
- mCrashHandler.unregister();
- mCrashHandler = null;
-
- if (mWifiLock.isHeld()) {
- mWifiLock.release();
- }
- }
-
- @Override
- public synchronized int onStartCommand (Intent intent, int flags, int startId) {
- // If we are busy doing a download, the new Intent here would normally be queued for
- // execution once that is done. In this case, however, we want to flip the boolean
- // while that is running, so handle that now.
- if (mDownloading && UpdateServiceHelper.ACTION_APPLY_UPDATE.equals(intent.getAction())) {
- Log.i(LOGTAG, "will apply update when download finished");
-
- mApplyImmediately = true;
- showDownloadNotification();
- } else if (UpdateServiceHelper.ACTION_CANCEL_DOWNLOAD.equals(intent.getAction())) {
- mCancelDownload = true;
- } else {
- super.onStartCommand(intent, flags, startId);
- }
-
- return Service.START_REDELIVER_INTENT;
- }
-
- @Override
- protected void onHandleIntent (final Intent intent) {
- if (UpdateServiceHelper.ACTION_REGISTER_FOR_UPDATES.equals(intent.getAction())) {
- AutoDownloadPolicy policy = AutoDownloadPolicy.get(
- intent.getIntExtra(UpdateServiceHelper.EXTRA_AUTODOWNLOAD_NAME,
- AutoDownloadPolicy.NONE.value));
-
- if (policy != AutoDownloadPolicy.NONE) {
- setAutoDownloadPolicy(policy);
- }
-
- String url = intent.getStringExtra(UpdateServiceHelper.EXTRA_UPDATE_URL_NAME);
- if (url != null) {
- setUpdateUrl(url);
- }
-
- registerForUpdates(false);
- } else if (UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE.equals(intent.getAction())) {
- startUpdate(intent.getIntExtra(UpdateServiceHelper.EXTRA_UPDATE_FLAGS_NAME, 0));
- // Use this instead for forcing a download from about:fennec
- // startUpdate(UpdateServiceHelper.FLAG_FORCE_DOWNLOAD | UpdateServiceHelper.FLAG_REINSTALL);
- } else if (UpdateServiceHelper.ACTION_DOWNLOAD_UPDATE.equals(intent.getAction())) {
- // We always want to do the download and apply it here
- mApplyImmediately = true;
- startUpdate(UpdateServiceHelper.FLAG_FORCE_DOWNLOAD);
- } else if (UpdateServiceHelper.ACTION_APPLY_UPDATE.equals(intent.getAction())) {
- applyUpdate(intent.getStringExtra(UpdateServiceHelper.EXTRA_PACKAGE_PATH_NAME));
- }
- }
-
- private static boolean hasFlag(int flags, int flag) {
- return (flags & flag) == flag;
- }
-
- private void sendCheckUpdateResult(CheckUpdateResult result) {
- Intent resultIntent = new Intent(UpdateServiceHelper.ACTION_CHECK_UPDATE_RESULT);
- resultIntent.putExtra("result", result.toString());
- sendBroadcast(resultIntent);
- }
-
- private int getUpdateInterval(boolean isRetry) {
- int interval;
- if (isRetry) {
- interval = INTERVAL_RETRY;
- } else if (!AppConstants.RELEASE_OR_BETA) {
- interval = INTERVAL_SHORT;
- } else {
- interval = INTERVAL_LONG;
- }
-
- return interval;
- }
-
- private void registerForUpdates(boolean isRetry) {
- Calendar lastAttempt = getLastAttemptDate();
- Calendar now = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
-
- int interval = getUpdateInterval(isRetry);
-
- if (lastAttempt == null || (now.getTimeInMillis() - lastAttempt.getTimeInMillis()) > interval) {
- // We've either never attempted an update, or we are passed the desired
- // time. Start an update now.
- Log.i(LOGTAG, "no update has ever been attempted, checking now");
- startUpdate(0);
- return;
- }
-
- AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
- if (manager == null)
- return;
-
- PendingIntent pending = PendingIntent.getService(this, 0, new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE, null, this, UpdateService.class), PendingIntent.FLAG_UPDATE_CURRENT);
- manager.cancel(pending);
-
- lastAttempt.setTimeInMillis(lastAttempt.getTimeInMillis() + interval);
- Log.i(LOGTAG, "next update will be at: " + lastAttempt.getTime());
-
- manager.set(AlarmManager.RTC_WAKEUP, lastAttempt.getTimeInMillis(), pending);
- }
-
- private void startUpdate(final int flags) {
- setLastAttemptDate();
-
- NetworkInfo netInfo = mConnectivityManager.getActiveNetworkInfo();
- if (netInfo == null || !netInfo.isConnected()) {
- Log.i(LOGTAG, "not connected to the network");
- registerForUpdates(true);
- sendCheckUpdateResult(CheckUpdateResult.NOT_AVAILABLE);
- return;
- }
-
- registerForUpdates(false);
-
- final UpdateInfo info = findUpdate(hasFlag(flags, UpdateServiceHelper.FLAG_REINSTALL));
- boolean haveUpdate = (info != null);
-
- if (!haveUpdate) {
- Log.i(LOGTAG, "no update available");
- sendCheckUpdateResult(CheckUpdateResult.NOT_AVAILABLE);
- return;
- }
-
- Log.i(LOGTAG, "update available, buildID = " + info.buildID);
-
- Permissions.from(this)
- .withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
- .doNotPrompt()
- .andFallback(new Runnable() {
- @Override
- public void run() {
- showPermissionNotification();
- sendCheckUpdateResult(CheckUpdateResult.NOT_AVAILABLE);
- }})
- .run(new Runnable() {
- @Override
- public void run() {
- startDownload(info, flags);
- }});
- }
-
- private void startDownload(UpdateInfo info, int flags) {
- AutoDownloadPolicy policy = getAutoDownloadPolicy();
-
- // We only start a download automatically if one of following criteria are met:
- //
- // - We have a FORCE_DOWNLOAD flag passed in
- // - The preference is set to 'always'
- // - The preference is set to 'wifi' and we are using a non-metered network (i.e. the user
- // is OK with large data transfers occurring)
- //
- boolean shouldStartDownload = hasFlag(flags, UpdateServiceHelper.FLAG_FORCE_DOWNLOAD) ||
- policy == AutoDownloadPolicy.ENABLED ||
- (policy == AutoDownloadPolicy.WIFI && !ConnectivityManagerCompat.isActiveNetworkMetered(mConnectivityManager));
-
- if (!shouldStartDownload) {
- Log.i(LOGTAG, "not initiating automatic update download due to policy " + policy.toString());
- sendCheckUpdateResult(CheckUpdateResult.AVAILABLE);
-
- // We aren't autodownloading here, so prompt to start the update
- Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_DOWNLOAD_UPDATE);
- notificationIntent.setClass(this, UpdateService.class);
- PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
-
- NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
- builder.setSmallIcon(R.drawable.ic_status_logo);
- builder.setWhen(System.currentTimeMillis());
- builder.setAutoCancel(true);
- builder.setContentTitle(getString(R.string.updater_start_title));
- builder.setContentText(getString(R.string.updater_start_select));
- builder.setContentIntent(contentIntent);
-
- mNotificationManager.notify(NOTIFICATION_ID, builder.build());
-
- return;
- }
-
- File pkg = downloadUpdatePackage(info, hasFlag(flags, UpdateServiceHelper.FLAG_OVERWRITE_EXISTING));
- if (pkg == null) {
- sendCheckUpdateResult(CheckUpdateResult.NOT_AVAILABLE);
- return;
- }
-
- Log.i(LOGTAG, "have update package at " + pkg);
-
- saveUpdateInfo(info, pkg);
- sendCheckUpdateResult(CheckUpdateResult.DOWNLOADED);
-
- if (mApplyImmediately) {
- applyUpdate(pkg);
- } else {
- // Prompt to apply the update
-
- Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_APPLY_UPDATE);
- notificationIntent.setClass(this, UpdateService.class);
- notificationIntent.putExtra(UpdateServiceHelper.EXTRA_PACKAGE_PATH_NAME, pkg.getAbsolutePath());
- PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
-
-
- NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
- builder.setSmallIcon(R.drawable.ic_status_logo);
- builder.setWhen(System.currentTimeMillis());
- builder.setAutoCancel(true);
- builder.setContentTitle(getString(R.string.updater_apply_title));
- builder.setContentText(getString(R.string.updater_apply_select));
- builder.setContentIntent(contentIntent);
-
- mNotificationManager.notify(NOTIFICATION_ID, builder.build());
- }
- }
-
- private UpdateInfo findUpdate(boolean force) {
- try {
- URI uri = getUpdateURI(force);
-
- if (uri == null) {
- Log.e(LOGTAG, "failed to get update URI");
- return null;
- }
-
- DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
- Document dom = builder.parse(ProxySelector.openConnectionWithProxy(uri).getInputStream());
-
- NodeList nodes = dom.getElementsByTagName("update");
- if (nodes == null || nodes.getLength() == 0)
- return null;
-
- Node updateNode = nodes.item(0);
- Node buildIdNode = updateNode.getAttributes().getNamedItem("buildID");
- if (buildIdNode == null)
- return null;
-
- nodes = dom.getElementsByTagName("patch");
- if (nodes == null || nodes.getLength() == 0)
- return null;
-
- Node patchNode = nodes.item(0);
- Node urlNode = patchNode.getAttributes().getNamedItem("URL");
- Node hashFunctionNode = patchNode.getAttributes().getNamedItem("hashFunction");
- Node hashValueNode = patchNode.getAttributes().getNamedItem("hashValue");
- Node sizeNode = patchNode.getAttributes().getNamedItem("size");
-
- if (urlNode == null || hashFunctionNode == null ||
- hashValueNode == null || sizeNode == null) {
- return null;
- }
-
- // Fill in UpdateInfo from the XML data
- UpdateInfo info = new UpdateInfo();
- info.uri = new URI(urlNode.getTextContent());
- info.buildID = buildIdNode.getTextContent();
- info.hashFunction = hashFunctionNode.getTextContent();
- info.hashValue = hashValueNode.getTextContent();
-
- try {
- info.size = Integer.parseInt(sizeNode.getTextContent());
- } catch (NumberFormatException e) {
- Log.e(LOGTAG, "Failed to find APK size: ", e);
- return null;
- }
-
- // Make sure we have all the stuff we need to apply the update
- if (!info.isValid()) {
- Log.e(LOGTAG, "missing some required update information, have: " + info);
- return null;
- }
-
- return info;
- } catch (Exception e) {
- Log.e(LOGTAG, "failed to check for update: ", e);
- return null;
- }
- }
-
- private MessageDigest createMessageDigest(String hashFunction) {
- String javaHashFunction = null;
-
- if ("sha512".equalsIgnoreCase(hashFunction)) {
- javaHashFunction = "SHA-512";
- } else {
- Log.e(LOGTAG, "Unhandled hash function: " + hashFunction);
- return null;
- }
-
- try {
- return MessageDigest.getInstance(javaHashFunction);
- } catch (java.security.NoSuchAlgorithmException e) {
- Log.e(LOGTAG, "Couldn't find algorithm " + javaHashFunction, e);
- return null;
- }
- }
-
- private void showDownloadNotification() {
- showDownloadNotification(null);
- }
-
- private void showDownloadNotification(File downloadFile) {
-
- Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_APPLY_UPDATE);
- notificationIntent.setClass(this, UpdateService.class);
-
- Intent cancelIntent = new Intent(UpdateServiceHelper.ACTION_CANCEL_DOWNLOAD);
- cancelIntent.setClass(this, UpdateService.class);
-
- if (downloadFile != null)
- notificationIntent.putExtra(UpdateServiceHelper.EXTRA_PACKAGE_PATH_NAME, downloadFile.getAbsolutePath());
-
- PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- PendingIntent deleteIntent = PendingIntent.getService(this, 0, cancelIntent, PendingIntent.FLAG_CANCEL_CURRENT);
-
- mBuilder = new NotificationCompat.Builder(this);
- mBuilder.setContentTitle(getResources().getString(R.string.updater_downloading_title))
- .setContentText(mApplyImmediately ? "" : getResources().getString(R.string.updater_downloading_select))
- .setSmallIcon(android.R.drawable.stat_sys_download)
- .setContentIntent(contentIntent)
- .setDeleteIntent(deleteIntent);
-
- mBuilder.setProgress(100, 0, true);
- mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
- }
-
- private void showDownloadFailure() {
- Intent notificationIntent = new Intent(UpdateServiceHelper.ACTION_CHECK_FOR_UPDATE);
- notificationIntent.setClass(this, UpdateService.class);
- PendingIntent contentIntent = PendingIntent.getService(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
-
- NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
- builder.setSmallIcon(R.drawable.ic_status_logo);
- builder.setWhen(System.currentTimeMillis());
- builder.setContentTitle(getString(R.string.updater_downloading_title_failed));
- builder.setContentText(getString(R.string.updater_downloading_retry));
- builder.setContentIntent(contentIntent);
-
- mNotificationManager.notify(NOTIFICATION_ID, builder.build());
- }
-
- private boolean deleteUpdatePackage(String path) {
- if (path == null) {
- return false;
- }
-
- File pkg = new File(path);
- if (!pkg.exists()) {
- return false;
- }
-
- pkg.delete();
- Log.i(LOGTAG, "deleted update package: " + path);
-
- return true;
- }
-
- private File downloadUpdatePackage(UpdateInfo info, boolean overwriteExisting) {
- URL url = null;
- try {
- url = info.uri.toURL();
- } catch (java.net.MalformedURLException e) {
- Log.e(LOGTAG, "failed to read URL: ", e);
- return null;
- }
-
- File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
- path.mkdirs();
- String fileName = new File(url.getFile()).getName();
- File downloadFile = new File(path, fileName);
-
- if (!overwriteExisting && info.buildID.equals(getLastBuildID()) && downloadFile.exists()) {
- // The last saved buildID is the same as the one for the current update. We also have a file
- // already downloaded, so it's probably the package we want. Verify it to be sure and just
- // return that if it matches.
-
- if (verifyDownloadedPackage(downloadFile)) {
- Log.i(LOGTAG, "using existing update package");
- return downloadFile;
- } else {
- // Didn't match, so we're going to download a new one.
- downloadFile.delete();
- }
- }
-
- if (!info.buildID.equals(getLastBuildID())) {
- // Delete the previous package when a new version becomes available.
- deleteUpdatePackage(getLastFileName());
- }
-
- Log.i(LOGTAG, "downloading update package");
- sendCheckUpdateResult(CheckUpdateResult.DOWNLOADING);
-
- OutputStream output = null;
- InputStream input = null;
-
- mDownloading = true;
- mCancelDownload = false;
- showDownloadNotification(downloadFile);
-
- try {
- NetworkInfo netInfo = mConnectivityManager.getActiveNetworkInfo();
- if (netInfo != null && netInfo.isConnected() &&
- netInfo.getType() == ConnectivityManager.TYPE_WIFI) {
- mWifiLock.acquire();
- }
-
- URLConnection conn = ProxySelector.openConnectionWithProxy(info.uri);
- int length = conn.getContentLength();
-
- output = new BufferedOutputStream(new FileOutputStream(downloadFile));
- input = new BufferedInputStream(conn.getInputStream());
-
- byte[] buf = new byte[BUFSIZE];
- int len = 0;
-
- int bytesRead = 0;
- int lastNotify = 0;
-
- while ((len = input.read(buf, 0, BUFSIZE)) > 0 && !mCancelDownload) {
- output.write(buf, 0, len);
- bytesRead += len;
- // Updating the notification takes time so only do it every 1MB
- if (bytesRead - lastNotify > 1048576) {
- mBuilder.setProgress(length, bytesRead, false);
- mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
- lastNotify = bytesRead;
- }
- }
-
- mNotificationManager.cancel(NOTIFICATION_ID);
-
- // if the download was canceled by the user
- // delete the update package
- if (mCancelDownload) {
- Log.i(LOGTAG, "download canceled by user!");
- downloadFile.delete();
-
- return null;
- } else {
- Log.i(LOGTAG, "completed update download!");
- return downloadFile;
- }
- } catch (Exception e) {
- downloadFile.delete();
- showDownloadFailure();
-
- Log.e(LOGTAG, "failed to download update: ", e);
- return null;
- } finally {
- try {
- if (input != null)
- input.close();
- } catch (java.io.IOException e) { }
-
- try {
- if (output != null)
- output.close();
- } catch (java.io.IOException e) { }
-
- mDownloading = false;
-
- if (mWifiLock.isHeld()) {
- mWifiLock.release();
- }
- }
- }
-
- private boolean verifyDownloadedPackage(File updateFile) {
- MessageDigest digest = createMessageDigest(getLastHashFunction());
- if (digest == null)
- return false;
-
- InputStream input = null;
-
- try {
- input = new BufferedInputStream(new FileInputStream(updateFile));
-
- byte[] buf = new byte[BUFSIZE];
- int len;
- while ((len = input.read(buf, 0, BUFSIZE)) > 0) {
- digest.update(buf, 0, len);
- }
- } catch (java.io.IOException e) {
- Log.e(LOGTAG, "Failed to verify update package: ", e);
- return false;
- } finally {
- try {
- if (input != null)
- input.close();
- } catch (java.io.IOException e) { }
- }
-
- String hex = Hex.encodeHexString(digest.digest());
- if (!hex.equals(getLastHashValue())) {
- Log.e(LOGTAG, "Package hash does not match");
- return false;
- }
-
- return true;
- }
-
- private void applyUpdate(String updatePath) {
- if (updatePath == null) {
- updatePath = getLastFileName();
- }
-
- if (updatePath != null) {
- applyUpdate(new File(updatePath));
- }
- }
-
- private void applyUpdate(File updateFile) {
- mApplyImmediately = false;
-
- if (!updateFile.exists())
- return;
-
- Log.i(LOGTAG, "Verifying package: " + updateFile);
-
- if (!verifyDownloadedPackage(updateFile)) {
- Log.e(LOGTAG, "Not installing update, failed verification");
- return;
- }
-
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setDataAndType(Uri.fromFile(updateFile), "application/vnd.android.package-archive");
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(intent);
- }
-
- private void showPermissionNotification() {
- Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
- Uri.fromParts("package", getPackageName(), null));
-
- PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
-
- NotificationCompat.BigTextStyle bigTextStyle = new NotificationCompat.BigTextStyle()
- .bigText(getString(R.string.updater_permission_text));
-
- Notification notification = new NotificationCompat.Builder(this)
- .setContentTitle(getString(R.string.updater_permission_title))
- .setContentText(getString(R.string.updater_permission_text))
- .setStyle(bigTextStyle)
- .setAutoCancel(true)
- .setSmallIcon(R.drawable.ic_status_logo)
- .setColor(ContextCompat.getColor(this, R.color.rejection_red))
- .setContentIntent(pendingIntent)
- .build();
-
- NotificationManagerCompat.from(this)
- .notify(R.id.updateServicePermissionNotification, notification);
- }
-
- private String getLastBuildID() {
- return mPrefs.getString(KEY_LAST_BUILDID, null);
- }
-
- private String getLastHashFunction() {
- return mPrefs.getString(KEY_LAST_HASH_FUNCTION, null);
- }
-
- private String getLastHashValue() {
- return mPrefs.getString(KEY_LAST_HASH_VALUE, null);
- }
-
- private String getLastFileName() {
- return mPrefs.getString(KEY_LAST_FILE_NAME, null);
- }
-
- private Calendar getLastAttemptDate() {
- long lastAttempt = mPrefs.getLong(KEY_LAST_ATTEMPT_DATE, -1);
- if (lastAttempt < 0)
- return null;
-
- GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
- cal.setTimeInMillis(lastAttempt);
- return cal;
- }
-
- private void setLastAttemptDate() {
- SharedPreferences.Editor editor = mPrefs.edit();
- editor.putLong(KEY_LAST_ATTEMPT_DATE, System.currentTimeMillis());
- editor.commit();
- }
-
- private AutoDownloadPolicy getAutoDownloadPolicy() {
- return AutoDownloadPolicy.get(mPrefs.getInt(KEY_AUTODOWNLOAD_POLICY, AutoDownloadPolicy.WIFI.value));
- }
-
- private void setAutoDownloadPolicy(AutoDownloadPolicy policy) {
- SharedPreferences.Editor editor = mPrefs.edit();
- editor.putInt(KEY_AUTODOWNLOAD_POLICY, policy.value);
- editor.commit();
- }
-
- private URI getUpdateURI(boolean force) {
- return UpdateServiceHelper.expandUpdateURI(this, mPrefs.getString(KEY_UPDATE_URL, null), force);
- }
-
- private void setUpdateUrl(String url) {
- SharedPreferences.Editor editor = mPrefs.edit();
- editor.putString(KEY_UPDATE_URL, url);
- editor.commit();
- }
-
- private void saveUpdateInfo(UpdateInfo info, File downloaded) {
- SharedPreferences.Editor editor = mPrefs.edit();
- editor.putString(KEY_LAST_BUILDID, info.buildID);
- editor.putString(KEY_LAST_HASH_FUNCTION, info.hashFunction);
- editor.putString(KEY_LAST_HASH_VALUE, info.hashValue);
- editor.putString(KEY_LAST_FILE_NAME, downloaded.toString());
- editor.commit();
- }
-
- private class UpdateInfo {
- public URI uri;
- public String buildID;
- public String hashFunction;
- public String hashValue;
- public int size;
-
- private boolean isNonEmpty(String s) {
- return s != null && s.length() > 0;
- }
-
- public boolean isValid() {
- return uri != null && isNonEmpty(buildID) &&
- isNonEmpty(hashFunction) && isNonEmpty(hashValue) && size > 0;
- }
-
- @Override
- public String toString() {
- return "uri = " + uri + ", buildID = " + buildID + ", hashFunction = " + hashFunction + ", hashValue = " + hashValue + ", size = " + size;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/updater/UpdateServiceHelper.java b/mobile/android/base/java/org/mozilla/gecko/updater/UpdateServiceHelper.java
deleted file mode 100644
index c4d198ae7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/updater/UpdateServiceHelper.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.updater;
-
-import org.mozilla.gecko.annotation.RobocopTarget;
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.PrefsHelper;
-import org.mozilla.gecko.util.ContextUtils;
-import org.mozilla.gecko.util.GeckoJarReader;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ApplicationInfo;
-import android.os.Build;
-import android.util.Log;
-
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.HashMap;
-
-public class UpdateServiceHelper {
- public static final String ACTION_REGISTER_FOR_UPDATES = AppConstants.ANDROID_PACKAGE_NAME + ".REGISTER_FOR_UPDATES";
- public static final String ACTION_UNREGISTER_FOR_UPDATES = AppConstants.ANDROID_PACKAGE_NAME + ".UNREGISTER_FOR_UPDATES";
- public static final String ACTION_CHECK_FOR_UPDATE = AppConstants.ANDROID_PACKAGE_NAME + ".CHECK_FOR_UPDATE";
- public static final String ACTION_CHECK_UPDATE_RESULT = AppConstants.ANDROID_PACKAGE_NAME + ".CHECK_UPDATE_RESULT";
- public static final String ACTION_DOWNLOAD_UPDATE = AppConstants.ANDROID_PACKAGE_NAME + ".DOWNLOAD_UPDATE";
- public static final String ACTION_APPLY_UPDATE = AppConstants.ANDROID_PACKAGE_NAME + ".APPLY_UPDATE";
- public static final String ACTION_CANCEL_DOWNLOAD = AppConstants.ANDROID_PACKAGE_NAME + ".CANCEL_DOWNLOAD";
-
- // Flags for ACTION_CHECK_FOR_UPDATE
- protected static final int FLAG_FORCE_DOWNLOAD = 1;
- protected static final int FLAG_OVERWRITE_EXISTING = 1 << 1;
- protected static final int FLAG_REINSTALL = 1 << 2;
- protected static final int FLAG_RETRY = 1 << 3;
-
- // Name of the Intent extra for the autodownload policy, used with ACTION_REGISTER_FOR_UPDATES
- protected static final String EXTRA_AUTODOWNLOAD_NAME = "autodownload";
-
- // Name of the Intent extra that holds the flags for ACTION_CHECK_FOR_UPDATE
- protected static final String EXTRA_UPDATE_FLAGS_NAME = "updateFlags";
-
- // Name of the Intent extra that holds the APK path, used with ACTION_APPLY_UPDATE
- protected static final String EXTRA_PACKAGE_PATH_NAME = "packagePath";
-
- // Name of the Intent extra for the update URL, used with ACTION_REGISTER_FOR_UPDATES
- protected static final String EXTRA_UPDATE_URL_NAME = "updateUrl";
-
- private static final String LOGTAG = "UpdateServiceHelper";
- private static final String DEFAULT_UPDATE_LOCALE = "en-US";
-
- // So that updates can be disabled by tests.
- private static volatile boolean isEnabled = true;
-
- private enum Pref {
- AUTO_DOWNLOAD_POLICY("app.update.autodownload"),
- UPDATE_URL("app.update.url.android");
-
- public final String name;
-
- private Pref(String name) {
- this.name = name;
- }
-
- public final static String[] names;
-
- @Override
- public String toString() {
- return this.name;
- }
-
- static {
- ArrayList<String> nameList = new ArrayList<String>();
-
- for (Pref id: Pref.values()) {
- nameList.add(id.toString());
- }
-
- names = nameList.toArray(new String[0]);
- }
- }
-
- @RobocopTarget
- public static void setEnabled(final boolean enabled) {
- isEnabled = enabled;
- }
-
- public static URI expandUpdateURI(Context context, String updateUri, boolean force) {
- if (updateUri == null) {
- return null;
- }
-
- PackageManager pm = context.getPackageManager();
-
- String pkgSpecial = AppConstants.MOZ_PKG_SPECIAL != null ?
- "-" + AppConstants.MOZ_PKG_SPECIAL :
- "";
- String locale = DEFAULT_UPDATE_LOCALE;
-
- try {
- ApplicationInfo info = pm.getApplicationInfo(AppConstants.ANDROID_PACKAGE_NAME, 0);
- String updateLocaleUrl = "jar:jar:file://" + info.sourceDir + "!/" + AppConstants.OMNIJAR_NAME + "!/update.locale";
-
- final String jarLocale = GeckoJarReader.getText(context, updateLocaleUrl);
- if (jarLocale != null) {
- locale = jarLocale.trim();
- }
- } catch (android.content.pm.PackageManager.NameNotFoundException e) {
- // Shouldn't really be possible, but fallback to default locale
- Log.i(LOGTAG, "Failed to read update locale file, falling back to " + locale);
- }
-
- String url = updateUri.replace("%PRODUCT%", AppConstants.MOZ_APP_BASENAME)
- .replace("%VERSION%", AppConstants.MOZ_APP_VERSION)
- .replace("%BUILD_ID%", force ? "0" : AppConstants.MOZ_APP_BUILDID)
- .replace("%BUILD_TARGET%", "Android_" + AppConstants.MOZ_APP_ABI + pkgSpecial)
- .replace("%LOCALE%", locale)
- .replace("%CHANNEL%", AppConstants.MOZ_UPDATE_CHANNEL)
- .replace("%OS_VERSION%", Build.VERSION.RELEASE)
- .replace("%DISTRIBUTION%", "default")
- .replace("%DISTRIBUTION_VERSION%", "default")
- .replace("%MOZ_VERSION%", AppConstants.MOZILLA_VERSION);
-
- try {
- return new URI(url);
- } catch (java.net.URISyntaxException e) {
- Log.e(LOGTAG, "Failed to create update url: ", e);
- return null;
- }
- }
-
- public static boolean isUpdaterEnabled(final Context context) {
- return AppConstants.MOZ_UPDATER && isEnabled && !ContextUtils.isInstalledFromGooglePlay(context);
- }
-
- public static void setUpdateUrl(Context context, String url) {
- registerForUpdates(context, null, url);
- }
-
- public static void setAutoDownloadPolicy(Context context, UpdateService.AutoDownloadPolicy policy) {
- registerForUpdates(context, policy, null);
- }
-
- public static void checkForUpdate(Context context) {
- if (context == null) {
- return;
- }
-
- context.startService(createIntent(context, ACTION_CHECK_FOR_UPDATE));
- }
-
- public static void downloadUpdate(Context context) {
- if (context == null) {
- return;
- }
-
- context.startService(createIntent(context, ACTION_DOWNLOAD_UPDATE));
- }
-
- public static void applyUpdate(Context context) {
- if (context == null) {
- return;
- }
-
- context.startService(createIntent(context, ACTION_APPLY_UPDATE));
- }
-
- public static void registerForUpdates(final Context context) {
- if (!isUpdaterEnabled(context)) {
- return;
- }
-
- final HashMap<String, Object> prefs = new HashMap<String, Object>();
-
- PrefsHelper.getPrefs(Pref.names, new PrefsHelper.PrefHandlerBase() {
- @Override public void prefValue(String pref, String value) {
- prefs.put(pref, value);
- }
-
- @Override public void finish() {
- UpdateServiceHelper.registerForUpdates(context,
- UpdateService.AutoDownloadPolicy.get(
- (String) prefs.get(Pref.AUTO_DOWNLOAD_POLICY.toString())),
- (String) prefs.get(Pref.UPDATE_URL.toString()));
- }
- });
- }
-
- public static void registerForUpdates(Context context, UpdateService.AutoDownloadPolicy policy, String url) {
- if (!isUpdaterEnabled(context)) {
- return;
- }
-
- Intent intent = createIntent(context, ACTION_REGISTER_FOR_UPDATES);
-
- if (policy != null) {
- intent.putExtra(EXTRA_AUTODOWNLOAD_NAME, policy.value);
- }
-
- if (url != null) {
- intent.putExtra(EXTRA_UPDATE_URL_NAME, url);
- }
-
- context.startService(intent);
- }
-
- private static Intent createIntent(Context context, String action) {
- return new Intent(action, null, context, UpdateService.class);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/util/ColorUtil.java b/mobile/android/base/java/org/mozilla/gecko/util/ColorUtil.java
deleted file mode 100644
index ec227d1ce..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/util/ColorUtil.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.util;
-
-import android.graphics.Color;
-
-public class ColorUtil {
- public static int darken(final int color, final double fraction) {
- int red = Color.red(color);
- int green = Color.green(color);
- int blue = Color.blue(color);
- red = darkenColor(red, fraction);
- green = darkenColor(green, fraction);
- blue = darkenColor(blue, fraction);
- final int alpha = Color.alpha(color);
- return Color.argb(alpha, red, green, blue);
- }
-
- public static int getReadableTextColor(final int backgroundColor) {
- final int greyValue = grayscaleFromRGB(backgroundColor);
- // 186 chosen rather than the seemingly obvious 128 because of gamma.
- if (greyValue < 186) {
- return Color.WHITE;
- } else {
- return Color.BLACK;
- }
- }
-
- private static int darkenColor(final int color, final double fraction) {
- return (int) Math.max(color - (color * fraction), 0);
- }
-
- private static int grayscaleFromRGB(final int color) {
- final int red = Color.red(color);
- final int green = Color.green(color);
- final int blue = Color.blue(color);
- // Magic weighting taken from a stackoverflow post, supposedly related to how
- // humans perceive color.
- return (int) (0.299 * red + 0.587 * green + 0.114 * blue);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/util/DrawableUtil.java b/mobile/android/base/java/org/mozilla/gecko/util/DrawableUtil.java
deleted file mode 100644
index f3c9eef83..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/util/DrawableUtil.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.util;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.CheckResult;
-import android.support.annotation.ColorInt;
-import android.support.annotation.ColorRes;
-import android.support.annotation.DrawableRes;
-import android.support.annotation.NonNull;
-import android.support.v4.content.ContextCompat;
-import android.support.v4.graphics.drawable.DrawableCompat;
-
-import org.mozilla.gecko.AppConstants;
-
-public class DrawableUtil {
-
- /**
- * Tints the given drawable with the given color and returns it.
- */
- @CheckResult
- public static Drawable tintDrawable(@NonNull final Context context,
- @DrawableRes final int drawableID,
- @ColorInt final int color) {
- final Drawable icon = DrawableCompat.wrap(ResourceDrawableUtils.getDrawable(context, drawableID)
- .mutate());
- DrawableCompat.setTint(icon, color);
- return icon;
- }
-
- /**
- * Tints the given drawable with the given color and returns it.
- */
- @CheckResult
- public static Drawable tintDrawableWithColorRes(@NonNull final Context context,
- @DrawableRes final int drawableID,
- @ColorRes final int colorID) {
- return tintDrawable(context, drawableID, ContextCompat.getColor(context, colorID));
- }
-
- /**
- * Tints the given drawable with the given tint list and returns it. Note that you
- * should no longer use the argument Drawable because the argument is not mutated
- * on pre-Lollipop devices but is mutated on L+ due to differences in the Support
- * Library implementation (bug 1193950).
- */
- @CheckResult
- public static Drawable tintDrawableWithStateList(@NonNull final Drawable drawable,
- @NonNull final ColorStateList colorList) {
- final Drawable wrappedDrawable = DrawableCompat.wrap(drawable.mutate());
- DrawableCompat.setTintList(wrappedDrawable, colorList);
-
- // DrawableCompat on pre-L doesn't handle its bounds correctly, and by default therefore won't
- // be rendered - we need to manually copy the bounds as a workaround:
- if (AppConstants.Versions.preMarshmallow) {
- wrappedDrawable.setBounds(0, 0, wrappedDrawable.getIntrinsicHeight(), wrappedDrawable.getIntrinsicHeight());
- }
-
- return wrappedDrawable;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/util/ResourceDrawableUtils.java b/mobile/android/base/java/org/mozilla/gecko/util/ResourceDrawableUtils.java
deleted file mode 100644
index 1e5c2a723..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/util/ResourceDrawableUtils.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.util;
-
-import android.content.Context;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.support.annotation.DrawableRes;
-import android.support.annotation.NonNull;
-import android.support.v7.widget.AppCompatDrawableManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-import org.mozilla.gecko.util.GeckoJarReader;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.UIAsyncTask;
-
-import java.io.InputStream;
-import java.net.URL;
-
-import static org.mozilla.gecko.gfx.BitmapUtils.getBitmapFromDataURI;
-import static org.mozilla.gecko.gfx.BitmapUtils.getResource;
-
-public class ResourceDrawableUtils {
- private static final String LOGTAG = "ResourceDrawableUtils";
-
- public static Drawable getDrawable(@NonNull final Context context,
- @DrawableRes final int drawableID) {
- // TODO: upgrade this call to use AppCompatResources when upgrading to support library >= 24.2
- // https://developer.android.com/reference/android/support/v7/content/res/AppCompatResources.html#getDrawable(android.content.Context,%20int)
- return AppCompatDrawableManager.get().getDrawable(context, drawableID);
- }
-
- public interface BitmapLoader {
- public void onBitmapFound(Drawable d);
- }
-
- public static void runOnBitmapFoundOnUiThread(final BitmapLoader loader, final Drawable d) {
- if (ThreadUtils.isOnUiThread()) {
- loader.onBitmapFound(d);
- return;
- }
-
- ThreadUtils.postToUiThread(new Runnable() {
- @Override
- public void run() {
- loader.onBitmapFound(d);
- }
- });
- }
-
- /**
- * Attempts to find a drawable associated with a given string, using its URI scheme to determine
- * how to load the drawable. The BitmapLoader's `onBitmapFound` method is always called, and
- * will be called with `null` if no drawable is found.
- *
- * The BitmapLoader `onBitmapFound` method always runs on the UI thread.
- */
- public static void getDrawable(final Context context, final String data, final BitmapLoader loader) {
- if (TextUtils.isEmpty(data)) {
- runOnBitmapFoundOnUiThread(loader, null);
- return;
- }
-
- if (data.startsWith("data")) {
- final BitmapDrawable d = new BitmapDrawable(context.getResources(), getBitmapFromDataURI(data));
- runOnBitmapFoundOnUiThread(loader, d);
- return;
- }
-
- if (data.startsWith("jar:") || data.startsWith("file://")) {
- (new UIAsyncTask.WithoutParams<Drawable>(ThreadUtils.getBackgroundHandler()) {
- @Override
- public Drawable doInBackground() {
- try {
- if (data.startsWith("jar:jar")) {
- return GeckoJarReader.getBitmapDrawable(
- context, context.getResources(), data);
- }
-
- // Don't attempt to validate the JAR signature when loading an add-on icon
- if (data.startsWith("jar:file")) {
- return GeckoJarReader.getBitmapDrawable(
- context, context.getResources(), Uri.decode(data));
- }
-
- final URL url = new URL(data);
- final InputStream is = (InputStream) url.getContent();
- try {
- return Drawable.createFromStream(is, "src");
- } finally {
- is.close();
- }
- } catch (Exception e) {
- Log.w(LOGTAG, "Unable to set icon", e);
- }
- return null;
- }
-
- @Override
- public void onPostExecute(Drawable drawable) {
- loader.onBitmapFound(drawable);
- }
- }).execute();
- return;
- }
-
- if (data.startsWith("-moz-icon://")) {
- final Uri imageUri = Uri.parse(data);
- final String ssp = imageUri.getSchemeSpecificPart();
- final String resource = ssp.substring(ssp.lastIndexOf('/') + 1);
-
- try {
- final Drawable d = context.getPackageManager().getApplicationIcon(resource);
- runOnBitmapFoundOnUiThread(loader, d);
- } catch (Exception ex) { }
-
- return;
- }
-
- if (data.startsWith("drawable://")) {
- final Uri imageUri = Uri.parse(data);
- final int id = getResource(context, imageUri);
- final Drawable d = getDrawable(context, id);
-
- runOnBitmapFoundOnUiThread(loader, d);
- return;
- }
-
- runOnBitmapFoundOnUiThread(loader, null);
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/util/TouchTargetUtil.java b/mobile/android/base/java/org/mozilla/gecko/util/TouchTargetUtil.java
deleted file mode 100644
index 6414dec9f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/util/TouchTargetUtil.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.util;
-
-import android.graphics.Rect;
-import android.view.TouchDelegate;
-import android.view.View;
-
-import org.mozilla.gecko.R;
-
-public class TouchTargetUtil {
- /**
- * Ensures that a given targetView has a large enough touch area to ensure it can be selected.
- * A TouchDelegate will be added to the enclosingView as necessary.
- *
- * @param targetView
- * @param enclosingView
- */
- public static void ensureTargetHitArea(final View targetView, final View enclosingView) {
- enclosingView.post(new Runnable() {
- @Override
- public void run() {
- Rect delegateArea = new Rect();
- targetView.getHitRect(delegateArea);
-
- final int targetHitArea = enclosingView.getContext().getResources().getDimensionPixelSize(R.dimen.touch_target_size);
-
- final int widthDelta = (targetHitArea - delegateArea.width()) / 2;
- delegateArea.right += widthDelta;
- delegateArea.left -= widthDelta;
-
- final int heightDelta = (targetHitArea - delegateArea.height()) / 2;
- delegateArea.bottom += heightDelta;
- delegateArea.top -= heightDelta;
-
- if (heightDelta <= 0 && widthDelta <= 0) {
- return;
- }
-
- TouchDelegate touchDelegate = new TouchDelegate(delegateArea, targetView);
- enclosingView.setTouchDelegate(touchDelegate);
- }
- });
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/util/UnusedResourcesUtil.java b/mobile/android/base/java/org/mozilla/gecko/util/UnusedResourcesUtil.java
deleted file mode 100644
index 0033e72a0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/util/UnusedResourcesUtil.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package org.mozilla.gecko.util;
-
-import org.mozilla.gecko.R;
-
-/**
- * (linter: UnusedResources) We use resources in places Android Lint can't check (e.g. JS) - this is
- * a set of those references so Android Lint stops complaining.
- */
-@SuppressWarnings("unused")
-final class UnusedResourcesUtil {
- public static final int[] CONSTANTS = {
- R.dimen.match_parent,
- R.dimen.wrap_content,
- };
-
- public static final int[] USED_IN_BRANDING = {
- R.drawable.large_icon
- };
-
- public static final int[] USED_IN_COLOR_PALETTE = {
- R.color.private_browsing_purple, // This will be used eventually, then this item removed.
- };
-
- public static final int[] USED_IN_CRASH_REPORTER = {
- R.string.crash_allow_contact2,
- R.string.crash_close_label,
- R.string.crash_comment,
- R.string.crash_email,
- R.string.crash_include_url2,
- R.string.crash_message2,
- R.string.crash_restart_label,
- R.string.crash_send_report_message3,
- R.string.crash_sorry,
- };
-
- public static final int[] USED_IN_JS = {
- R.drawable.ab_search,
- R.drawable.alert_camera,
- R.drawable.alert_download,
- R.drawable.alert_download_animation,
- R.drawable.alert_mic,
- R.drawable.alert_mic_camera,
- R.drawable.casting,
- R.drawable.casting_active,
- R.drawable.close,
- R.drawable.homepage_banner_firstrun,
- R.drawable.icon_openinapp,
- R.drawable.pause,
- R.drawable.phone,
- R.drawable.play,
- R.drawable.reader,
- R.drawable.reader_active,
- R.drawable.sync_promo,
- R.drawable.undo_button_icon,
- };
-
- public static final int[] USED_IN_MANIFEST = {
- R.drawable.search_launcher,
- R.string.crash_reporter_title,
- R.xml.fxaccount_authenticator,
- R.xml.fxaccount_syncadapter,
- R.xml.search_widget_info,
- R.xml.searchable,
- };
-
- public static final int[] USED_IN_SUGGESTEDSITES = {
- R.drawable.suggestedsites_amazon,
- R.drawable.suggestedsites_facebook,
- R.drawable.suggestedsites_restricted_fxsupport,
- R.drawable.suggestedsites_restricted_mozilla,
- R.drawable.suggestedsites_twitter,
- R.drawable.suggestedsites_webmaker,
- R.drawable.suggestedsites_wikipedia,
- R.drawable.suggestedsites_youtube,
- };
-
- public static final int[] USED_IN_BOOKMARKDEFAULTS = {
- R.raw.bookmarkdefaults_favicon_addons,
- R.raw.bookmarkdefaults_favicon_support,
- R.raw.bookmarkdefaults_favicon_restricted_support,
- R.raw.bookmarkdefaults_favicon_restricted_webmaker,
- R.string.bookmarkdefaults_title_restricted_support,
- R.string.bookmarkdefaults_url_restricted_support,
- R.string.bookmarkdefaults_title_restricted_webmaker,
- R.string.bookmarkdefaults_url_restricted_webmaker,
- };
-
- public static final int[] USED_IN_PREFS = {
- R.xml.preferences_advanced,
- R.xml.preferences_accessibility,
- R.xml.preferences_home,
- R.xml.preferences_privacy,
- R.xml.preferences_privacy_clear_tablet,
- R.xml.preferences_default_browser_tablet
- };
-
- // We are migrating to Gradle 2.10 and the Android Gradle plugin 2.0. The new plugin does find
- // more unused resources but we are not ready to remove them yet. Some of the resources are going
- // to be reused soon. This is a temporary solution so that the gradle migration is not blocked.
- // See bug 1263390 / bug 1268414.
- public static final int[] TEMPORARY_UNUSED_WHILE_MIGRATING_GRADLE = {
- R.color.remote_tabs_setup_button_background_hit,
-
- R.drawable.remote_tabs_setup_button_background,
-
- R.style.TabsPanelSectionBase,
- R.style.TabsPanelSection,
- R.style.TabsPanelItemBase,
- R.style.TabsPanelItem,
- R.style.TabsPanelItem_TextAppearance,
- R.style.TabsPanelItem_TextAppearance_Header,
- R.style.TabsPanelItem_TextAppearance_Linkified,
- R.style.TabWidget,
- R.style.GeckoDialogTitle,
- R.style.GeckoDialogTitle_SubTitle,
- R.style.RemoteTabsPanelItem,
- R.style.RemoteTabsPanelItem_TextAppearance,
- R.style.RemoteTabsPanelItem_TextAppearance_Header,
- R.style.RemoteTabsPanelItem_TextAppearance_Linkified,
- R.style.RemoteTabsPanelItem_Button,
- };
-
- // String resources that are used in the full-pane Activity Stream that are temporarily
- // not needed while Activity Stream is part of the HomePager
- public static final int[] TEMPORARY_UNUSED_ACTIVITY_STREAM = {
- R.string.activity_stream_topsites
- };
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/util/ViewUtil.java b/mobile/android/base/java/org/mozilla/gecko/util/ViewUtil.java
deleted file mode 100644
index 180e821e7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/util/ViewUtil.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.util;
-
-import android.content.res.TypedArray;
-import android.view.View;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.R;
-
-public class ViewUtil {
-
- /**
- * Enable a circular touch ripple for a given view. This is intended for borderless views,
- * such as (3-dot) menu buttons.
- *
- * Because of platform limitations a square ripple is used on Android 4.
- */
- public static void enableTouchRipple(View view) {
- final TypedArray backgroundDrawableArray;
- if (AppConstants.Versions.feature21Plus) {
- backgroundDrawableArray = view.getContext().obtainStyledAttributes(new int[] { R.attr.selectableItemBackgroundBorderless });
- } else {
- backgroundDrawableArray = view.getContext().obtainStyledAttributes(new int[] { R.attr.selectableItemBackground });
- }
-
- // This call is deprecated, but the replacement setBackground(Drawable) isn't available
- // until API 16.
- view.setBackgroundDrawable(backgroundDrawableArray.getDrawable(0));
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/ActivityChooserModel.java b/mobile/android/base/java/org/mozilla/gecko/widget/ActivityChooserModel.java
deleted file mode 100644
index 8cde1ee05..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/ActivityChooserModel.java
+++ /dev/null
@@ -1,1359 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * Mozilla: Changing the package.
- */
-//package android.widget;
-package org.mozilla.gecko.widget;
-
-// Mozilla: New import
-import android.accounts.Account;
-import android.content.pm.PackageManager;
-
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.TabsAccessor;
-import org.mozilla.gecko.distribution.Distribution;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.SyncStatusListener;
-import org.mozilla.gecko.overlays.ui.ShareDialog;
-import org.mozilla.gecko.R;
-import java.io.File;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ResolveInfo;
-import android.database.Cursor;
-import android.database.DataSetObservable;
-import android.os.AsyncTask;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Xml;
-
-/**
- * Mozilla: Unused import.
- */
-//import com.android.internal.content.PackageMonitor;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-/**
- * <p>
- * This class represents a data model for choosing a component for handing a
- * given {@link Intent}. The model is responsible for querying the system for
- * activities that can handle the given intent and order found activities
- * based on historical data of previous choices. The historical data is stored
- * in an application private file. If a client does not want to have persistent
- * choice history the file can be omitted, thus the activities will be ordered
- * based on historical usage for the current session.
- * <p>
- * </p>
- * For each backing history file there is a singleton instance of this class. Thus,
- * several clients that specify the same history file will share the same model. Note
- * that if multiple clients are sharing the same model they should implement semantically
- * equivalent functionality since setting the model intent will change the found
- * activities and they may be inconsistent with the functionality of some of the clients.
- * For example, choosing a share activity can be implemented by a single backing
- * model and two different views for performing the selection. If however, one of the
- * views is used for sharing but the other for importing, for example, then each
- * view should be backed by a separate model.
- * </p>
- * <p>
- * The way clients interact with this class is as follows:
- * </p>
- * <p>
- * <pre>
- * <code>
- * // Get a model and set it to a couple of clients with semantically similar function.
- * ActivityChooserModel dataModel =
- * ActivityChooserModel.get(context, "task_specific_history_file_name.xml");
- *
- * ActivityChooserModelClient modelClient1 = getActivityChooserModelClient1();
- * modelClient1.setActivityChooserModel(dataModel);
- *
- * ActivityChooserModelClient modelClient2 = getActivityChooserModelClient2();
- * modelClient2.setActivityChooserModel(dataModel);
- *
- * // Set an intent to choose a an activity for.
- * dataModel.setIntent(intent);
- * <pre>
- * <code>
- * </p>
- * <p>
- * <strong>Note:</strong> This class is thread safe.
- * </p>
- *
- * @hide
- */
-public class ActivityChooserModel extends DataSetObservable {
-
- /**
- * Client that utilizes an {@link ActivityChooserModel}.
- */
- public interface ActivityChooserModelClient {
-
- /**
- * Sets the {@link ActivityChooserModel}.
- *
- * @param dataModel The model.
- */
- public void setActivityChooserModel(ActivityChooserModel dataModel);
- }
-
- /**
- * Defines a sorter that is responsible for sorting the activities
- * based on the provided historical choices and an intent.
- */
- public interface ActivitySorter {
-
- /**
- * Sorts the <code>activities</code> in descending order of relevance
- * based on previous history and an intent.
- *
- * @param intent The {@link Intent}.
- * @param activities Activities to be sorted.
- * @param historicalRecords Historical records.
- */
- // This cannot be done by a simple comparator since an Activity weight
- // is computed from history. Note that Activity implements Comparable.
- public void sort(Intent intent, List<ActivityResolveInfo> activities,
- List<HistoricalRecord> historicalRecords);
- }
-
- /**
- * Listener for choosing an activity.
- */
- public interface OnChooseActivityListener {
-
- /**
- * Called when an activity has been chosen. The client can decide whether
- * an activity can be chosen and if so the caller of
- * {@link ActivityChooserModel#chooseActivity(int)} will receive and {@link Intent}
- * for launching it.
- * <p>
- * <strong>Note:</strong> Modifying the intent is not permitted and
- * any changes to the latter will be ignored.
- * </p>
- *
- * @param host The listener's host model.
- * @param intent The intent for launching the chosen activity.
- * @return Whether the intent is handled and should not be delivered to clients.
- *
- * @see ActivityChooserModel#chooseActivity(int)
- */
- public boolean onChooseActivity(ActivityChooserModel host, Intent intent);
- }
-
- /**
- * Flag for selecting debug mode.
- */
- private static final boolean DEBUG = false;
-
- /**
- * Tag used for logging.
- */
- static final String LOG_TAG = ActivityChooserModel.class.getSimpleName();
-
- /**
- * The root tag in the history file.
- */
- private static final String TAG_HISTORICAL_RECORDS = "historical-records";
-
- /**
- * The tag for a record in the history file.
- */
- private static final String TAG_HISTORICAL_RECORD = "historical-record";
-
- /**
- * Attribute for the activity.
- */
- private static final String ATTRIBUTE_ACTIVITY = "activity";
-
- /**
- * Attribute for the choice time.
- */
- private static final String ATTRIBUTE_TIME = "time";
-
- /**
- * Attribute for the choice weight.
- */
- private static final String ATTRIBUTE_WEIGHT = "weight";
-
- /**
- * The default maximal length of the choice history.
- */
- public static final int DEFAULT_HISTORY_MAX_LENGTH = 50;
-
- /**
- * The amount with which to inflate a chosen activity when set as default.
- */
- private static final int DEFAULT_ACTIVITY_INFLATION = 5;
-
- /**
- * Default weight for a choice record.
- */
- private static final float DEFAULT_HISTORICAL_RECORD_WEIGHT = 1.0f;
-
- /**
- * The extension of the history file.
- */
- private static final String HISTORY_FILE_EXTENSION = ".xml";
-
- /**
- * An invalid item index.
- */
- private static final int INVALID_INDEX = -1;
-
- /**
- * Lock to guard the model registry.
- */
- private static final Object sRegistryLock = new Object();
-
- /**
- * This the registry for data models.
- */
- private static final Map<String, ActivityChooserModel> sDataModelRegistry =
- new HashMap<String, ActivityChooserModel>();
-
- /**
- * Lock for synchronizing on this instance.
- */
- private final Object mInstanceLock = new Object();
-
- /**
- * List of activities that can handle the current intent.
- */
- private final List<ActivityResolveInfo> mActivities = new ArrayList<ActivityResolveInfo>();
-
- /**
- * List with historical choice records.
- */
- private final List<HistoricalRecord> mHistoricalRecords = new ArrayList<HistoricalRecord>();
-
- /**
- * Monitor for added and removed packages.
- */
- /**
- * Mozilla: Converted from a PackageMonitor to a DataModelPackageMonitor to avoid importing a new class.
- */
- private final DataModelPackageMonitor mPackageMonitor = new DataModelPackageMonitor();
-
- /**
- * Context for accessing resources.
- */
- final Context mContext;
-
- /**
- * The name of the history file that backs this model.
- */
- final String mHistoryFileName;
-
- /**
- * The intent for which a activity is being chosen.
- */
- private Intent mIntent;
-
- /**
- * The sorter for ordering activities based on intent and past choices.
- */
- private ActivitySorter mActivitySorter = new DefaultSorter();
-
- /**
- * The maximal length of the choice history.
- */
- private int mHistoryMaxSize = DEFAULT_HISTORY_MAX_LENGTH;
-
- /**
- * Flag whether choice history can be read. In general many clients can
- * share the same data model and {@link #readHistoricalDataIfNeeded()} may be called
- * by arbitrary of them any number of times. Therefore, this class guarantees
- * that the very first read succeeds and subsequent reads can be performed
- * only after a call to {@link #persistHistoricalDataIfNeeded()} followed by change
- * of the share records.
- */
- boolean mCanReadHistoricalData = true;
-
- /**
- * Flag whether the choice history was read. This is used to enforce that
- * before calling {@link #persistHistoricalDataIfNeeded()} a call to
- * {@link #persistHistoricalDataIfNeeded()} has been made. This aims to avoid a
- * scenario in which a choice history file exits, it is not read yet and
- * it is overwritten. Note that always all historical records are read in
- * full and the file is rewritten. This is necessary since we need to
- * purge old records that are outside of the sliding window of past choices.
- */
- private boolean mReadShareHistoryCalled;
-
- /**
- * Flag whether the choice records have changed. In general many clients can
- * share the same data model and {@link #persistHistoricalDataIfNeeded()} may be called
- * by arbitrary of them any number of times. Therefore, this class guarantees
- * that choice history will be persisted only if it has changed.
- */
- private boolean mHistoricalRecordsChanged = true;
-
- /**
- * Flag whether to reload the activities for the current intent.
- */
- boolean mReloadActivities;
-
- /**
- * Policy for controlling how the model handles chosen activities.
- */
- private OnChooseActivityListener mActivityChooserModelPolicy;
-
- /**
- * Mozilla: Share overlay variables.
- */
- private final SyncStatusListener mSyncStatusListener = new SyncStatusDelegate();
-
- /**
- * Gets the data model backed by the contents of the provided file with historical data.
- * Note that only one data model is backed by a given file, thus multiple calls with
- * the same file name will return the same model instance. If no such instance is present
- * it is created.
- *
- * <p>
- * <strong>Always use difference historical data files for semantically different actions.
- * For example, sharing is different from importing.</strong>
- * </p>
- *
- * @param context Context for loading resources.
- * @param historyFileName File name with choice history, <code>null</code>
- * if the model should not be backed by a file. In this case the activities
- * will be ordered only by data from the current session.
- *
- * @return The model.
- */
- public static ActivityChooserModel get(Context context, String historyFileName) {
- synchronized (sRegistryLock) {
- ActivityChooserModel dataModel = sDataModelRegistry.get(historyFileName);
- if (dataModel == null) {
- dataModel = new ActivityChooserModel(context, historyFileName);
- sDataModelRegistry.put(historyFileName, dataModel);
- }
- return dataModel;
- }
- }
-
- /**
- * Creates a new instance.
- *
- * @param context Context for loading resources.
- * @param historyFileName The history XML file.
- */
- private ActivityChooserModel(Context context, String historyFileName) {
- mContext = context.getApplicationContext();
- if (!TextUtils.isEmpty(historyFileName)
- && !historyFileName.endsWith(HISTORY_FILE_EXTENSION)) {
- mHistoryFileName = historyFileName + HISTORY_FILE_EXTENSION;
- } else {
- mHistoryFileName = historyFileName;
- }
-
- /**
- * Mozilla: Uses modified receiver
- */
- mPackageMonitor.register(mContext);
-
- /**
- * Mozilla: Add Sync Status Listener.
- */
- // TODO: We only need to add a sync status listener if the ShareDialog passes the intent filter.
- FirefoxAccounts.addSyncStatusListener(mSyncStatusListener);
- }
-
- /**
- * Sets an intent for which to choose a activity.
- * <p>
- * <strong>Note:</strong> Clients must set only semantically similar
- * intents for each data model.
- * <p>
- *
- * @param intent The intent.
- */
- public void setIntent(Intent intent) {
- synchronized (mInstanceLock) {
- if (mIntent == intent) {
- return;
- }
- mIntent = intent;
- mReloadActivities = true;
- ensureConsistentState();
- }
- }
-
- /**
- * Gets the intent for which a activity is being chosen.
- *
- * @return The intent.
- */
- public Intent getIntent() {
- synchronized (mInstanceLock) {
- return mIntent;
- }
- }
-
- /**
- * Gets the number of activities that can handle the intent.
- *
- * @return The activity count.
- *
- * @see #setIntent(Intent)
- */
- public int getActivityCount() {
- synchronized (mInstanceLock) {
- ensureConsistentState();
- return mActivities.size();
- }
- }
-
- /**
- * Gets an activity at a given index.
- *
- * @return The activity.
- *
- * @see ActivityResolveInfo
- * @see #setIntent(Intent)
- */
- public ResolveInfo getActivity(int index) {
- synchronized (mInstanceLock) {
- ensureConsistentState();
- return mActivities.get(index).resolveInfo;
- }
- }
-
- /**
- * Gets the index of a the given activity.
- *
- * @param activity The activity index.
- *
- * @return The index if found, -1 otherwise.
- */
- public int getActivityIndex(ResolveInfo activity) {
- synchronized (mInstanceLock) {
- ensureConsistentState();
- List<ActivityResolveInfo> activities = mActivities;
- final int activityCount = activities.size();
- for (int i = 0; i < activityCount; i++) {
- ActivityResolveInfo currentActivity = activities.get(i);
- if (currentActivity.resolveInfo == activity) {
- return i;
- }
- }
- return INVALID_INDEX;
- }
- }
-
- /**
- * Chooses a activity to handle the current intent. This will result in
- * adding a historical record for that action and construct intent with
- * its component name set such that it can be immediately started by the
- * client.
- * <p>
- * <strong>Note:</strong> By calling this method the client guarantees
- * that the returned intent will be started. This intent is returned to
- * the client solely to let additional customization before the start.
- * </p>
- *
- * @return An {@link Intent} for launching the activity or null if the
- * policy has consumed the intent or there is not current intent
- * set via {@link #setIntent(Intent)}.
- *
- * @see HistoricalRecord
- * @see OnChooseActivityListener
- */
- public Intent chooseActivity(int index) {
- synchronized (mInstanceLock) {
- if (mIntent == null) {
- return null;
- }
-
- ensureConsistentState();
-
- ActivityResolveInfo chosenActivity = mActivities.get(index);
-
- ComponentName chosenName = new ComponentName(
- chosenActivity.resolveInfo.activityInfo.packageName,
- chosenActivity.resolveInfo.activityInfo.name);
-
- Intent choiceIntent = new Intent(mIntent);
- choiceIntent.setComponent(chosenName);
-
- if (mActivityChooserModelPolicy != null) {
- // Do not allow the policy to change the intent.
- Intent choiceIntentCopy = new Intent(choiceIntent);
- final boolean handled = mActivityChooserModelPolicy.onChooseActivity(this,
- choiceIntentCopy);
- if (handled) {
- return null;
- }
- }
-
- HistoricalRecord historicalRecord = new HistoricalRecord(chosenName,
- System.currentTimeMillis(), DEFAULT_HISTORICAL_RECORD_WEIGHT);
- addHistoricalRecord(historicalRecord);
-
- return choiceIntent;
- }
- }
-
- /**
- * Sets the listener for choosing an activity.
- *
- * @param listener The listener.
- */
- public void setOnChooseActivityListener(OnChooseActivityListener listener) {
- synchronized (mInstanceLock) {
- mActivityChooserModelPolicy = listener;
- }
- }
-
- /**
- * Gets the default activity, The default activity is defined as the one
- * with highest rank i.e. the first one in the list of activities that can
- * handle the intent.
- *
- * @return The default activity, <code>null</code> id not activities.
- *
- * @see #getActivity(int)
- */
- public ResolveInfo getDefaultActivity() {
- synchronized (mInstanceLock) {
- ensureConsistentState();
- if (!mActivities.isEmpty()) {
- return mActivities.get(0).resolveInfo;
- }
- }
- return null;
- }
-
- /**
- * Sets the default activity. The default activity is set by adding a
- * historical record with weight high enough that this activity will
- * become the highest ranked. Such a strategy guarantees that the default
- * will eventually change if not used. Also the weight of the record for
- * setting a default is inflated with a constant amount to guarantee that
- * it will stay as default for awhile.
- *
- * @param index The index of the activity to set as default.
- */
- public void setDefaultActivity(int index) {
- synchronized (mInstanceLock) {
- ensureConsistentState();
-
- ActivityResolveInfo newDefaultActivity = mActivities.get(index);
- ActivityResolveInfo oldDefaultActivity = mActivities.get(0);
-
- final float weight;
- if (oldDefaultActivity != null) {
- // Add a record with weight enough to boost the chosen at the top.
- weight = oldDefaultActivity.weight - newDefaultActivity.weight
- + DEFAULT_ACTIVITY_INFLATION;
- } else {
- weight = DEFAULT_HISTORICAL_RECORD_WEIGHT;
- }
-
- ComponentName defaultName = new ComponentName(
- newDefaultActivity.resolveInfo.activityInfo.packageName,
- newDefaultActivity.resolveInfo.activityInfo.name);
- HistoricalRecord historicalRecord = new HistoricalRecord(defaultName,
- System.currentTimeMillis(), weight);
- addHistoricalRecord(historicalRecord);
- }
- }
-
- /**
- * Persists the history data to the backing file if the latter
- * was provided. Calling this method before a call to {@link #readHistoricalDataIfNeeded()}
- * throws an exception. Calling this method more than one without choosing an
- * activity has not effect.
- *
- * @throws IllegalStateException If this method is called before a call to
- * {@link #readHistoricalDataIfNeeded()}.
- */
- private void persistHistoricalDataIfNeeded() {
- if (!mReadShareHistoryCalled) {
- throw new IllegalStateException("No preceding call to #readHistoricalData");
- }
- if (!mHistoricalRecordsChanged) {
- return;
- }
- mHistoricalRecordsChanged = false;
- if (!TextUtils.isEmpty(mHistoryFileName)) {
- /**
- * Mozilla: Converted to a normal task.execute call so that this works on < ICS phones.
- */
- new PersistHistoryAsyncTask().execute(new ArrayList<HistoricalRecord>(mHistoricalRecords), mHistoryFileName);
- }
- }
-
- /**
- * Sets the sorter for ordering activities based on historical data and an intent.
- *
- * @param activitySorter The sorter.
- *
- * @see ActivitySorter
- */
- public void setActivitySorter(ActivitySorter activitySorter) {
- synchronized (mInstanceLock) {
- if (mActivitySorter == activitySorter) {
- return;
- }
- mActivitySorter = activitySorter;
- if (sortActivitiesIfNeeded()) {
- notifyChanged();
- }
- }
- }
-
- /**
- * Sets the maximal size of the historical data. Defaults to
- * {@link #DEFAULT_HISTORY_MAX_LENGTH}
- * <p>
- * <strong>Note:</strong> Setting this property will immediately
- * enforce the specified max history size by dropping enough old
- * historical records to enforce the desired size. Thus, any
- * records that exceed the history size will be discarded and
- * irreversibly lost.
- * </p>
- *
- * @param historyMaxSize The max history size.
- */
- public void setHistoryMaxSize(int historyMaxSize) {
- synchronized (mInstanceLock) {
- if (mHistoryMaxSize == historyMaxSize) {
- return;
- }
- mHistoryMaxSize = historyMaxSize;
- pruneExcessiveHistoricalRecordsIfNeeded();
- if (sortActivitiesIfNeeded()) {
- notifyChanged();
- }
- }
- }
-
- /**
- * Gets the history max size.
- *
- * @return The history max size.
- */
- public int getHistoryMaxSize() {
- synchronized (mInstanceLock) {
- return mHistoryMaxSize;
- }
- }
-
- /**
- * Gets the history size.
- *
- * @return The history size.
- */
- public int getHistorySize() {
- synchronized (mInstanceLock) {
- ensureConsistentState();
- return mHistoricalRecords.size();
- }
- }
-
- public int getDistinctActivityCountInHistory() {
- synchronized (mInstanceLock) {
- ensureConsistentState();
- final List<String> packages = new ArrayList<String>();
- for (HistoricalRecord record : mHistoricalRecords) {
- String activity = record.activity.flattenToString();
- if (!packages.contains(activity)) {
- packages.add(activity);
- }
- }
- return packages.size();
- }
- }
-
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
-
- /**
- * Mozilla: Not needed for the application.
- */
- mPackageMonitor.unregister();
- FirefoxAccounts.removeSyncStatusListener(mSyncStatusListener);
- }
-
- /**
- * Ensures the model is in a consistent state which is the
- * activities for the current intent have been loaded, the
- * most recent history has been read, and the activities
- * are sorted.
- */
- private void ensureConsistentState() {
- boolean stateChanged = loadActivitiesIfNeeded();
- stateChanged |= readHistoricalDataIfNeeded();
- pruneExcessiveHistoricalRecordsIfNeeded();
- if (stateChanged) {
- sortActivitiesIfNeeded();
- notifyChanged();
- }
- }
-
- /**
- * Sorts the activities if necessary which is if there is a
- * sorter, there are some activities to sort, and there is some
- * historical data.
- *
- * @return Whether sorting was performed.
- */
- private boolean sortActivitiesIfNeeded() {
- if (mActivitySorter != null && mIntent != null
- && !mActivities.isEmpty() && !mHistoricalRecords.isEmpty()) {
- mActivitySorter.sort(mIntent, mActivities,
- Collections.unmodifiableList(mHistoricalRecords));
- return true;
- }
- return false;
- }
-
- /**
- * Loads the activities for the current intent if needed which is
- * if they are not already loaded for the current intent.
- *
- * @return Whether loading was performed.
- */
- private boolean loadActivitiesIfNeeded() {
- if (mReloadActivities && mIntent != null) {
- mReloadActivities = false;
- mActivities.clear();
- List<ResolveInfo> resolveInfos = mContext.getPackageManager()
- .queryIntentActivities(mIntent, 0);
- final int resolveInfoCount = resolveInfos.size();
-
- /**
- * Mozilla: Temporary variables to prevent performance degradation in the loop.
- */
- final PackageManager packageManager = mContext.getPackageManager();
- final String channelToRemoveLabel = mContext.getResources().getString(R.string.overlay_share_label);
- final String shareDialogClassName = ShareDialog.class.getCanonicalName();
-
- for (int i = 0; i < resolveInfoCount; i++) {
- ResolveInfo resolveInfo = resolveInfos.get(i);
-
- /**
- * Mozilla: We want "Add to Firefox" to appear differently inside of Firefox than
- * from external applications - override the name and icon here.
- *
- * Do not display the menu item if there are no devices to share to.
- *
- * Note: we check both the class name and the label to ensure we only change the
- * label of the current channel.
- */
- if (shareDialogClassName.equals(resolveInfo.activityInfo.name) &&
- channelToRemoveLabel.equals(resolveInfo.loadLabel(packageManager))) {
- // Don't add the menu item if there are no devices to share to.
- if (!hasOtherSyncClients()) {
- continue;
- }
-
- resolveInfo.labelRes = R.string.overlay_share_send_other;
- resolveInfo.icon = R.drawable.icon_shareplane;
- }
-
- mActivities.add(new ActivityResolveInfo(resolveInfo));
- }
- return true;
- }
- return false;
- }
-
- /**
- * Reads the historical data if necessary which is it has
- * changed, there is a history file, and there is not persist
- * in progress.
- *
- * @return Whether reading was performed.
- */
- private boolean readHistoricalDataIfNeeded() {
- if (mCanReadHistoricalData && mHistoricalRecordsChanged &&
- !TextUtils.isEmpty(mHistoryFileName)) {
- mCanReadHistoricalData = false;
- mReadShareHistoryCalled = true;
- readHistoricalDataImpl();
- return true;
- }
- return false;
- }
-
- /**
- * Adds a historical record.
- *
- * @param historicalRecord The record to add.
- * @return True if the record was added.
- */
- private boolean addHistoricalRecord(HistoricalRecord historicalRecord) {
- final boolean added = mHistoricalRecords.add(historicalRecord);
- if (added) {
- mHistoricalRecordsChanged = true;
- pruneExcessiveHistoricalRecordsIfNeeded();
- persistHistoricalDataIfNeeded();
- sortActivitiesIfNeeded();
- notifyChanged();
- }
- return added;
- }
-
- /**
- * Removes all historical records for this pkg.
- *
- * @param historicalRecord The pkg to delete records for.
- * @return True if the record was added.
- */
- boolean removeHistoricalRecordsForPackage(final String pkg) {
- boolean removed = false;
-
- for (Iterator<HistoricalRecord> i = mHistoricalRecords.iterator(); i.hasNext();) {
- final HistoricalRecord record = i.next();
- if (record.activity.getPackageName().equals(pkg)) {
- i.remove();
- removed = true;
- }
- }
-
- if (removed) {
- mHistoricalRecordsChanged = true;
- pruneExcessiveHistoricalRecordsIfNeeded();
- persistHistoricalDataIfNeeded();
- sortActivitiesIfNeeded();
- notifyChanged();
- }
-
- return removed;
- }
-
- /**
- * Prunes older excessive records to guarantee maxHistorySize.
- */
- private void pruneExcessiveHistoricalRecordsIfNeeded() {
- final int pruneCount = mHistoricalRecords.size() - mHistoryMaxSize;
- if (pruneCount <= 0) {
- return;
- }
- mHistoricalRecordsChanged = true;
- for (int i = 0; i < pruneCount; i++) {
- HistoricalRecord prunedRecord = mHistoricalRecords.remove(0);
- if (DEBUG) {
- Log.i(LOG_TAG, "Pruned: " + prunedRecord);
- }
- }
- }
-
- /**
- * Represents a record in the history.
- */
- public final static class HistoricalRecord {
-
- /**
- * The activity name.
- */
- public final ComponentName activity;
-
- /**
- * The choice time.
- */
- public final long time;
-
- /**
- * The record weight.
- */
- public final float weight;
-
- /**
- * Creates a new instance.
- *
- * @param activityName The activity component name flattened to string.
- * @param time The time the activity was chosen.
- * @param weight The weight of the record.
- */
- public HistoricalRecord(String activityName, long time, float weight) {
- this(ComponentName.unflattenFromString(activityName), time, weight);
- }
-
- /**
- * Creates a new instance.
- *
- * @param activityName The activity name.
- * @param time The time the activity was chosen.
- * @param weight The weight of the record.
- */
- public HistoricalRecord(ComponentName activityName, long time, float weight) {
- this.activity = activityName;
- this.time = time;
- this.weight = weight;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((activity == null) ? 0 : activity.hashCode());
- result = prime * result + (int) (time ^ (time >>> 32));
- result = prime * result + Float.floatToIntBits(weight);
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- HistoricalRecord other = (HistoricalRecord) obj;
- if (activity == null) {
- if (other.activity != null) {
- return false;
- }
- } else if (!activity.equals(other.activity)) {
- return false;
- }
- if (time != other.time) {
- return false;
- }
- if (Float.floatToIntBits(weight) != Float.floatToIntBits(other.weight)) {
- return false;
- }
- return true;
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("[");
- builder.append("; activity:").append(activity);
- builder.append("; time:").append(time);
- builder.append("; weight:").append(new BigDecimal(weight));
- builder.append("]");
- return builder.toString();
- }
- }
-
- /**
- * Represents an activity.
- */
- public final class ActivityResolveInfo implements Comparable<ActivityResolveInfo> {
-
- /**
- * The {@link ResolveInfo} of the activity.
- */
- public final ResolveInfo resolveInfo;
-
- /**
- * Weight of the activity. Useful for sorting.
- */
- public float weight;
-
- /**
- * Creates a new instance.
- *
- * @param resolveInfo activity {@link ResolveInfo}.
- */
- public ActivityResolveInfo(ResolveInfo resolveInfo) {
- this.resolveInfo = resolveInfo;
- }
-
- @Override
- public int hashCode() {
- return 31 + Float.floatToIntBits(weight);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- ActivityResolveInfo other = (ActivityResolveInfo) obj;
- if (Float.floatToIntBits(weight) != Float.floatToIntBits(other.weight)) {
- return false;
- }
- return true;
- }
-
- @Override
- public int compareTo(ActivityResolveInfo another) {
- return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight);
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("[");
- builder.append("resolveInfo:").append(resolveInfo.toString());
- builder.append("; weight:").append(new BigDecimal(weight));
- builder.append("]");
- return builder.toString();
- }
- }
-
- /**
- * Default activity sorter implementation.
- */
- private final class DefaultSorter implements ActivitySorter {
- private static final float WEIGHT_DECAY_COEFFICIENT = 0.95f;
-
- private final Map<String, ActivityResolveInfo> mPackageNameToActivityMap =
- new HashMap<String, ActivityResolveInfo>();
-
- @Override
- public void sort(Intent intent, List<ActivityResolveInfo> activities,
- List<HistoricalRecord> historicalRecords) {
- Map<String, ActivityResolveInfo> packageNameToActivityMap =
- mPackageNameToActivityMap;
- packageNameToActivityMap.clear();
-
- final int activityCount = activities.size();
- for (int i = 0; i < activityCount; i++) {
- ActivityResolveInfo activity = activities.get(i);
- activity.weight = 0.0f;
-
- // Make sure we're using a non-ambiguous name here
- ComponentName chosenName = new ComponentName(
- activity.resolveInfo.activityInfo.packageName,
- activity.resolveInfo.activityInfo.name);
- String packageName = chosenName.flattenToString();
- packageNameToActivityMap.put(packageName, activity);
- }
-
- final int lastShareIndex = historicalRecords.size() - 1;
- float nextRecordWeight = 1;
- for (int i = lastShareIndex; i >= 0; i--) {
- HistoricalRecord historicalRecord = historicalRecords.get(i);
- String packageName = historicalRecord.activity.flattenToString();
- ActivityResolveInfo activity = packageNameToActivityMap.get(packageName);
- if (activity != null) {
- activity.weight += historicalRecord.weight * nextRecordWeight;
- nextRecordWeight = nextRecordWeight * WEIGHT_DECAY_COEFFICIENT;
- }
- }
-
- Collections.sort(activities);
-
- if (DEBUG) {
- for (int i = 0; i < activityCount; i++) {
- Log.i(LOG_TAG, "Sorted: " + activities.get(i));
- }
- }
- }
- }
-
- /**
- * Command for reading the historical records from a file off the UI thread.
- */
- private void readHistoricalDataImpl() {
- try {
- GeckoProfile profile = GeckoProfile.get(mContext);
- File f = profile.getFile(mHistoryFileName);
- if (!f.exists()) {
- // Fall back to the non-profile aware file if it exists...
- File oldFile = new File(mHistoryFileName);
- oldFile.renameTo(f);
- }
- readHistoricalDataFromStream(new FileInputStream(f));
- } catch (FileNotFoundException fnfe) {
- final Distribution dist = Distribution.getInstance(mContext);
- dist.addOnDistributionReadyCallback(new Distribution.ReadyCallback() {
- @Override
- public void distributionNotFound() {
- }
-
- @Override
- public void distributionFound(Distribution distribution) {
- try {
- File distFile = dist.getDistributionFile("quickshare/" + mHistoryFileName);
- if (distFile == null) {
- if (DEBUG) {
- Log.i(LOG_TAG, "Could not open historical records file: " + mHistoryFileName);
- }
- return;
- }
- readHistoricalDataFromStream(new FileInputStream(distFile));
- } catch (Exception ex) {
- if (DEBUG) {
- Log.i(LOG_TAG, "Could not open historical records file: " + mHistoryFileName);
- }
- return;
- }
- }
-
- @Override
- public void distributionArrivedLate(Distribution distribution) {
- distributionFound(distribution);
- }
- });
- }
- }
-
- void readHistoricalDataFromStream(FileInputStream fis) {
- try {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(fis, null);
-
- int type = XmlPullParser.START_DOCUMENT;
- while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
- type = parser.next();
- }
-
- if (!TAG_HISTORICAL_RECORDS.equals(parser.getName())) {
- throw new XmlPullParserException("Share records file does not start with "
- + TAG_HISTORICAL_RECORDS + " tag.");
- }
-
- List<HistoricalRecord> historicalRecords = mHistoricalRecords;
- historicalRecords.clear();
-
- while (true) {
- type = parser.next();
- if (type == XmlPullParser.END_DOCUMENT) {
- break;
- }
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- String nodeName = parser.getName();
- if (!TAG_HISTORICAL_RECORD.equals(nodeName)) {
- throw new XmlPullParserException("Share records file not well-formed.");
- }
-
- String activity = parser.getAttributeValue(null, ATTRIBUTE_ACTIVITY);
- final long time =
- Long.parseLong(parser.getAttributeValue(null, ATTRIBUTE_TIME));
- final float weight =
- Float.parseFloat(parser.getAttributeValue(null, ATTRIBUTE_WEIGHT));
- HistoricalRecord readRecord = new HistoricalRecord(activity, time, weight);
- historicalRecords.add(readRecord);
-
- if (DEBUG) {
- Log.i(LOG_TAG, "Read " + readRecord.toString());
- }
- }
-
- if (DEBUG) {
- Log.i(LOG_TAG, "Read " + historicalRecords.size() + " historical records.");
- }
- } catch (XmlPullParserException | IOException xppe) {
- Log.e(LOG_TAG, "Error reading historical record file: " + mHistoryFileName, xppe);
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException ioe) {
- /* ignore */
- }
- }
- }
- }
-
- /**
- * Command for persisting the historical records to a file off the UI thread.
- */
- private final class PersistHistoryAsyncTask extends AsyncTask<Object, Void, Void> {
-
- @Override
- @SuppressWarnings("unchecked")
- public Void doInBackground(Object... args) {
- List<HistoricalRecord> historicalRecords = (List<HistoricalRecord>) args[0];
- String historyFileName = (String) args[1];
-
- FileOutputStream fos = null;
-
- try {
- // Mozilla - Update the location we save files to
- GeckoProfile profile = GeckoProfile.get(mContext);
- File file = profile.getFile(historyFileName);
- fos = new FileOutputStream(file);
- } catch (FileNotFoundException fnfe) {
- Log.e(LOG_TAG, "Error writing historical record file: " + historyFileName, fnfe);
- return null;
- }
-
- XmlSerializer serializer = Xml.newSerializer();
-
- try {
- serializer.setOutput(fos, null);
- serializer.startDocument("UTF-8", true);
- serializer.startTag(null, TAG_HISTORICAL_RECORDS);
-
- final int recordCount = historicalRecords.size();
- for (int i = 0; i < recordCount; i++) {
- HistoricalRecord record = historicalRecords.remove(0);
- serializer.startTag(null, TAG_HISTORICAL_RECORD);
- serializer.attribute(null, ATTRIBUTE_ACTIVITY,
- record.activity.flattenToString());
- serializer.attribute(null, ATTRIBUTE_TIME, String.valueOf(record.time));
- serializer.attribute(null, ATTRIBUTE_WEIGHT, String.valueOf(record.weight));
- serializer.endTag(null, TAG_HISTORICAL_RECORD);
- if (DEBUG) {
- Log.i(LOG_TAG, "Wrote " + record.toString());
- }
- }
-
- serializer.endTag(null, TAG_HISTORICAL_RECORDS);
- serializer.endDocument();
-
- if (DEBUG) {
- Log.i(LOG_TAG, "Wrote " + recordCount + " historical records.");
- }
- } catch (IllegalArgumentException | IOException | IllegalStateException e) {
- Log.e(LOG_TAG, "Error writing historical record file: " + mHistoryFileName, e);
- } finally {
- mCanReadHistoricalData = true;
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- /* ignore */
- }
- }
- }
- return null;
- }
- }
-
- /**
- * Keeps in sync the historical records and activities with the installed applications.
- */
- /**
- * Mozilla: Adapted significantly
- */
- private static final String LOGTAG = "GeckoActivityChooserModel";
- private final class DataModelPackageMonitor extends BroadcastReceiver {
- Context mContext;
-
- public DataModelPackageMonitor() { }
-
- public void register(Context context) {
- mContext = context;
-
- String[] intents = new String[] {
- Intent.ACTION_PACKAGE_REMOVED,
- Intent.ACTION_PACKAGE_ADDED,
- Intent.ACTION_PACKAGE_CHANGED
- };
-
- for (String intent : intents) {
- IntentFilter removeFilter = new IntentFilter(intent);
- removeFilter.addDataScheme("package");
- context.registerReceiver(this, removeFilter);
- }
- }
-
- public void unregister() {
- mContext.unregisterReceiver(this);
- mContext = null;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
- String packageName = intent.getData().getSchemeSpecificPart();
- removeHistoricalRecordsForPackage(packageName);
- }
-
- mReloadActivities = true;
- }
- }
-
- /**
- * Mozilla: Return whether or not there are other synced clients.
- */
- private boolean hasOtherSyncClients() {
- // ClientsDatabaseAccessor returns stale data (bug 1145896) so we work around this by
- // checking if we have accounts set up - if not, we can't have any clients.
- if (!FirefoxAccounts.firefoxAccountsExist(mContext)) {
- return false;
- }
-
- final BrowserDB browserDB = BrowserDB.from(mContext);
- final TabsAccessor tabsAccessor = browserDB.getTabsAccessor();
- final Cursor remoteClientsCursor = tabsAccessor
- .getRemoteClientsByRecencyCursor(mContext);
- if (remoteClientsCursor == null) {
- return false;
- }
-
- try {
- return remoteClientsCursor.getCount() > 0;
- } finally {
- remoteClientsCursor.close();
- }
- }
-
- /**
- * Mozilla: Reload activities on sync.
- */
- private class SyncStatusDelegate implements SyncStatusListener {
- @Override
- public Context getContext() {
- return mContext;
- }
-
- @Override
- public Account getAccount() {
- return FirefoxAccounts.getFirefoxAccount(getContext());
- }
-
- @Override
- public void onSyncStarted() {
- }
-
- @Override
- public void onSyncFinished() {
- // TODO: We only need to reload activities when the number of devices changes.
- // This may not be worth it if we have to touch the DB to get the client count.
- synchronized (mInstanceLock) {
- mReloadActivities = true;
- }
- }
- }
-}
-
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/AllCapsTextView.java b/mobile/android/base/java/org/mozilla/gecko/widget/AllCapsTextView.java
deleted file mode 100644
index 6bd1e36e4..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/AllCapsTextView.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.TextView;
-
-public class AllCapsTextView extends TextView {
-
- public AllCapsTextView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public void setText(CharSequence text, BufferType type) {
- super.setText(text.toString().toUpperCase(), type);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/AnchoredPopup.java b/mobile/android/base/java/org/mozilla/gecko/widget/AnchoredPopup.java
deleted file mode 100644
index a504c5832..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/AnchoredPopup.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.R;
-
-import android.app.Activity;
-import android.content.Context;
-import android.graphics.drawable.BitmapDrawable;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-import android.widget.PopupWindow;
-import org.mozilla.gecko.util.HardwareUtils;
-
-/**
- * AnchoredPopup is the base class for doorhanger notifications, and is anchored to the urlbar.
- */
-public abstract class AnchoredPopup extends PopupWindow {
- public interface OnVisibilityChangeListener {
- public void onDoorHangerShow();
- public void onDoorHangerHide();
- }
-
- private View mAnchor;
- private OnVisibilityChangeListener onVisibilityChangeListener;
-
- protected RoundedCornerLayout mContent;
- protected boolean mInflated;
-
- protected final Context mContext;
-
- public AnchoredPopup(Context context) {
- super(context);
-
- mContext = context;
-
- setAnimationStyle(R.style.PopupAnimation);
- }
-
- protected void init() {
- // Hide the default window background. Passing null prevents the below setOutTouchable()
- // call from working, so use an empty BitmapDrawable instead.
- setBackgroundDrawable(new BitmapDrawable(mContext.getResources()));
-
- // Allow the popup to be dismissed when touching outside.
- setOutsideTouchable(true);
-
- // PopupWindow has a default width and height of 0, so set the width here.
- int width = (int) mContext.getResources().getDimension(R.dimen.doorhanger_width);
- setWindowLayoutMode(0, ViewGroup.LayoutParams.WRAP_CONTENT);
- setWidth(width);
-
- final LayoutInflater inflater = LayoutInflater.from(mContext);
- final View layout = inflater.inflate(R.layout.anchored_popup, null);
- setContentView(layout);
-
- mContent = (RoundedCornerLayout) layout.findViewById(R.id.content);
-
- mInflated = true;
- }
-
- /**
- * Sets the anchor for this popup.
- *
- * @param anchor Anchor view for positioning the arrow.
- */
- public void setAnchor(View anchor) {
- mAnchor = anchor;
- }
-
- public void setOnVisibilityChangeListener(OnVisibilityChangeListener listener) {
- onVisibilityChangeListener = listener;
- }
-
- /**
- * Shows the popup with the arrow pointing to the center of the anchor view. If the anchor
- * isn't visible, the popup will just be shown at the top of the root view.
- */
- public void show() {
- if (!mInflated) {
- throw new IllegalStateException("ArrowPopup#init() must be called before ArrowPopup#show()");
- }
-
- if (onVisibilityChangeListener != null) {
- onVisibilityChangeListener.onDoorHangerShow();
- }
-
- final int[] anchorLocation = new int[2];
- if (mAnchor != null) {
- mAnchor.getLocationInWindow(anchorLocation);
- }
-
- // The doorhanger should overlap the bottom of the urlbar.
- int offsetY = mContext.getResources().getDimensionPixelOffset(R.dimen.doorhanger_offsetY);
- final View decorView = ((Activity) mContext).getWindow().getDecorView();
-
- final boolean validAnchor = (mAnchor != null) && (anchorLocation[1] > 0);
- if (HardwareUtils.isTablet()) {
- if (validAnchor) {
- showAsDropDown(mAnchor, 0, 0);
- } else {
- // The anchor will be offscreen if the dynamic toolbar is hidden, so anticipate the re-shown position
- // of the toolbar.
- final int offsetX = mContext.getResources().getDimensionPixelOffset(R.dimen.doorhanger_offsetX);
- showAtLocation(decorView, Gravity.TOP | Gravity.LEFT, offsetX, offsetY);
- }
- } else {
- // If the anchor is null or out of the window bounds, just show the popup at the top of the
- // root view.
- final View anchor = validAnchor ? mAnchor : decorView;
-
- showAtLocation(anchor, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, offsetY);
- }
- }
-
- @Override
- public void dismiss() {
- super.dismiss();
- if (onVisibilityChangeListener != null) {
- onVisibilityChangeListener.onDoorHangerHide();
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/AnimatedHeightLayout.java b/mobile/android/base/java/org/mozilla/gecko/widget/AnimatedHeightLayout.java
deleted file mode 100644
index f1343b0fb..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/AnimatedHeightLayout.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import org.mozilla.gecko.animation.HeightChangeAnimation;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.animation.Animation;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.RelativeLayout;
-
-public class AnimatedHeightLayout extends RelativeLayout {
- private static final String LOGTAG = "GeckoAnimatedHeightLayout";
- private static final int ANIMATION_DURATION = 100;
- private boolean mAnimating;
-
- public AnimatedHeightLayout(Context context) {
- super(context, null);
- }
-
- public AnimatedHeightLayout(Context context, AttributeSet attrs) {
- super(context, attrs, 0);
- }
-
- public AnimatedHeightLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int oldHeight = getMeasuredHeight();
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int newHeight = getMeasuredHeight();
-
- if (!mAnimating && oldHeight != 0 && oldHeight != newHeight) {
- mAnimating = true;
- setMeasuredDimension(getMeasuredWidth(), oldHeight);
-
- // Animate the difference of suggestion row height
- Animation anim = new HeightChangeAnimation(this, oldHeight, newHeight);
- anim.setDuration(ANIMATION_DURATION);
- anim.setInterpolator(new DecelerateInterpolator());
- anim.setAnimationListener(new Animation.AnimationListener() {
- @Override
- public void onAnimationStart(Animation animation) {}
- @Override
- public void onAnimationRepeat(Animation animation) {}
- @Override
- public void onAnimationEnd(Animation animation) {
- post(new Runnable() {
- @Override
- public void run() {
- finishAnimation();
- }
- });
- }
- });
- startAnimation(anim);
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- finishAnimation();
- }
-
- void finishAnimation() {
- if (mAnimating) {
- getLayoutParams().height = LayoutParams.WRAP_CONTENT;
- mAnimating = false;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/BasicColorPicker.java b/mobile/android/base/java/org/mozilla/gecko/widget/BasicColorPicker.java
deleted file mode 100644
index 4f1468203..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/BasicColorPicker.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import org.mozilla.gecko.R;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.ArrayAdapter;
-import android.widget.AdapterView;
-import android.widget.CheckedTextView;
-import android.widget.ListView;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
-
-public class BasicColorPicker extends ListView {
- private final static String LOGTAG = "GeckoBasicColorPicker";
- private final static List<Integer> DEFAULT_COLORS = Arrays.asList(Color.rgb(215, 57, 32),
- Color.rgb(255, 134, 5),
- Color.rgb(255, 203, 19),
- Color.rgb(95, 173, 71),
- Color.rgb(84, 201, 168),
- Color.rgb(33, 161, 222),
- Color.rgb(16, 36, 87),
- Color.rgb(91, 32, 103),
- Color.rgb(212, 221, 228),
- Color.BLACK);
-
- private static Drawable mCheckDrawable;
- int mSelected;
- final ColorPickerListAdapter mAdapter;
-
- public BasicColorPicker(Context context) {
- this(context, null);
- }
-
- public BasicColorPicker(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public BasicColorPicker(Context context, AttributeSet attrs, int style) {
- this(context, attrs, style, DEFAULT_COLORS);
- }
-
- public BasicColorPicker(Context context, AttributeSet attrs, int style, List<Integer> colors) {
- super(context, attrs, style);
- mAdapter = new ColorPickerListAdapter(context, new ArrayList<Integer>(colors));
- setAdapter(mAdapter);
-
- setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- mSelected = position;
- mAdapter.notifyDataSetChanged();
- }
- });
- }
-
- public int getColor() {
- return mAdapter.getItem(mSelected);
- }
-
- public void setColor(int color) {
- if (!DEFAULT_COLORS.contains(color)) {
- mSelected = mAdapter.getCount();
- mAdapter.add(color);
- } else {
- mSelected = DEFAULT_COLORS.indexOf(color);
- }
-
- setSelection(mSelected);
- mAdapter.notifyDataSetChanged();
- }
-
- Drawable getCheckDrawable() {
- if (mCheckDrawable == null) {
- Resources res = getContext().getResources();
-
- TypedValue typedValue = new TypedValue();
- getContext().getTheme().resolveAttribute(android.R.attr.listPreferredItemHeight, typedValue, true);
- DisplayMetrics metrics = new android.util.DisplayMetrics();
- ((WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(metrics);
- int height = (int) typedValue.getDimension(metrics);
-
- Drawable background = res.getDrawable(R.drawable.color_picker_row_bg);
- Rect r = new Rect();
- background.getPadding(r);
- height -= r.top + r.bottom;
-
- mCheckDrawable = res.getDrawable(R.drawable.color_picker_checkmark);
- mCheckDrawable.setBounds(0, 0, height, height);
- }
-
- return mCheckDrawable;
- }
-
- private class ColorPickerListAdapter extends ArrayAdapter<Integer> {
- private final List<Integer> mColors;
-
- public ColorPickerListAdapter(Context context, List<Integer> colors) {
- super(context, R.layout.color_picker_row, colors);
- mColors = colors;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View v = super.getView(position, convertView, parent);
-
- Drawable d = v.getBackground();
- d.setColorFilter(getItem(position), PorterDuff.Mode.MULTIPLY);
- v.setBackgroundDrawable(d);
-
- Drawable check = null;
- CheckedTextView checked = ((CheckedTextView) v);
- if (mSelected == position) {
- check = getCheckDrawable();
- }
-
- checked.setCompoundDrawables(check, null, null, null);
- checked.setText("");
-
- return v;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/CheckableLinearLayout.java b/mobile/android/base/java/org/mozilla/gecko/widget/CheckableLinearLayout.java
deleted file mode 100644
index b740592fe..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/CheckableLinearLayout.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.CheckBox;
-import android.widget.Checkable;
-import android.widget.LinearLayout;
-
-
-public class CheckableLinearLayout extends LinearLayout implements Checkable {
-
- private CheckBox mCheckBox;
-
- public CheckableLinearLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public boolean isChecked() {
- return mCheckBox != null && mCheckBox.isChecked();
- }
-
- @Override
- public void setChecked(boolean isChecked) {
- if (mCheckBox != null) {
- mCheckBox.setChecked(isChecked);
- }
- }
-
- @Override
- public void toggle() {
- if (mCheckBox != null) {
- mCheckBox.toggle();
- }
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mCheckBox = (CheckBox) findViewById(R.id.checkbox);
- mCheckBox.setClickable(false);
- }
-}
-
-
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/ClickableWhenDisabledEditText.java b/mobile/android/base/java/org/mozilla/gecko/widget/ClickableWhenDisabledEditText.java
deleted file mode 100644
index 206341212..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/ClickableWhenDisabledEditText.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.widget.EditText;
-
-public class ClickableWhenDisabledEditText extends EditText {
- public ClickableWhenDisabledEditText(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (!isEnabled() && event.getAction() == MotionEvent.ACTION_UP) {
- return performClick();
- }
- return super.onTouchEvent(event);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/ContentSecurityDoorHanger.java b/mobile/android/base/java/org/mozilla/gecko/widget/ContentSecurityDoorHanger.java
deleted file mode 100644
index 96b20a6c3..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/ContentSecurityDoorHanger.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import android.support.v4.content.ContextCompat;
-import android.util.Log;
-import android.widget.Button;
-import android.widget.TextView;
-import org.mozilla.gecko.R;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.content.Context;
-import android.view.View;
-
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.toolbar.SiteIdentityPopup;
-
-import java.util.Locale;
-
-public class ContentSecurityDoorHanger extends DoorHanger {
- private static final String LOGTAG = "GeckoSecurityDoorHanger";
-
- private final TextView mTitle;
- private final TextView mSecurityState;
- private final TextView mMessage;
-
- public ContentSecurityDoorHanger(Context context, DoorhangerConfig config, Type type) {
- super(context, config, type);
-
- mTitle = (TextView) findViewById(R.id.security_title);
- mSecurityState = (TextView) findViewById(R.id.security_state);
- mMessage = (TextView) findViewById(R.id.security_message);
-
- loadConfig(config);
- }
-
- @Override
- protected void loadConfig(DoorhangerConfig config) {
- final String message = config.getMessage();
- if (message != null) {
- mMessage.setText(message);
- }
-
- final JSONObject options = config.getOptions();
- if (options != null) {
- setOptions(options);
- }
-
- final DoorhangerConfig.Link link = config.getLink();
- if (link != null) {
- addLink(link.label, link.url);
- }
-
- addButtonsToLayout(config);
- }
-
- @Override
- protected int getContentResource() {
- return R.layout.doorhanger_security;
- }
-
- @Override
- public void setOptions(final JSONObject options) {
- super.setOptions(options);
- final JSONObject link = options.optJSONObject("link");
- if (link != null) {
- try {
- final String linkLabel = link.getString("label");
- final String linkUrl = link.getString("url");
- addLink(linkLabel, linkUrl);
- } catch (JSONException e) { }
- }
-
- final JSONObject trackingProtection = options.optJSONObject("tracking_protection");
- if (trackingProtection != null) {
- mTitle.setVisibility(VISIBLE);
- mTitle.setText(R.string.doorhanger_tracking_title);
- try {
- final boolean enabled = trackingProtection.getBoolean("enabled");
- if (enabled) {
- mMessage.setText(R.string.doorhanger_tracking_message_enabled);
- mSecurityState.setText(R.string.doorhanger_tracking_state_enabled);
- mSecurityState.setTextColor(ContextCompat.getColor(getContext(), R.color.affirmative_green));
- } else {
- mMessage.setText(R.string.doorhanger_tracking_message_disabled);
- mSecurityState.setText(R.string.doorhanger_tracking_state_disabled);
- mSecurityState.setTextColor(ContextCompat.getColor(getContext(), R.color.rejection_red));
- }
- mMessage.setVisibility(VISIBLE);
- mSecurityState.setVisibility(VISIBLE);
- } catch (JSONException e) { }
- }
- }
-
- @Override
- protected OnClickListener makeOnButtonClickListener(final int id, final String telemetryExtra) {
- return new Button.OnClickListener() {
- @Override
- public void onClick(View v) {
- final String expandedExtra = mType.toString().toLowerCase(Locale.US) + "-" + telemetryExtra;
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.DOORHANGER, expandedExtra);
-
- final JSONObject response = new JSONObject();
- try {
- switch (mType) {
- case TRACKING:
- response.put("allowContent", (id == SiteIdentityPopup.ButtonType.DISABLE.ordinal()));
- response.put("contentType", ("tracking"));
- break;
- default:
- Log.w(LOGTAG, "Unknown doorhanger type " + mType.toString());
- }
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error creating onClick response", e);
- }
-
- mOnButtonClickListener.onButtonClick(response, ContentSecurityDoorHanger.this);
- }
- };
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/CropImageView.java b/mobile/android/base/java/org/mozilla/gecko/widget/CropImageView.java
deleted file mode 100644
index 63cb84c5a..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/CropImageView.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Matrix;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.widget.ImageView;
-
-import org.mozilla.gecko.widget.themed.ThemedImageView;
-
-/**
- * An ImageView which will always display at the given width and calculated height (based on the width and
- * the supplied aspect ratio), drawn starting from the top left hand corner. A supplied drawable will be resized to fit
- * the width of the view; if the resized drawable is too tall for the view then the drawable will be cropped at the
- * bottom, however if the resized drawable is too short for the view to display whilst honouring it's given width and
- * height then the drawable will be displayed at full height with the right hand side cropped.
- */
-public abstract class CropImageView extends ThemedImageView {
- public static final String LOGTAG = "Gecko" + CropImageView.class.getSimpleName();
-
- private int viewWidth;
- private int viewHeight;
- private int drawableWidth;
- private int drawableHeight;
-
- private boolean resize = true;
- private Matrix layoutCurrentMatrix = new Matrix();
- private Matrix layoutNextMatrix = new Matrix();
-
-
- public CropImageView(final Context context) {
- this(context, null);
- }
-
- public CropImageView(final Context context, final AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public CropImageView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- init();
- }
-
- protected abstract float getAspectRatio();
-
- protected void init() {
- // Setting the pivots means that the image will be drawn from the top left hand corner. There are
- // issues in Android 4.1 (16) which mean setting these values to 0 may not work.
- // http://stackoverflow.com/questions/26658124/setpivotx-doesnt-work-on-android-4-1-1-nineoldandroids
- setPivotX(1);
- setPivotY(1);
- }
-
- /**
- * Measure the view to determine the measured width and height.
- * The height is constrained by the measured width.
- *
- * @param widthMeasureSpec horizontal space requirements as imposed by the parent.
- * @param heightMeasureSpec vertical space requirements as imposed by the parent, but ignored.
- */
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- // Default measuring.
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- // Force the height based on the aspect ratio.
- viewWidth = getMeasuredWidth();
- viewHeight = (int) (viewWidth * getAspectRatio());
-
- setMeasuredDimension(viewWidth, viewHeight);
-
- updateImageMatrix();
- }
-
- protected void updateImageMatrix() {
- if (!resize || getDrawable() == null) {
- return;
- }
-
- setScaleType(ImageView.ScaleType.MATRIX);
-
- getDrawable().setBounds(0, 0, viewWidth, viewHeight);
-
- final float horizontalScaleValue = (float) viewWidth / (float) drawableWidth;
- final float verticalScaleValue = (float) viewHeight / (float) drawableHeight;
-
- final float scale = Math.max(verticalScaleValue, horizontalScaleValue);
-
- layoutNextMatrix.reset();
- layoutNextMatrix.setScale(scale, scale);
- setImageMatrix(layoutNextMatrix);
-
- // You can't modify the matrix in place and we want to avoid allocation, so let's keep two references to two
- // different matrix objects that we can swap when the values need to change
- final Matrix swapReferenceMatrix = layoutCurrentMatrix;
- layoutCurrentMatrix = layoutNextMatrix;
- layoutNextMatrix = swapReferenceMatrix;
- }
-
- public void setImageBitmap(final Bitmap bm, final boolean resize) {
- super.setImageBitmap(bm);
-
- this.resize = resize;
- updateImageMatrix();
- }
-
- @Override
- public void setImageResource(final int resId) {
- super.setImageResource(resId);
- setImageMatrix(null);
- resize = false;
- }
-
- @Override
- public void setImageDrawable(final Drawable drawable) {
- this.setImageDrawable(drawable, false);
- }
-
- public void setImageDrawable(final Drawable drawable, final boolean resize) {
- super.setImageDrawable(drawable);
-
- if (drawable != null) {
- // Reset the matrix to ensure that any previous changes aren't carried through.
- setImageMatrix(null);
-
- drawableWidth = drawable.getIntrinsicWidth();
- drawableHeight = drawable.getIntrinsicHeight();
- } else {
- drawableWidth = -1;
- drawableHeight = -1;
- }
-
- this.resize = resize;
-
- updateImageMatrix();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/DateTimePicker.java b/mobile/android/base/java/org/mozilla/gecko/widget/DateTimePicker.java
deleted file mode 100644
index 67f1bcd1d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/DateTimePicker.java
+++ /dev/null
@@ -1,665 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.mozilla.gecko.widget;
-
-import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Locale;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.AppConstants.Versions;
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.text.format.DateFormat;
-import android.text.format.DateUtils;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.Display;
-import android.view.LayoutInflater;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.CalendarView;
-import android.widget.EditText;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-import android.widget.NumberPicker;
-
-public class DateTimePicker extends FrameLayout {
- private static final boolean DEBUG = true;
- private static final String LOGTAG = "GeckoDateTimePicker";
- private static final int DEFAULT_START_YEAR = 1;
- private static final int DEFAULT_END_YEAR = 9999;
- private static final char DATE_FORMAT_DAY = 'd';
- private static final char DATE_FORMAT_MONTH = 'M';
- private static final char DATE_FORMAT_YEAR = 'y';
-
- boolean mYearEnabled = true;
- boolean mMonthEnabled = true;
- boolean mWeekEnabled;
- boolean mDayEnabled = true;
- boolean mHourEnabled = true;
- boolean mMinuteEnabled = true;
- boolean mIs12HourMode;
- private boolean mCalendarEnabled;
-
- // Size of the screen in inches;
- private final int mScreenWidth;
- private final int mScreenHeight;
- private final OnValueChangeListener mOnChangeListener;
- private final LinearLayout mPickers;
- private final LinearLayout mDateSpinners;
- private final LinearLayout mTimeSpinners;
-
- final NumberPicker mDaySpinner;
- final NumberPicker mMonthSpinner;
- final NumberPicker mWeekSpinner;
- final NumberPicker mYearSpinner;
- final NumberPicker mHourSpinner;
- final NumberPicker mMinuteSpinner;
- final NumberPicker mAMPMSpinner;
- private final CalendarView mCalendar;
- private final EditText mDaySpinnerInput;
- private final EditText mMonthSpinnerInput;
- private final EditText mWeekSpinnerInput;
- private final EditText mYearSpinnerInput;
- private final EditText mHourSpinnerInput;
- private final EditText mMinuteSpinnerInput;
- private final EditText mAMPMSpinnerInput;
- private Locale mCurrentLocale;
- private String[] mShortMonths;
- private String[] mShortAMPMs;
- private int mNumberOfMonths;
-
- Calendar mTempDate;
- Calendar mCurrentDate;
- private Calendar mMinDate;
- private Calendar mMaxDate;
- private final PickersState mState;
-
- public static enum PickersState { DATE, MONTH, WEEK, TIME, DATETIME };
-
- public class OnValueChangeListener implements NumberPicker.OnValueChangeListener {
- @Override
- public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
- updateInputState();
- mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
- if (DEBUG) {
- Log.d(LOGTAG, "SDK version > 10, using new behavior");
- }
-
- // The native date picker widget on these SDKs increments
- // the next field when one field reaches the maximum.
- if (picker == mDaySpinner && mDayEnabled) {
- int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
- int old = mTempDate.get(Calendar.DAY_OF_MONTH);
- setTempDate(Calendar.DAY_OF_MONTH, old, newVal, 1, maxDayOfMonth);
- } else if (picker == mMonthSpinner && mMonthEnabled) {
- int old = mTempDate.get(Calendar.MONTH);
- setTempDate(Calendar.MONTH, old, newVal, Calendar.JANUARY, Calendar.DECEMBER);
- } else if (picker == mWeekSpinner) {
- int old = mTempDate.get(Calendar.WEEK_OF_YEAR);
- int maxWeekOfYear = mTempDate.getActualMaximum(Calendar.WEEK_OF_YEAR);
- setTempDate(Calendar.WEEK_OF_YEAR, old, newVal, 0, maxWeekOfYear);
- } else if (picker == mYearSpinner && mYearEnabled) {
- int month = mTempDate.get(Calendar.MONTH);
- mTempDate.set(Calendar.YEAR, newVal);
- // Changing the year shouldn't change the month. (in case of non-leap year a Feb 29)
- // change the day instead;
- if (month != mTempDate.get(Calendar.MONTH)) {
- mTempDate.set(Calendar.MONTH, month);
- mTempDate.set(Calendar.DAY_OF_MONTH,
- mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH));
- }
- } else if (picker == mHourSpinner && mHourEnabled) {
- if (mIs12HourMode) {
- setTempDate(Calendar.HOUR, oldVal, newVal, 1, 12);
- } else {
- setTempDate(Calendar.HOUR_OF_DAY, oldVal, newVal, 0, 23);
- }
- } else if (picker == mMinuteSpinner && mMinuteEnabled) {
- setTempDate(Calendar.MINUTE, oldVal, newVal, 0, 59);
- } else if (picker == mAMPMSpinner && mHourEnabled) {
- mTempDate.set(Calendar.AM_PM, newVal);
- } else {
- throw new IllegalArgumentException();
- }
- setDate(mTempDate);
- if (mDayEnabled) {
- mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
- }
- if (mWeekEnabled) {
- mWeekSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.WEEK_OF_YEAR));
- }
- updateCalendar();
- updateSpinners();
- notifyDateChanged();
- }
-
- private void setTempDate(int field, int oldVal, int newVal, int min, int max) {
- if (oldVal == max && newVal == min) {
- mTempDate.add(field, 1);
- } else if (oldVal == min && newVal == max) {
- mTempDate.add(field, -1);
- } else {
- mTempDate.add(field, newVal - oldVal);
- }
- }
- }
-
- private static final NumberPicker.Formatter TWO_DIGIT_FORMATTER = new NumberPicker.Formatter() {
- final StringBuilder mBuilder = new StringBuilder();
-
- final java.util.Formatter mFmt = new java.util.Formatter(mBuilder, java.util.Locale.US);
-
- final Object[] mArgs = new Object[1];
-
- @Override
- public String format(int value) {
- mArgs[0] = value;
- mBuilder.delete(0, mBuilder.length());
- mFmt.format("%02d", mArgs);
- return mFmt.toString();
- }
- };
-
- private void displayPickers() {
- setWeekShown(false);
- set12HourShown(mIs12HourMode);
- if (mState == PickersState.DATETIME) {
- return;
- }
-
- setHourShown(false);
- setMinuteShown(false);
- if (mState == PickersState.WEEK) {
- setDayShown(false);
- setMonthShown(false);
- setWeekShown(true);
- } else if (mState == PickersState.MONTH) {
- setDayShown(false);
- }
- }
-
- public DateTimePicker(Context context) {
- this(context, "", "", PickersState.DATE, null, null);
- }
-
- public DateTimePicker(Context context, String dateFormat, String dateTimeValue, PickersState state, String minDateValue, String maxDateValue) {
- super(context);
-
- setCurrentLocale(Locale.getDefault());
-
- mState = state;
- LayoutInflater inflater = LayoutInflater.from(context);
- inflater.inflate(R.layout.datetime_picker, this, true);
-
- mOnChangeListener = new OnValueChangeListener();
-
- mDateSpinners = (LinearLayout)findViewById(R.id.date_spinners);
- mTimeSpinners = (LinearLayout)findViewById(R.id.time_spinners);
- mPickers = (LinearLayout)findViewById(R.id.datetime_picker);
-
- // We will display differently according to the screen size width.
- WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- Display display = wm.getDefaultDisplay();
- DisplayMetrics dm = new DisplayMetrics();
- display.getMetrics(dm);
- mScreenWidth = display.getWidth() / dm.densityDpi;
- mScreenHeight = display.getHeight() / dm.densityDpi;
-
- if (DEBUG) {
- Log.d(LOGTAG, "screen width: " + mScreenWidth + " screen height: " + mScreenHeight);
- }
-
- // Set the min / max attribute.
- try {
- if (minDateValue != null && !minDateValue.equals("")) {
- mMinDate.setTime(new SimpleDateFormat(dateFormat).parse(minDateValue));
- } else {
- mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
- }
- } catch (Exception ex) {
- Log.e(LOGTAG, "Error parsing format sting: " + ex);
- mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
- }
-
- try {
- if (maxDateValue != null && !maxDateValue.equals("")) {
- mMaxDate.setTime(new SimpleDateFormat(dateFormat).parse(maxDateValue));
- } else {
- mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
- }
- } catch (Exception ex) {
- Log.e(LOGTAG, "Error parsing format string: " + ex);
- mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
- }
-
- // Find the initial date from the constructor arguments.
- try {
- if (!dateTimeValue.equals("")) {
- mTempDate.setTime(new SimpleDateFormat(dateFormat).parse(dateTimeValue));
- } else {
- mTempDate.setTimeInMillis(System.currentTimeMillis());
- }
- } catch (Exception ex) {
- Log.e(LOGTAG, "Error parsing format string: " + ex);
- mTempDate.setTimeInMillis(System.currentTimeMillis());
- }
-
- if (mMaxDate.before(mMinDate)) {
- // If the input date range is illogical/garbage, we should not restrict the input range (i.e. allow the
- // user to select any date). If we try to make any assumptions based on the illogical min/max date we could
- // potentially prevent the user from selecting dates that are in the developers intended range, so it's best
- // to allow anything.
- mMinDate.set(DEFAULT_START_YEAR, Calendar.JANUARY, 1);
- mMaxDate.set(DEFAULT_END_YEAR, Calendar.DECEMBER, 31);
- }
-
- // mTempDate will either be a site-supplied value, or today's date otherwise. CalendarView implementations can
- // crash if they're supplied an invalid date (i.e. a date not in the specified range), hence we need to set
- // a sensible default date here.
- if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
- mTempDate.setTimeInMillis(mMinDate.getTimeInMillis());
- }
-
- // If we're displaying a date, the screen is wide enough
- // (and if we're using an SDK where the calendar view exists)
- // then display a calendar.
- if (mState == PickersState.DATE || mState == PickersState.DATETIME) {
- mCalendar = new CalendarView(context);
- mCalendar.setVisibility(GONE);
-
- mCalendar.setFocusable(true);
- mCalendar.setFocusableInTouchMode(true);
- mCalendar.setMaxDate(mMaxDate.getTimeInMillis());
- mCalendar.setMinDate(mMinDate.getTimeInMillis());
- mCalendar.setDate(mTempDate.getTimeInMillis(), false, false);
-
- mCalendar.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
- @Override
- public void onSelectedDayChange(
- CalendarView view, int year, int month, int monthDay) {
- mTempDate.set(year, month, monthDay);
- setDate(mTempDate);
- notifyDateChanged();
- }
- });
-
- final int height;
- if (Versions.preLollipop) {
- // The 4.X version of CalendarView doesn't request any height, resulting in
- // the whole dialog not appearing unless we manually request height.
- height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200, getResources().getDisplayMetrics());;
- } else {
- height = LayoutParams.WRAP_CONTENT;
- }
-
- mPickers.addView(mCalendar, LayoutParams.MATCH_PARENT, height);
-
- } else {
- // If the screen is more wide than high, we are displaying day and
- // time spinners, and if there is no calendar displayed, we should
- // display the fields in one row.
- if (mScreenWidth > mScreenHeight && mState == PickersState.DATETIME) {
- mPickers.setOrientation(LinearLayout.HORIZONTAL);
- }
- mCalendar = null;
- }
-
- // Initialize all spinners.
- mDaySpinner = setupSpinner(R.id.day, 1,
- mTempDate.get(Calendar.DAY_OF_MONTH));
- mDaySpinner.setFormatter(TWO_DIGIT_FORMATTER);
- mDaySpinnerInput = (EditText) mDaySpinner.getChildAt(1);
-
- mMonthSpinner = setupSpinner(R.id.month, 1,
- mTempDate.get(Calendar.MONTH) + 1); // Month is 0-based
- mMonthSpinner.setFormatter(TWO_DIGIT_FORMATTER);
- mMonthSpinner.setDisplayedValues(mShortMonths);
- mMonthSpinnerInput = (EditText) mMonthSpinner.getChildAt(1);
-
- mWeekSpinner = setupSpinner(R.id.week, 1,
- mTempDate.get(Calendar.WEEK_OF_YEAR));
- mWeekSpinner.setFormatter(TWO_DIGIT_FORMATTER);
- mWeekSpinnerInput = (EditText) mWeekSpinner.getChildAt(1);
-
- mYearSpinner = setupSpinner(R.id.year, DEFAULT_START_YEAR,
- DEFAULT_END_YEAR);
- mYearSpinnerInput = (EditText) mYearSpinner.getChildAt(1);
-
- mAMPMSpinner = setupSpinner(R.id.ampm, 0, 1);
- mAMPMSpinner.setFormatter(TWO_DIGIT_FORMATTER);
-
- if (mIs12HourMode) {
- mHourSpinner = setupSpinner(R.id.hour, 1, 12);
- mAMPMSpinnerInput = (EditText) mAMPMSpinner.getChildAt(1);
- mAMPMSpinner.setDisplayedValues(mShortAMPMs);
- } else {
- mHourSpinner = setupSpinner(R.id.hour, 0, 23);
- mAMPMSpinnerInput = null;
- }
-
- mHourSpinner.setFormatter(TWO_DIGIT_FORMATTER);
- mHourSpinnerInput = (EditText) mHourSpinner.getChildAt(1);
-
- mMinuteSpinner = setupSpinner(R.id.minute, 0, 59);
- mMinuteSpinner.setFormatter(TWO_DIGIT_FORMATTER);
- mMinuteSpinnerInput = (EditText) mMinuteSpinner.getChildAt(1);
-
- // The order in which the spinners are displayed are locale-dependent
- reorderDateSpinners();
-
- // Set the date to the initial date. Since this date can come from the user,
- // it can fire an exception (out-of-bound date)
- try {
- updateDate(mTempDate);
- } catch (Exception ex) {
- }
-
- // Display only the pickers needed for the current state.
- displayPickers();
- }
-
- public NumberPicker setupSpinner(int id, int min, int max) {
- NumberPicker mSpinner = (NumberPicker) findViewById(id);
- mSpinner.setMinValue(min);
- mSpinner.setMaxValue(max);
- mSpinner.setOnValueChangedListener(mOnChangeListener);
- mSpinner.setOnLongPressUpdateInterval(100);
- return mSpinner;
- }
-
- public long getTimeInMillis() {
- return mCurrentDate.getTimeInMillis();
- }
-
- private void reorderDateSpinners() {
- mDateSpinners.removeAllViews();
- char[] order = DateFormat.getDateFormatOrder(getContext());
- final int spinnerCount = order.length;
-
- for (int i = 0; i < spinnerCount; i++) {
- switch (order[i]) {
- case DATE_FORMAT_DAY:
- mDateSpinners.addView(mDaySpinner);
- break;
- case DATE_FORMAT_MONTH:
- mDateSpinners.addView(mMonthSpinner);
- break;
- case DATE_FORMAT_YEAR:
- mDateSpinners.addView(mYearSpinner);
- break;
- default:
- throw new IllegalArgumentException();
- }
- }
-
- mDateSpinners.addView(mWeekSpinner);
- }
-
- void setDate(Calendar calendar) {
- mCurrentDate = mTempDate;
- if (mCurrentDate.before(mMinDate)) {
- mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
- } else if (mCurrentDate.after(mMaxDate)) {
- mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
- }
- }
-
- void updateInputState() {
- InputMethodManager inputMethodManager = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- if (mYearEnabled && inputMethodManager.isActive(mYearSpinnerInput)) {
- mYearSpinnerInput.clearFocus();
- inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
- } else if (mMonthEnabled && inputMethodManager.isActive(mMonthSpinnerInput)) {
- mMonthSpinnerInput.clearFocus();
- inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
- } else if (mDayEnabled && inputMethodManager.isActive(mDaySpinnerInput)) {
- mDaySpinnerInput.clearFocus();
- inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
- } else if (mHourEnabled && inputMethodManager.isActive(mHourSpinnerInput)) {
- mHourSpinnerInput.clearFocus();
- inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
- } else if (mMinuteEnabled && inputMethodManager.isActive(mMinuteSpinnerInput)) {
- mMinuteSpinnerInput.clearFocus();
- inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
- }
- }
-
- void updateSpinners() {
- if (mDayEnabled) {
- if (mCurrentDate.equals(mMinDate)) {
- mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
- mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
- } else if (mCurrentDate.equals(mMaxDate)) {
- mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
- mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
- } else {
- mDaySpinner.setMinValue(1);
- mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
- }
- mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
- }
-
- if (mWeekEnabled) {
- mWeekSpinner.setMinValue(1);
- mWeekSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.WEEK_OF_YEAR));
- mWeekSpinner.setValue(mCurrentDate.get(Calendar.WEEK_OF_YEAR));
- }
-
- if (mMonthEnabled) {
- mMonthSpinner.setDisplayedValues(null);
- if (mCurrentDate.equals(mMinDate)) {
- mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
- mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
- } else if (mCurrentDate.equals(mMaxDate)) {
- mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
- mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
- } else {
- mMonthSpinner.setMinValue(Calendar.JANUARY);
- mMonthSpinner.setMaxValue(Calendar.DECEMBER);
- }
-
- String[] displayedValues = Arrays.copyOfRange(mShortMonths,
- mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
- mMonthSpinner.setDisplayedValues(displayedValues);
- mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
- }
-
- if (mYearEnabled) {
- mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
- mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
- mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
- }
-
- if (mHourEnabled) {
- if (mIs12HourMode) {
- mHourSpinner.setValue(mCurrentDate.get(Calendar.HOUR));
- mAMPMSpinner.setValue(mCurrentDate.get(Calendar.AM_PM));
- mAMPMSpinner.setDisplayedValues(mShortAMPMs);
- } else {
- mHourSpinner.setValue(mCurrentDate.get(Calendar.HOUR_OF_DAY));
- }
- }
- if (mMinuteEnabled) {
- mMinuteSpinner.setValue(mCurrentDate.get(Calendar.MINUTE));
- }
- }
-
- void updateCalendar() {
- if (mCalendarEnabled) {
- mCalendar.setDate(mCurrentDate.getTimeInMillis(), false, false);
- }
- }
-
- void notifyDateChanged() {
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
- }
-
- public void toggleCalendar(boolean shown) {
- if ((mState != PickersState.DATE && mState != PickersState.DATETIME)) {
- return;
- }
-
- if (shown) {
- mCalendarEnabled = true;
- mCalendar.setVisibility(VISIBLE);
- setYearShown(false);
- setWeekShown(false);
- setMonthShown(false);
- setDayShown(false);
- } else {
- mCalendar.setVisibility(GONE);
- setYearShown(true);
- setMonthShown(true);
- setDayShown(true);
- mPickers.setOrientation(LinearLayout.HORIZONTAL);
- mCalendarEnabled = false;
- }
- }
-
- private void setYearShown(boolean shown) {
- if (shown) {
- toggleCalendar(false);
- mYearSpinner.setVisibility(VISIBLE);
- mYearEnabled = true;
- } else {
- mYearSpinner.setVisibility(GONE);
- mYearEnabled = false;
- }
- }
-
- private void setWeekShown(boolean shown) {
- if (shown) {
- toggleCalendar(false);
- mWeekSpinner.setVisibility(VISIBLE);
- mWeekEnabled = true;
- } else {
- mWeekSpinner.setVisibility(GONE);
- mWeekEnabled = false;
- }
- }
-
- private void setMonthShown(boolean shown) {
- if (shown) {
- toggleCalendar(false);
- mMonthSpinner.setVisibility(VISIBLE);
- mMonthEnabled = true;
- } else {
- mMonthSpinner.setVisibility(GONE);
- mMonthEnabled = false;
- }
- }
-
- private void setDayShown(boolean shown) {
- if (shown) {
- toggleCalendar(false);
- mDaySpinner.setVisibility(VISIBLE);
- mDayEnabled = true;
- } else {
- mDaySpinner.setVisibility(GONE);
- mDayEnabled = false;
- }
- }
-
- private void set12HourShown(boolean shown) {
- if (shown) {
- mAMPMSpinner.setVisibility(VISIBLE);
- } else {
- mAMPMSpinner.setVisibility(GONE);
- }
- }
-
- private void setHourShown(boolean shown) {
- if (shown) {
- mHourSpinner.setVisibility(VISIBLE);
- mHourEnabled = true;
- } else {
- mHourSpinner.setVisibility(GONE);
- mAMPMSpinner.setVisibility(GONE);
- mTimeSpinners.setVisibility(GONE);
- mHourEnabled = false;
- }
- }
-
- private void setMinuteShown(boolean shown) {
- if (shown) {
- mMinuteSpinner.setVisibility(VISIBLE);
- mTimeSpinners.findViewById(R.id.mincolon).setVisibility(VISIBLE);
- mMinuteEnabled = true;
- } else {
- mMinuteSpinner.setVisibility(GONE);
- mTimeSpinners.findViewById(R.id.mincolon).setVisibility(GONE);
- mMinuteEnabled = false;
- }
- }
-
- private void setCurrentLocale(Locale locale) {
- if (locale.equals(mCurrentLocale)) {
- return;
- }
-
- mCurrentLocale = locale;
- mIs12HourMode = !DateFormat.is24HourFormat(getContext());
- mTempDate = getCalendarForLocale(mTempDate, locale);
- mMinDate = getCalendarForLocale(mMinDate, locale);
- mMaxDate = getCalendarForLocale(mMaxDate, locale);
- mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
-
- mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1;
-
- mShortAMPMs = new String[2];
- mShortAMPMs[0] = DateUtils.getAMPMString(Calendar.AM);
- mShortAMPMs[1] = DateUtils.getAMPMString(Calendar.PM);
-
- mShortMonths = new String[mNumberOfMonths];
- for (int i = 0; i < mNumberOfMonths; i++) {
- mShortMonths[i] = DateUtils.getMonthString(Calendar.JANUARY + i,
- DateUtils.LENGTH_MEDIUM);
- }
- }
-
- private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
- if (oldCalendar == null) {
- return Calendar.getInstance(locale);
- }
-
- final long currentTimeMillis = oldCalendar.getTimeInMillis();
- Calendar newCalendar = Calendar.getInstance(locale);
- newCalendar.setTimeInMillis(currentTimeMillis);
- return newCalendar;
- }
-
- public void updateDate(Calendar calendar) {
- if (mCurrentDate.equals(calendar)) {
- return;
- }
- mCurrentDate.setTimeInMillis(calendar.getTimeInMillis());
- if (mCurrentDate.before(mMinDate)) {
- mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
- } else if (mCurrentDate.after(mMaxDate)) {
- mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
- }
- updateSpinners();
- notifyDateChanged();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/DefaultDoorHanger.java b/mobile/android/base/java/org/mozilla/gecko/widget/DefaultDoorHanger.java
deleted file mode 100644
index cb8716af7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/DefaultDoorHanger.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import android.support.v4.content.ContextCompat;
-import android.text.Html;
-import android.text.Spanned;
-import android.util.Log;
-import android.widget.Button;
-import android.widget.TextView;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.prompts.PromptInput;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.content.Context;
-import android.text.TextUtils;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CheckBox;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-public class DefaultDoorHanger extends DoorHanger {
- private static final String LOGTAG = "GeckoDefaultDoorHanger";
-
- private static int sSpinnerTextColor = -1;
-
- private final TextView mMessage;
- private List<PromptInput> mInputs;
- private CheckBox mCheckBox;
-
- public DefaultDoorHanger(Context context, DoorhangerConfig config, Type type) {
- super(context, config, type);
-
- mMessage = (TextView) findViewById(R.id.doorhanger_message);
-
- if (sSpinnerTextColor == -1) {
- sSpinnerTextColor = ContextCompat.getColor(context, R.color.text_color_primary_disable_only);
- }
-
- switch (mType) {
- case GEOLOCATION:
- mIcon.setImageResource(R.drawable.location);
- mIcon.setVisibility(VISIBLE);
- break;
-
- case DESKTOPNOTIFICATION2:
- mIcon.setImageResource(R.drawable.push_notification);
- mIcon.setVisibility(VISIBLE);
- break;
- }
-
- loadConfig(config);
- }
-
- @Override
- protected void loadConfig(DoorhangerConfig config) {
- final String message = config.getMessage();
- if (message != null) {
- setMessage(message);
- }
-
- final JSONObject options = config.getOptions();
- if (options != null) {
- setOptions(options);
- }
-
- final DoorhangerConfig.Link link = config.getLink();
- if (link != null) {
- addLink(link.label, link.url);
- }
-
- addButtonsToLayout(config);
- }
-
- @Override
- protected int getContentResource() {
- return R.layout.default_doorhanger;
- }
-
- private List<PromptInput> getInputs() {
- return mInputs;
- }
-
- private CheckBox getCheckBox() {
- return mCheckBox;
- }
-
- @Override
- public void setOptions(final JSONObject options) {
- super.setOptions(options);
-
- final JSONArray inputs = options.optJSONArray("inputs");
- if (inputs != null) {
- mInputs = new ArrayList<PromptInput>();
-
- final ViewGroup group = (ViewGroup) findViewById(R.id.doorhanger_inputs);
- group.setVisibility(VISIBLE);
-
- for (int i = 0; i < inputs.length(); i++) {
- try {
- PromptInput input = PromptInput.getInput(inputs.getJSONObject(i));
- mInputs.add(input);
-
- final int padding = mResources.getDimensionPixelSize(R.dimen.doorhanger_section_padding_medium);
- View v = input.getView(getContext());
- styleInput(input, v);
- v.setPadding(0, 0, 0, padding);
- group.addView(v);
- } catch (JSONException ex) { }
- }
- }
-
- final String checkBoxText = options.optString("checkbox");
- if (!TextUtils.isEmpty(checkBoxText)) {
- mCheckBox = (CheckBox) findViewById(R.id.doorhanger_checkbox);
- mCheckBox.setText(checkBoxText);
- if (options.has("checkboxState")) {
- final boolean checkBoxState = options.optBoolean("checkboxState");
- mCheckBox.setChecked(checkBoxState);
- }
- mCheckBox.setVisibility(VISIBLE);
- }
- }
-
- @Override
- protected OnClickListener makeOnButtonClickListener(final int id, final String telemetryExtra) {
- return new Button.OnClickListener() {
- @Override
- public void onClick(View v) {
- final String expandedExtra = mType.toString().toLowerCase(Locale.US) + "-" + telemetryExtra;
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.DOORHANGER, expandedExtra);
-
- final JSONObject response = new JSONObject();
- try {
- response.put("callback", id);
-
- CheckBox checkBox = getCheckBox();
- // If the checkbox is being used, pass its value
- if (checkBox != null) {
- response.put("checked", checkBox.isChecked());
- }
-
- List<PromptInput> doorHangerInputs = getInputs();
- if (doorHangerInputs != null) {
- JSONObject inputs = new JSONObject();
- for (PromptInput input : doorHangerInputs) {
- inputs.put(input.getId(), input.getValue());
- }
- response.put("inputs", inputs);
- }
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error creating onClick response", e);
- }
-
- mOnButtonClickListener.onButtonClick(response, DefaultDoorHanger.this);
- }
- };
- }
-
- private void setMessage(String message) {
- Spanned markupMessage = Html.fromHtml(message);
- mMessage.setText(markupMessage);
- }
-
- private void styleInput(PromptInput input, View view) {
- if (input instanceof PromptInput.MenulistInput) {
- styleDropdownInputs(input, view);
- }
- view.setPadding(0, 0, 0, mResources.getDimensionPixelSize(R.dimen.doorhanger_subsection_padding));
- }
-
- private void styleDropdownInputs(PromptInput input, View view) {
- PromptInput.MenulistInput spinInput = (PromptInput.MenulistInput) input;
-
- if (spinInput.textView != null) {
- spinInput.textView.setTextColor(sSpinnerTextColor);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/DefaultItemAnimatorBase.java b/mobile/android/base/java/org/mozilla/gecko/widget/DefaultItemAnimatorBase.java
deleted file mode 100644
index 5beec3a5c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/DefaultItemAnimatorBase.java
+++ /dev/null
@@ -1,685 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import android.support.annotation.NonNull;
-import android.support.v4.animation.AnimatorCompatHelper;
-import android.support.v4.view.ViewCompat;
-import android.support.v4.view.ViewPropertyAnimatorCompat;
-import android.support.v4.view.ViewPropertyAnimatorListener;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.SimpleItemAnimator;
-import android.view.View;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * This basically follows the approach taken by Wasabeef:
- * <a href="https://github.com/wasabeef/recyclerview-animators">https://github.com/wasabeef/recyclerview-animators</a>
- * based off of Android's DefaultItemAnimator from October 2016:
- * <a href="https://github.com/android/platform_frameworks_support/blob/432f3317f8a9b8cf98277938ea5df4021e983055/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java">
- * https://github.com/android/platform_frameworks_support/blob/432f3317f8a9b8cf98277938ea5df4021e983055/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
- * </a>
- * <p>
- * Usage Notes:
- * </p>
- * <ul>
- * <li>You <strong>must</strong> add a Default*VpaListener to your animate*Impl animation - the
- * listener takes care of animation bookkeeping.</li>
- * <li>You should call {@link #resetAnimation(RecyclerView.ViewHolder)} at some point in
- * preAnimate*Impl if you choose to proceed with the animation. Some animations will want to
- * know some or all of the current animation values for initializing their own animation
- * values before resetting the current animation, so this class does not provide the reset
- * service itself.</li>
- * <li>{@link #resetViewProperties(View)} is used to reset a view any time an animation ends or
- * gets canceled - you should redefine resetViewProperties if the version here doesn't reset
- * all of the properties you're animating.</li>
- * </ul>
- */
-public class DefaultItemAnimatorBase extends SimpleItemAnimator {
- private List<RecyclerView.ViewHolder> pendingRemovals = new ArrayList<>();
- private List<RecyclerView.ViewHolder> pendingAdditions = new ArrayList<>();
- private List<MoveInfo> pendingMoves = new ArrayList<>();
- private List<ChangeInfo> pendingChanges = new ArrayList<>();
-
- private List<List<RecyclerView.ViewHolder>> additionsList = new ArrayList<>();
- private List<List<MoveInfo>> movesList = new ArrayList<>();
- private List<List<ChangeInfo>> changesList = new ArrayList<>();
-
- private List<RecyclerView.ViewHolder> addAnimations = new ArrayList<>();
- private List<RecyclerView.ViewHolder> moveAnimations = new ArrayList<>();
- private List<RecyclerView.ViewHolder> removeAnimations = new ArrayList<>();
- private List<RecyclerView.ViewHolder> changeAnimations = new ArrayList<>();
-
- protected static class MoveInfo {
- public RecyclerView.ViewHolder holder;
- public int fromX, fromY, toX, toY;
-
- public MoveInfo(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
- this.holder = holder;
- this.fromX = fromX;
- this.fromY = fromY;
- this.toX = toX;
- this.toY = toY;
- }
- }
-
- protected static class ChangeInfo {
- public RecyclerView.ViewHolder oldHolder, newHolder;
- public int fromX, fromY, toX, toY;
-
- public ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder) {
- this.oldHolder = oldHolder;
- this.newHolder = newHolder;
- }
-
- public ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
- int fromX, int fromY, int toX, int toY) {
- this(oldHolder, newHolder);
- this.fromX = fromX;
- this.fromY = fromY;
- this.toX = toX;
- this.toY = toY;
- }
-
- @Override
- public String toString() {
- return "ChangeInfo{" +
- "oldHolder=" + oldHolder +
- ", newHolder=" + newHolder +
- ", fromX=" + fromX +
- ", fromY=" + fromY +
- ", toX=" + toX +
- ", toY=" + toY +
- '}';
- }
- }
-
- @Override
- public void runPendingAnimations() {
- final boolean removalsPending = !pendingRemovals.isEmpty();
- final boolean movesPending = !pendingMoves.isEmpty();
- final boolean changesPending = !pendingChanges.isEmpty();
- final boolean additionsPending = !pendingAdditions.isEmpty();
- if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
- return;
- }
- // First, remove stuff.
- for (final RecyclerView.ViewHolder holder : pendingRemovals) {
- animateRemoveImpl(holder);
- }
- pendingRemovals.clear();
- // Next, move stuff.
- if (movesPending) {
- final List<MoveInfo> moves = new ArrayList<>();
- moves.addAll(pendingMoves);
- movesList.add(moves);
- pendingMoves.clear();
- final Runnable mover = new Runnable() {
- @Override
- public void run() {
- for (final MoveInfo moveInfo : moves) {
- animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
- moveInfo.toX, moveInfo.toY);
- }
- moves.clear();
- movesList.remove(moves);
- }
- };
- if (removalsPending) {
- final View view = moves.get(0).holder.itemView;
- ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
- } else {
- mover.run();
- }
- }
- // Next, change stuff, to run in parallel with move animations.
- if (changesPending) {
- final List<ChangeInfo> changes = new ArrayList<>();
- changes.addAll(pendingChanges);
- changesList.add(changes);
- pendingChanges.clear();
- final Runnable changer = new Runnable() {
- @Override
- public void run() {
- for (final ChangeInfo change : changes) {
- animateChangeImpl(change);
- }
- changes.clear();
- changesList.remove(changes);
- }
- };
- if (removalsPending) {
- RecyclerView.ViewHolder holder = changes.get(0).oldHolder;
- ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
- } else {
- changer.run();
- }
- }
- // Next, add stuff.
- if (additionsPending) {
- final List<RecyclerView.ViewHolder> additions = new ArrayList<>();
- additions.addAll(pendingAdditions);
- additionsList.add(additions);
- pendingAdditions.clear();
- final Runnable adder = new Runnable() {
- public void run() {
- for (final RecyclerView.ViewHolder holder : additions) {
- animateAddImpl(holder);
- }
- additions.clear();
- additionsList.remove(additions);
- }
- };
- if (removalsPending || movesPending || changesPending) {
- final long removeDuration = removalsPending ? getRemoveDuration() : 0;
- final long moveDuration = movesPending ? getMoveDuration() : 0;
- final long changeDuration = changesPending ? getChangeDuration() : 0;
- final long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
- final View view = additions.get(0).itemView;
- ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
- } else {
- adder.run();
- }
- }
- }
-
- @Override
- public boolean animateRemove(final RecyclerView.ViewHolder holder) {
- if (!preAnimateRemoveImpl(holder)) {
- dispatchRemoveFinished(holder);
- return false;
- }
- pendingRemovals.add(holder);
- return true;
- }
-
- protected boolean preAnimateRemoveImpl(final RecyclerView.ViewHolder holder) {
- resetAnimation(holder);
- return true;
- }
-
- protected void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
- ViewCompat.animate(holder.itemView)
- .setDuration(getRemoveDuration())
- .alpha(0)
- .setListener(new DefaultRemoveVpaListener(holder))
- .start();
- }
-
- @Override
- public boolean animateAdd(final RecyclerView.ViewHolder holder) {
- if (!preAnimateAddImpl(holder)) {
- dispatchAddFinished(holder);
- return false;
- }
- pendingAdditions.add(holder);
- return true;
- }
-
- protected boolean preAnimateAddImpl(RecyclerView.ViewHolder holder) {
- resetAnimation(holder);
- holder.itemView.setAlpha(0);
- return true;
- }
-
- protected void animateAddImpl(final RecyclerView.ViewHolder holder) {
- ViewCompat.animate(holder.itemView)
- .setDuration(getAddDuration())
- .alpha(1)
- .setListener(new DefaultAddVpaListener(holder))
- .start();
- }
-
- @Override
- public boolean animateMove(final RecyclerView.ViewHolder holder,
- int fromX, int fromY, int toX, int toY) {
- final View view = holder.itemView;
- fromX += ViewCompat.getTranslationX(holder.itemView);
- fromY += ViewCompat.getTranslationY(holder.itemView);
- final int deltaX = toX - fromX;
- final int deltaY = toY - fromY;
- if (deltaX == 0 && deltaY == 0) {
- dispatchMoveFinished(holder);
- return false;
- }
- resetAnimation(holder);
- if (deltaX != 0) {
- ViewCompat.setTranslationX(view, -deltaX);
- }
- if (deltaY != 0) {
- ViewCompat.setTranslationY(view, -deltaY);
- }
- pendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
- return true;
- }
-
- protected void animateMoveImpl(final RecyclerView.ViewHolder holder,
- int fromX, int fromY, int toX, int toY) {
- final View view = holder.itemView;
- final int deltaX = toX - fromX;
- final int deltaY = toY - fromY;
- if (deltaX != 0) {
- ViewCompat.animate(view).translationX(0);
- }
- if (deltaY != 0) {
- ViewCompat.animate(view).translationY(0);
- }
- // TODO: make EndActions end listeners instead, since end actions aren't called when
- // vpas are canceled (and can't end them. why?)
- // need listener functionality in VPACompat for this. Ick.
- final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
- moveAnimations.add(holder);
- animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() {
- @Override
- public void onAnimationStart(View view) {
- dispatchMoveStarting(holder);
- }
- @Override
- public void onAnimationCancel(View view) {
- resetViewProperties(view);
- }
- @Override
- public void onAnimationEnd(View view) {
- animation.setListener(null);
- dispatchMoveFinished(holder);
- moveAnimations.remove(holder);
- dispatchFinishedWhenDone();
- }
- }).start();
- }
-
- @Override
- public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
- int fromX, int fromY, int toX, int toY) {
- if (oldHolder == newHolder) {
- // Don't know how to run change animations when the same view holder is re-used.
- // Run a move animation to handle position changes (if there are any).
- if (fromX != toX || fromY != toY) {
- // *Don't* call dispatchChangeFinished here, it leads to unbalanced isRecyclable calls.
- return animateMove(oldHolder, fromX, fromY, toX, toY);
- }
- dispatchChangeFinished(oldHolder, true);
- return false;
- }
- final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView);
- final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView);
- final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
- resetAnimation(oldHolder);
- final int deltaX = (int) (toX - fromX - prevTranslationX);
- final int deltaY = (int) (toY - fromY - prevTranslationY);
- // Recover previous translation state after ending animation.
- ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX);
- ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY);
- ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
- if (newHolder != null) {
- // Carry over translation values.
- resetAnimation(newHolder);
- ViewCompat.setTranslationX(newHolder.itemView, -deltaX);
- ViewCompat.setTranslationY(newHolder.itemView, -deltaY);
- ViewCompat.setAlpha(newHolder.itemView, 0);
- }
- pendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
- return true;
- }
-
- protected void animateChangeImpl(final ChangeInfo changeInfo) {
- final RecyclerView.ViewHolder holder = changeInfo.oldHolder;
- final View view = holder == null ? null : holder.itemView;
- final RecyclerView.ViewHolder newHolder = changeInfo.newHolder;
- final View newView = newHolder != null ? newHolder.itemView : null;
- if (view != null) {
- final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
- getChangeDuration());
- changeAnimations.add(changeInfo.oldHolder);
- oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
- oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
- oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() {
- @Override
- public void onAnimationStart(View view) {
- dispatchChangeStarting(changeInfo.oldHolder, true);
- }
-
- @Override
- public void onAnimationEnd(View view) {
- oldViewAnim.setListener(null);
- resetViewProperties(view);
- dispatchChangeFinished(changeInfo.oldHolder, true);
- changeAnimations.remove(changeInfo.oldHolder);
- dispatchFinishedWhenDone();
- }
- }).start();
- }
- if (newView != null) {
- final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView);
- changeAnimations.add(changeInfo.newHolder);
- newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).
- alpha(1).setListener(new VpaListenerAdapter() {
- @Override
- public void onAnimationStart(View view) {
- dispatchChangeStarting(changeInfo.newHolder, false);
- }
- @Override
- public void onAnimationEnd(View view) {
- newViewAnimation.setListener(null);
- resetViewProperties(view);
- dispatchChangeFinished(changeInfo.newHolder, false);
- changeAnimations.remove(changeInfo.newHolder);
- dispatchFinishedWhenDone();
- }
- }).start();
-}
- }
-
- private void endChangeAnimation(List<ChangeInfo> infoList, RecyclerView.ViewHolder item) {
- for (int i = infoList.size() - 1; i >= 0; i--) {
- final ChangeInfo changeInfo = infoList.get(i);
- if (endChangeAnimationIfNecessary(changeInfo, item)) {
- if (changeInfo.oldHolder == null && changeInfo.newHolder == null) {
- infoList.remove(changeInfo);
- }
- }
- }
- }
-
- private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) {
- if (changeInfo.oldHolder != null) {
- endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder);
- }
- if (changeInfo.newHolder != null) {
- endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder);
- }
- }
-
- private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, RecyclerView.ViewHolder item) {
- boolean oldItem = false;
- if (changeInfo.newHolder == item) {
- changeInfo.newHolder = null;
- } else if (changeInfo.oldHolder == item) {
- changeInfo.oldHolder = null;
- oldItem = true;
- } else {
- return false;
- }
- resetViewProperties(item.itemView);
- dispatchChangeFinished(item, oldItem);
- return true;
- }
-
- /**
- * Called to reset all properties possibly animated by any and all defined animations.
- */
- protected void resetViewProperties(View view) {
- view.setTranslationX(0);
- view.setTranslationY(0);
- view.setAlpha(1);
- }
-
- @Override
- public void endAnimation(RecyclerView.ViewHolder item) {
-
- final View view = item.itemView;
- // This calls dispatch*Finished, resets view properties, and removes item from current
- // animations list if the view is currently being animated.
- ViewCompat.animate(view).cancel();
- // TODO if some other animations are chained to end, how do we cancel them as well?
- for (int i = pendingMoves.size() - 1; i >= 0; i--) {
- final MoveInfo moveInfo = pendingMoves.get(i);
- if (moveInfo.holder == item) {
- resetViewProperties(view);
- dispatchMoveFinished(item);
- pendingMoves.remove(i);
- }
- }
- endChangeAnimation(pendingChanges, item);
- if (pendingRemovals.remove(item)) {
- resetViewProperties(view);
- dispatchRemoveFinished(item);
- }
- if (pendingAdditions.remove(item)) {
- resetViewProperties(view);
- dispatchAddFinished(item);
- }
-
- for (int i = changesList.size() - 1; i >= 0; i--) {
- final List<ChangeInfo> changes = changesList.get(i);
- endChangeAnimation(changes, item);
- if (changes.isEmpty()) {
- changesList.remove(i);
- }
- }
- for (int i = movesList.size() - 1; i >= 0; i--) {
- final List<MoveInfo> moves = movesList.get(i);
- for (int j = moves.size() - 1; j >= 0; j--) {
- final MoveInfo moveInfo = moves.get(j);
- if (moveInfo.holder == item) {
- resetViewProperties(view);
- dispatchMoveFinished(item);
- moves.remove(j);
- if (moves.isEmpty()) {
- movesList.remove(i);
- }
- break;
- }
- }
- }
- for (int i = additionsList.size() - 1; i >= 0; i--) {
- final List<RecyclerView.ViewHolder> additions = additionsList.get(i);
- if (additions.remove(item)) {
- resetViewProperties(view);
- dispatchAddFinished(item);
- if (additions.isEmpty()) {
- additionsList.remove(i);
- }
- }
- }
- dispatchFinishedWhenDone();
- }
-
- protected void resetAnimation(RecyclerView.ViewHolder holder) {
- AnimatorCompatHelper.clearInterpolator(holder.itemView);
- endAnimation(holder);
- }
-
- @Override
- public boolean isRunning() {
- return (!pendingAdditions.isEmpty() ||
- !pendingChanges.isEmpty() ||
- !pendingMoves.isEmpty() ||
- !pendingRemovals.isEmpty() ||
- !moveAnimations.isEmpty() ||
- !removeAnimations.isEmpty() ||
- !addAnimations.isEmpty() ||
- !changeAnimations.isEmpty() ||
- !movesList.isEmpty() ||
- !additionsList.isEmpty() ||
- !changesList.isEmpty());
- }
-
- /**
- * Check the state of currently pending and running animations. If there are none
- * pending/running, call {@link #dispatchAnimationsFinished()} to notify any
- * listeners.
- */
- protected void dispatchFinishedWhenDone() {
- if (!isRunning()) {
- dispatchAnimationsFinished();
- }
- }
-
- @Override
- public void endAnimations() {
- int count = pendingMoves.size();
- for (int i = count - 1; i >= 0; i--) {
- final MoveInfo item = pendingMoves.get(i);
- resetViewProperties(item.holder.itemView);
- dispatchMoveFinished(item.holder);
- pendingMoves.remove(i);
- }
- count = pendingRemovals.size();
- for (int i = count - 1; i >= 0; i--) {
- final RecyclerView.ViewHolder item = pendingRemovals.get(i);
- resetViewProperties(item.itemView);
- dispatchRemoveFinished(item);
- pendingRemovals.remove(i);
- }
- count = pendingAdditions.size();
- for (int i = count - 1; i >= 0; i--) {
- final RecyclerView.ViewHolder item = pendingAdditions.get(i);
- resetViewProperties(item.itemView);
- dispatchAddFinished(item);
- pendingAdditions.remove(i);
- }
- count = pendingChanges.size();
- for (int i = count - 1; i >= 0; i--) {
- endChangeAnimationIfNecessary(pendingChanges.get(i));
- }
- pendingChanges.clear();
- if (!isRunning()) {
- return;
- }
-
- int listCount = movesList.size();
- for (int i = listCount - 1; i >= 0; i--) {
- final List<MoveInfo> moves = movesList.get(i);
- count = moves.size();
- for (int j = count - 1; j >= 0; j--) {
- final MoveInfo moveInfo = moves.get(j);
- final RecyclerView.ViewHolder item = moveInfo.holder;
- resetViewProperties(item.itemView);
- dispatchMoveFinished(item);
- moves.remove(j);
- if (moves.isEmpty()) {
- movesList.remove(moves);
- }
- }
- }
- listCount = additionsList.size();
- for (int i = listCount - 1; i >= 0; i--) {
- final List<RecyclerView.ViewHolder> additions = additionsList.get(i);
- count = additions.size();
- for (int j = count - 1; j >= 0; j--) {
- final RecyclerView.ViewHolder item = additions.get(j);
- resetViewProperties(item.itemView);
- dispatchAddFinished(item);
- additions.remove(j);
- if (additions.isEmpty()) {
- additionsList.remove(additions);
- }
- }
- }
- listCount = changesList.size();
- for (int i = listCount - 1; i >= 0; i--) {
- final List<ChangeInfo> changes = changesList.get(i);
- count = changes.size();
- for (int j = count - 1; j >= 0; j--) {
- endChangeAnimationIfNecessary(changes.get(j));
- if (changes.isEmpty()) {
- changesList.remove(changes);
- }
- }
- }
-
- cancelAll(removeAnimations);
- cancelAll(moveAnimations);
- cancelAll(addAnimations);
- cancelAll(changeAnimations);
-
- dispatchAnimationsFinished();
- }
-
- public void cancelAll(List<RecyclerView.ViewHolder> viewHolders) {
- for (int i = viewHolders.size() - 1; i >= 0; i--) {
- ViewCompat.animate(viewHolders.get(i).itemView).cancel();
- }
- }
-
- /**
- * {@inheritDoc}
- * <p>
- * If the payload list is not empty, DefaultItemAnimator returns <code>true</code>.
- * When this is the case:
- * <ul>
- * <li>If you override
- * {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)},
- * both ViewHolder arguments will be the same instance.
- * </li>
- * <li>
- * If you are not overriding
- * {@link #animateChange(RecyclerView.ViewHolder, RecyclerView.ViewHolder, int, int, int, int)},
- * then DefaultItemAnimator will call
- * {@link #animateMove(RecyclerView.ViewHolder, int, int, int, int)} and run a move animation
- * instead.
- * </li>
- * </ul>
- */
- @Override
- public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder,
- @NonNull List<Object> payloads) {
- return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
- }
-
- private class VpaListenerAdapter implements ViewPropertyAnimatorListener {
- @Override
- public void onAnimationStart(View view) {}
-
- // Note that onAnimationEnd is called (in addition to OnAnimationCancel) whenever an
- // animation is canceled.
- @Override
- public void onAnimationEnd(View view) {
- resetViewProperties(view);
- view.animate().setListener(null);
- }
-
- @Override
- public void onAnimationCancel(View view) {}
- }
-
- protected class DefaultRemoveVpaListener extends VpaListenerAdapter {
- private final RecyclerView.ViewHolder viewHolder;
-
- public DefaultRemoveVpaListener(final RecyclerView.ViewHolder holder) {
- viewHolder = holder;
- }
-
- @Override
- public void onAnimationStart(View view) {
- removeAnimations.add(viewHolder);
- dispatchRemoveStarting(viewHolder);
- }
-
- @Override
- public void onAnimationEnd(View view) {
- removeAnimations.remove(viewHolder);
- dispatchRemoveFinished(viewHolder);
- dispatchFinishedWhenDone();
- super.onAnimationEnd(view);
- }
- }
-
- protected class DefaultAddVpaListener extends VpaListenerAdapter {
- private final RecyclerView.ViewHolder viewHolder;
-
- public DefaultAddVpaListener(final RecyclerView.ViewHolder holder) {
- viewHolder = holder;
- }
-
- @Override
- public void onAnimationStart(View view) {
- addAnimations.add(viewHolder);
- dispatchAddStarting(viewHolder);
- }
-
- @Override
- public void onAnimationEnd(View view) {
- addAnimations.remove(viewHolder);
- dispatchAddFinished(viewHolder);
- dispatchFinishedWhenDone();
- super.onAnimationEnd(view);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/DoorHanger.java b/mobile/android/base/java/org/mozilla/gecko/widget/DoorHanger.java
deleted file mode 100644
index 7d32278e4..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/DoorHanger.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.support.v4.content.ContextCompat;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewStub;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import org.json.JSONObject;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tabs;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-
-import java.util.Locale;
-
-public abstract class DoorHanger extends LinearLayout {
-
- public static DoorHanger Get(Context context, DoorhangerConfig config) {
- final Type type = config.getType();
- switch (type) {
- case LOGIN:
- return new LoginDoorHanger(context, config);
- case TRACKING:
- return new ContentSecurityDoorHanger(context, config, type);
- }
- return new DefaultDoorHanger(context, config, type);
- }
-
- // Doorhanger types created from Gecko are checked against enum strings to determine type.
- public static enum Type { DEFAULT, LOGIN, TRACKING, GEOLOCATION, DESKTOPNOTIFICATION2, WEBRTC, VIBRATION }
-
- public interface OnButtonClickListener {
- public void onButtonClick(JSONObject response, DoorHanger doorhanger);
- }
-
- private static final String LOGTAG = "GeckoDoorHanger";
-
- // Divider between doorhangers.
- private final View mDivider;
-
- private final Button mNegativeButton;
- private final Button mPositiveButton;
- protected final OnButtonClickListener mOnButtonClickListener;
-
- // The tab this doorhanger is associated with.
- private final int mTabId;
-
- // DoorHanger identifier.
- private final String mIdentifier;
-
- protected final Type mType;
-
- protected final ImageView mIcon;
- protected final TextView mLink;
- protected final TextView mDoorhangerTitle;
-
- protected final Context mContext;
- protected final Resources mResources;
-
- protected int mDividerColor;
-
- protected boolean mPersistWhileVisible;
- protected int mPersistenceCount;
- protected long mTimeout;
-
- protected DoorHanger(Context context, DoorhangerConfig config, Type type) {
- super(context);
-
- mContext = context;
- mResources = context.getResources();
- mTabId = config.getTabId();
- mIdentifier = config.getId();
- mType = type;
-
- LayoutInflater.from(context).inflate(R.layout.doorhanger, this);
- setOrientation(VERTICAL);
-
- mDivider = findViewById(R.id.divider_doorhanger);
- mIcon = (ImageView) findViewById(R.id.doorhanger_icon);
- mLink = (TextView) findViewById(R.id.doorhanger_link);
- mDoorhangerTitle = (TextView) findViewById(R.id.doorhanger_title);
-
- mNegativeButton = (Button) findViewById(R.id.doorhanger_button_negative);
- mPositiveButton = (Button) findViewById(R.id.doorhanger_button_positive);
- mOnButtonClickListener = config.getButtonClickListener();
-
- mDividerColor = ContextCompat.getColor(context, R.color.toolbar_divider_grey);
-
- final ViewStub contentStub = (ViewStub) findViewById(R.id.content);
- contentStub.setLayoutResource(getContentResource());
- contentStub.inflate();
-
- final String typeExtra = mType.toString().toLowerCase(Locale.US);
- Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.DOORHANGER, typeExtra);
- }
-
- protected abstract int getContentResource();
-
- protected abstract void loadConfig(DoorhangerConfig config);
-
- protected void setOptions(final JSONObject options) {
- final int persistence = options.optInt("persistence");
- if (persistence > 0) {
- mPersistenceCount = persistence;
- }
-
- mPersistWhileVisible = options.optBoolean("persistWhileVisible");
-
- final long timeout = options.optLong("timeout");
- if (timeout > 0) {
- mTimeout = timeout;
- }
- }
-
- protected void addButtonsToLayout(DoorhangerConfig config) {
- final DoorhangerConfig.ButtonConfig negativeButtonConfig = config.getNegativeButtonConfig();
- final DoorhangerConfig.ButtonConfig positiveButtonConfig = config.getPositiveButtonConfig();
-
- if (negativeButtonConfig != null) {
- mNegativeButton.setText(negativeButtonConfig.label);
- mNegativeButton.setOnClickListener(makeOnButtonClickListener(negativeButtonConfig.callback, "negative"));
- mNegativeButton.setVisibility(VISIBLE);
- }
-
- if (positiveButtonConfig != null) {
- mPositiveButton.setText(positiveButtonConfig.label);
- mPositiveButton.setOnClickListener(makeOnButtonClickListener(positiveButtonConfig.callback, "positive"));
- mPositiveButton.setVisibility(VISIBLE);
- }
- }
-
- public int getTabId() {
- return mTabId;
- }
-
- public String getIdentifier() {
- return mIdentifier;
- }
-
- public void showDivider() {
- mDivider.setVisibility(View.VISIBLE);
- }
-
- public void hideDivider() {
- mDivider.setVisibility(View.GONE);
- }
-
- public void setIcon(int resId) {
- mIcon.setImageResource(resId);
- mIcon.setVisibility(View.VISIBLE);
- }
-
- protected void addLink(String label, final String url) {
- mLink.setText(label);
- mLink.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- final String typeExtra = mType.toString().toLowerCase(Locale.US);
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.DOORHANGER, typeExtra);
- Tabs.getInstance().loadUrlInTab(url);
- }
- });
- mLink.setVisibility(VISIBLE);
- }
-
- protected abstract OnClickListener makeOnButtonClickListener(final int id, final String telemetryExtra);
-
- /*
- * Checks with persistence and timeout options to see if it's okay to remove a doorhanger.
- *
- * @param isShowing Whether or not this doorhanger is currently visible to the user.
- * (e.g. the DoorHanger view might be VISIBLE, but its parent could be hidden)
- */
- public boolean shouldRemove(boolean isShowing) {
- if (mPersistWhileVisible && isShowing) {
- // We still want to decrement mPersistence, even if the popup is showing
- if (mPersistenceCount != 0)
- mPersistenceCount--;
- return false;
- }
-
- // If persistence is set to -1, the doorhanger will never be
- // automatically removed.
- if (mPersistenceCount != 0) {
- mPersistenceCount--;
- return false;
- }
-
- if (System.currentTimeMillis() <= mTimeout) {
- return false;
- }
-
- return true;
- }
-
- public void showTitle(Bitmap favicon, String title) {
- mDoorhangerTitle.setText(title);
- mDoorhangerTitle.setCompoundDrawablesWithIntrinsicBounds(new BitmapDrawable(getResources(), favicon), null, null, null);
- if (favicon != null) {
- mDoorhangerTitle.setCompoundDrawablePadding((int) mContext.getResources().getDimension(R.dimen.doorhanger_drawable_padding));
- }
- mDoorhangerTitle.setVisibility(VISIBLE);
- }
-
- public void hideTitle() {
- mDoorhangerTitle.setVisibility(GONE);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/DoorhangerConfig.java b/mobile/android/base/java/org/mozilla/gecko/widget/DoorhangerConfig.java
deleted file mode 100644
index 98f1e57e1..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/DoorhangerConfig.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import android.util.Log;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import org.mozilla.gecko.widget.DoorHanger.Type;
-
-public class DoorhangerConfig {
-
- public static class Link {
- public final String label;
- public final String url;
-
- private Link(String label, String url) {
- this.label = label;
- this.url = url;
- }
- }
-
- public static class ButtonConfig {
- public final String label;
- public final int callback;
-
- public ButtonConfig(String label, int callback) {
- this.label = label;
- this.callback = callback;
- }
- }
- private static final String LOGTAG = "DoorhangerConfig";
-
- private final int tabId;
- private final String id;
- private final DoorHanger.OnButtonClickListener buttonClickListener;
- private final DoorHanger.Type type;
- private String message;
- private JSONObject options;
- private Link link;
- private ButtonConfig positiveButtonConfig;
- private ButtonConfig negativeButtonConfig;
-
- public DoorhangerConfig(Type type, DoorHanger.OnButtonClickListener listener) {
- // XXX: This should only be used by SiteIdentityPopup doorhangers which
- // don't need tab or id references, until bug 1141904 unifies doorhangers.
-
- this(-1, null, type, listener);
- }
-
- public DoorhangerConfig(int tabId, String id, DoorHanger.Type type, DoorHanger.OnButtonClickListener buttonClickListener) {
- this.tabId = tabId;
- this.id = id;
- this.type = type;
- this.buttonClickListener = buttonClickListener;
- }
-
- public int getTabId() {
- return tabId;
- }
-
- public String getId() {
- return id;
- }
-
- public Type getType() {
- return type;
- }
-
- public void setMessage(String message) {
- this.message = message;
- }
-
- public String getMessage() {
- return message;
- }
-
- public void setOptions(JSONObject options) {
- this.options = options;
-
- // Set link if there is a link provided in options.
- final JSONObject linkObj = options.optJSONObject("link");
- if (linkObj != null) {
- try {
- setLink(linkObj.getString("label"), linkObj.getString("url"));
- } catch (JSONException e) {
- Log.e(LOGTAG, "Malformed link object in options");
- }
- }
- }
-
- public JSONObject getOptions() {
- return options;
- }
-
- public void setButton(String label, int callbackId, boolean isPositive) {
- final ButtonConfig buttonConfig = new ButtonConfig(label, callbackId);
- if (isPositive) {
- positiveButtonConfig = buttonConfig;
- } else {
- negativeButtonConfig = buttonConfig;
- }
- }
-
- public ButtonConfig getPositiveButtonConfig() {
- return positiveButtonConfig;
- }
-
- public ButtonConfig getNegativeButtonConfig() {
- return negativeButtonConfig;
- }
-
- public DoorHanger.OnButtonClickListener getButtonClickListener() {
- return this.buttonClickListener;
- }
-
- public void setLink(String label, String url) {
- this.link = new Link(label, url);
- }
-
- public Link getLink() {
- return link;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/EllipsisTextView.java b/mobile/android/base/java/org/mozilla/gecko/widget/EllipsisTextView.java
deleted file mode 100644
index 44f88e668..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/EllipsisTextView.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.util.AttributeSet;
-import android.widget.TextView;
-
-/**
- * Text view that correctly handles maxLines and ellipsizing for Android < 2.3.
- */
-public class EllipsisTextView extends TextView {
- private final String ellipsis;
-
- private final int maxLines;
- private CharSequence originalText;
-
- public EllipsisTextView(Context context) {
- this(context, null);
- }
-
- public EllipsisTextView(Context context, AttributeSet attrs) {
- this(context, attrs, android.R.attr.textViewStyle);
- }
-
- public EllipsisTextView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- ellipsis = getResources().getString(R.string.ellipsis);
-
- TypedArray a = context.getTheme()
- .obtainStyledAttributes(attrs, R.styleable.EllipsisTextView, 0, 0);
- maxLines = a.getInteger(R.styleable.EllipsisTextView_ellipsizeAtLine, 1);
- a.recycle();
- }
-
- public void setOriginalText(CharSequence text) {
- originalText = text;
- setText(text);
- }
-
- @Override
- public void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
-
- // There is extra space, start over with the original text
- if (getLineCount() < maxLines) {
- setText(originalText);
- }
-
- // If we are over the max line attribute, ellipsize
- if (getLineCount() > maxLines) {
- final int endIndex = getLayout().getLineEnd(maxLines - 1) - 1 - ellipsis.length();
- final String text = getText().subSequence(0, endIndex) + ellipsis;
- // Make sure that we don't change originalText
- setText(text);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/ExternalIntentDuringPrivateBrowsingPromptFragment.java b/mobile/android/base/java/org/mozilla/gecko/widget/ExternalIntentDuringPrivateBrowsingPromptFragment.java
deleted file mode 100644
index b4d1e13d9..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/ExternalIntentDuringPrivateBrowsingPromptFragment.java
+++ /dev/null
@@ -1,106 +0,0 @@
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-package org.mozilla.gecko.widget;
-
-import org.mozilla.gecko.ActivityHandlerHelper;
-import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Tab;
-import org.mozilla.gecko.Tabs;
-
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Bundle;
-import android.support.v4.app.DialogFragment;
-import android.support.v4.app.FragmentManager;
-import android.support.v7.app.AlertDialog;
-import android.util.Log;
-
-import java.util.List;
-
-/**
- * A DialogFragment to contain a dialog that appears when the user clicks an Intent:// URI during private browsing. The
- * dialog appears to notify the user that a clicked link will open in an external application, potentially leaking their
- * browsing history.
- */
-public class ExternalIntentDuringPrivateBrowsingPromptFragment extends DialogFragment {
- private static final String LOGTAG = ExternalIntentDuringPrivateBrowsingPromptFragment.class.getSimpleName();
- private static final String FRAGMENT_TAG = "ExternalIntentPB";
-
- private static final String KEY_APPLICATION_NAME = "matchingApplicationName";
- private static final String KEY_INTENT = "intent";
-
- @Override
- public Dialog onCreateDialog(final Bundle savedInstanceState) {
- final Bundle args = getArguments();
- final CharSequence matchingApplicationName = args.getCharSequence(KEY_APPLICATION_NAME);
- final Intent intent = args.getParcelable(KEY_INTENT);
-
- final Context context = getActivity();
- final String promptMessage = context.getString(R.string.intent_uri_private_browsing_prompt, matchingApplicationName);
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setMessage(promptMessage)
- .setTitle(intent.getDataString())
- .setPositiveButton(R.string.button_yes, new DialogInterface.OnClickListener() {
- public void onClick(final DialogInterface dialog, final int id) {
- context.startActivity(intent);
- }
- })
- .setNegativeButton(R.string.button_no, null /* we do nothing if the user rejects */ );
- return builder.create();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
-
- GeckoApplication.watchReference(getActivity(), this);
- }
-
- /**
- * @return true if the Activity is started or a dialog is shown. false if the Activity fails to start.
- */
- public static boolean showDialogOrAndroidChooser(final Context context, final FragmentManager fragmentManager,
- final Intent intent) {
- final Tab selectedTab = Tabs.getInstance().getSelectedTab();
- if (selectedTab == null || !selectedTab.isPrivate()) {
- return ActivityHandlerHelper.startIntentAndCatch(LOGTAG, context, intent);
- }
-
- final PackageManager pm = context.getPackageManager();
- final List<ResolveInfo> matchingActivities = pm.queryIntentActivities(intent, 0);
- if (matchingActivities.size() == 1) {
- final ExternalIntentDuringPrivateBrowsingPromptFragment fragment = new ExternalIntentDuringPrivateBrowsingPromptFragment();
-
- final Bundle args = new Bundle(2);
- args.putCharSequence(KEY_APPLICATION_NAME, matchingActivities.get(0).loadLabel(pm));
- args.putParcelable(KEY_INTENT, intent);
- fragment.setArguments(args);
-
- fragment.show(fragmentManager, FRAGMENT_TAG);
- // We don't know the results of the user interaction with the fragment so just return true.
- return true;
- } else if (matchingActivities.size() > 1) {
- // We want to show the Android Intent Chooser. However, we have no way of distinguishing regular tabs from
- // private tabs to the chooser. Thus, if a user chooses "Always" in regular browsing mode, the chooser will
- // not be shown and the URL will be opened. Therefore we explicitly show the chooser (which notably does not
- // have an "Always" option).
- final String androidChooserTitle =
- context.getResources().getString(R.string.intent_uri_private_browsing_multiple_match_title);
- final Intent chooserIntent = Intent.createChooser(intent, androidChooserTitle);
- return ActivityHandlerHelper.startIntentAndCatch(LOGTAG, context, chooserIntent);
- } else {
- // Normally, we show about:neterror when an Intent does not resolve
- // but we don't have the references here to do that so log instead.
- Log.w(LOGTAG, "showDialogOrAndroidChooser unexpectedly called with Intent that does not resolve");
- return false;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/FadedMultiColorTextView.java b/mobile/android/base/java/org/mozilla/gecko/widget/FadedMultiColorTextView.java
deleted file mode 100644
index 08bb55ef6..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/FadedMultiColorTextView.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.LinearGradient;
-import android.graphics.Paint;
-import android.graphics.Shader;
-import android.util.AttributeSet;
-
-/**
- * Fades the end of the text by gecko:fadeWidth amount,
- * if the text is too long and requires an ellipsis.
- *
- * This implementation is an improvement over Android's built-in fadingEdge
- * but potentially slower than the {@link org.mozilla.gecko.widget.FadedSingleColorTextView}.
- * It works for text of multiple colors but only one background color. It works by
- * drawing a gradient rectangle with the background color over the text, fading it out.
- */
-public class FadedMultiColorTextView extends FadedTextView {
- private final ColorStateList fadeBackgroundColorList;
-
- private final Paint fadePaint;
- private FadedTextGradient backgroundGradient;
-
- public FadedMultiColorTextView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- fadePaint = new Paint();
-
- final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FadedMultiColorTextView);
- fadeBackgroundColorList =
- a.getColorStateList(R.styleable.FadedMultiColorTextView_fadeBackgroundColor);
- a.recycle();
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- final boolean needsEllipsis = needsEllipsis();
- if (needsEllipsis) {
- final int right = getWidth() - getCompoundPaddingRight();
- final float left = right - fadeWidth;
-
- updateGradientShader(needsEllipsis, right);
-
- // Shrink height of gradient to prevent it overlaying parent view border.
- // The shrunk size just nee to cover the text itself.
- final float density = getResources().getDisplayMetrics().density;
- final float h = Math.abs(fadePaint.getFontMetrics().top) + 1;
- final float l = fadePaint.getFontMetrics().bottom + 1;
- final float top = getBaseline() - h * density;
- final float bottom = getBaseline() + l * density;
-
- canvas.drawRect(left, top, right, bottom, fadePaint);
- }
- }
-
- private void updateGradientShader(final boolean needsEllipsis, final int gradientEndRight) {
- final int backgroundColor =
- fadeBackgroundColorList.getColorForState(getDrawableState(), Color.RED);
-
- final boolean needsNewGradient = (backgroundGradient == null ||
- backgroundGradient.getBackgroundColor() != backgroundColor ||
- backgroundGradient.getEndRight() != gradientEndRight);
-
- if (needsEllipsis && needsNewGradient) {
- backgroundGradient = new FadedTextGradient(gradientEndRight, fadeWidth, backgroundColor);
- fadePaint.setShader(backgroundGradient);
- }
- }
-
- private static class FadedTextGradient extends LinearGradient {
- private final int endRight;
- private final int backgroundColor;
-
- public FadedTextGradient(final int gradientEndRight, final int fadeWidth,
- final int backgroundColor) {
- super(gradientEndRight - fadeWidth, 0, gradientEndRight, 0,
- getColorWithZeroedAlpha(backgroundColor), backgroundColor, Shader.TileMode.CLAMP);
-
- this.endRight = gradientEndRight;
- this.backgroundColor = backgroundColor;
- }
-
- private static int getColorWithZeroedAlpha(final int color) {
- return Color.argb(0, Color.red(color), Color.green(color), Color.blue(color));
- }
-
- public int getEndRight() {
- return endRight;
- }
-
- public int getBackgroundColor() {
- return backgroundColor;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/FadedSingleColorTextView.java b/mobile/android/base/java/org/mozilla/gecko/widget/FadedSingleColorTextView.java
deleted file mode 100644
index 866b7ecbd..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/FadedSingleColorTextView.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.LinearGradient;
-import android.graphics.Shader;
-import android.util.AttributeSet;
-
-/**
- * Fades the end of the text by gecko:fadeWidth amount,
- * if the text is too long and requires an ellipsis.
- *
- * This implementation is an improvement over Android's built-in fadingEdge
- * and the fastest of Fennec's implementations. However, it only works for
- * text of one color. It works by applying a linear gradient directly to the text.
- */
-public class FadedSingleColorTextView extends FadedTextView {
- // Shader for the fading edge.
- private FadedTextGradient mTextGradient;
-
- public FadedSingleColorTextView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- private void updateGradientShader() {
- final int color = getCurrentTextColor();
- final int width = getAvailableWidth();
-
- final boolean needsNewGradient = (mTextGradient == null ||
- mTextGradient.getColor() != color ||
- mTextGradient.getWidth() != width);
-
- final boolean needsEllipsis = needsEllipsis();
- if (needsEllipsis && needsNewGradient) {
- mTextGradient = new FadedTextGradient(width, fadeWidth, color);
- }
-
- getPaint().setShader(needsEllipsis ? mTextGradient : null);
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- updateGradientShader();
- super.onDraw(canvas);
- }
-
- private static class FadedTextGradient extends LinearGradient {
- private final int mWidth;
- private final int mColor;
-
- public FadedTextGradient(int width, int fadeWidth, int color) {
- super(0, 0, width, 0,
- new int[] { color, color, 0x0 },
- new float[] { 0, ((float) (width - fadeWidth) / width), 1.0f },
- Shader.TileMode.CLAMP);
-
- mWidth = width;
- mColor = color;
- }
-
- public int getWidth() {
- return mWidth;
- }
-
- public int getColor() {
- return mColor;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/FadedTextView.java b/mobile/android/base/java/org/mozilla/gecko/widget/FadedTextView.java
deleted file mode 100644
index e10433083..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/FadedTextView.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.text.Layout;
-import android.util.AttributeSet;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.widget.themed.ThemedTextView;
-
-/**
- * An implementation of FadedTextView should fade the end of the text
- * by gecko:fadeWidth amount, if the text is too long and requires an ellipsis.
- */
-public abstract class FadedTextView extends ThemedTextView {
- // Width of the fade effect from end of the view.
- protected final int fadeWidth;
-
- public FadedTextView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
-
- setSingleLine(true);
- setEllipsize(null);
-
- final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FadedTextView);
- fadeWidth = a.getDimensionPixelSize(R.styleable.FadedTextView_fadeWidth, 0);
- a.recycle();
- }
-
- protected int getAvailableWidth() {
- return getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight();
- }
-
- protected boolean needsEllipsis() {
- final int width = getAvailableWidth();
- if (width <= 0) {
- return false;
- }
-
- final Layout layout = getLayout();
- return (layout != null && layout.getLineWidth(0) > width);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/FaviconView.java b/mobile/android/base/java/org/mozilla/gecko/widget/FaviconView.java
deleted file mode 100644
index 4652345b4..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/FaviconView.java
+++ /dev/null
@@ -1,268 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.icons.IconCallback;
-import org.mozilla.gecko.icons.IconResponse;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
-import android.widget.ImageView;
-
-import java.lang.ref.WeakReference;
-
-/**
- * Special version of ImageView for favicons.
- * Displays solid colour background around Favicon to fill space not occupied by the icon. Colour
- * selected is the dominant colour of the provided Favicon.
- */
-public class FaviconView extends ImageView {
- private static final String LOGTAG = "GeckoFaviconView";
-
- private static String DEFAULT_FAVICON_KEY = FaviconView.class.getSimpleName() + "DefaultFavicon";
-
- // Default x/y-radius of the oval used to round the corners of the background (dp)
- private static final int DEFAULT_CORNER_RADIUS_DP = 4;
-
- private Bitmap mIconBitmap;
-
- // Reference to the unscaled bitmap, if any, to prevent repeated assignments of the same bitmap
- // to the view from causing repeated rescalings (Some of the callers do this)
- private Bitmap mUnscaledBitmap;
-
- private int mActualWidth;
- private int mActualHeight;
-
- // Flag indicating if the most recently assigned image is considered likely to need scaling.
- private boolean mScalingExpected;
-
- // Dominant color of the favicon.
- private int mDominantColor;
-
- // Paint for drawing the background.
- private static final Paint sBackgroundPaint;
-
- // Size of the background rectangle.
- private final RectF mBackgroundRect;
-
- // The x/y-radius of the oval used to round the corners of the background (pixels)
- private final float mBackgroundCornerRadius;
-
- // Type of the border whose value is defined in attrs.xml .
- private final boolean isDominantBorderEnabled;
-
- // boolean switch for overriding scaletype, whose value is defined in attrs.xml .
- private final boolean isOverrideScaleTypeEnabled;
-
- // boolean switch for disabling rounded corners, value defined in attrs.xml .
- private final boolean areRoundCornersEnabled;
-
- // Initializing the static paints.
- static {
- sBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- sBackgroundPaint.setStyle(Paint.Style.FILL);
- }
-
- public FaviconView(Context context, AttributeSet attrs) {
- super(context, attrs);
- TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.FaviconView, 0, 0);
-
- try {
- isDominantBorderEnabled = a.getBoolean(R.styleable.FaviconView_dominantBorderEnabled, true);
- isOverrideScaleTypeEnabled = a.getBoolean(R.styleable.FaviconView_overrideScaleType, true);
- areRoundCornersEnabled = a.getBoolean(R.styleable.FaviconView_enableRoundCorners, true);
- } finally {
- a.recycle();
- }
-
- if (isOverrideScaleTypeEnabled) {
- setScaleType(ImageView.ScaleType.CENTER);
- }
-
- final DisplayMetrics metrics = getResources().getDisplayMetrics();
-
- mBackgroundRect = new RectF(0, 0, 0, 0);
- mBackgroundCornerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_CORNER_RADIUS_DP, metrics);
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
-
- // No point rechecking the image if there hasn't really been any change.
- if (w == mActualWidth && h == mActualHeight) {
- return;
- }
-
- mActualWidth = w;
- mActualHeight = h;
-
- mBackgroundRect.right = w;
- mBackgroundRect.bottom = h;
-
- formatImage();
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- if (isDominantBorderEnabled) {
- sBackgroundPaint.setColor(mDominantColor & 0x7FFFFFFF);
-
- if (areRoundCornersEnabled) {
- canvas.drawRoundRect(mBackgroundRect, mBackgroundCornerRadius, mBackgroundCornerRadius, sBackgroundPaint);
- } else {
- canvas.drawRect(mBackgroundRect, sBackgroundPaint);
- }
- }
-
- super.onDraw(canvas);
- }
-
- /**
- * Formats the image for display, if the prerequisite data are available. Upscales tiny Favicons to
- * normal sized ones, replaces null bitmaps with the default Favicon, and fills all remaining space
- * in this view with the coloured background.
- */
- private void formatImage() {
- // We're waiting for both onSizeChanged and updateImage to be called before scaling.
- if (mIconBitmap == null || mActualWidth == 0 || mActualHeight == 0) {
- showNoImage();
- return;
- }
-
- if (mScalingExpected && mActualWidth != mIconBitmap.getWidth()) {
- scaleBitmap();
- // Don't scale the image every time something changes.
- mScalingExpected = false;
- }
-
- setImageBitmap(mIconBitmap);
-
- // After scaling, determine if we have empty space around the scaled image which we need to
- // fill with the coloured background. If applicable, show it.
- // We assume Favicons are still squares and only bother with the background if more than 3px
- // of it would be displayed.
- if (Math.abs(mIconBitmap.getWidth() - mActualWidth) < 3) {
- mDominantColor = 0;
- }
- }
-
- private void scaleBitmap() {
- // If the Favicon can be resized to fill the view exactly without an enlargment of more than
- // a factor of two, do so.
- int doubledSize = mIconBitmap.getWidth() * 2;
- if (mActualWidth > doubledSize) {
- // If the view is more than twice the size of the image, just double the image size
- // and do the rest with padding.
- mIconBitmap = Bitmap.createScaledBitmap(mIconBitmap, doubledSize, doubledSize, true);
- } else {
- // Otherwise, scale the image to fill the view.
- mIconBitmap = Bitmap.createScaledBitmap(mIconBitmap, mActualWidth, mActualWidth, true);
- }
- }
-
- /**
- * Sets the icon displayed in this Favicon view to the bitmap provided. If the size of the view
- * has been set, the display will be updated right away, otherwise the update will be deferred
- * until then. The key provided is used to cache the result of the calculation of the dominant
- * colour of the provided image - this value is used to draw the coloured background in this view
- * if the icon is not large enough to fill it.
- *
- * @param allowScaling If true, allows the provided bitmap to be scaled by this FaviconView.
- * Typically, you should prefer using Favicons obtained via the caching system
- * (Favicons class), so as to exploit caching.
- */
- private void updateImageInternal(IconResponse response, boolean allowScaling) {
- // Reassigning the same bitmap? Don't bother.
- if (mUnscaledBitmap == response.getBitmap()) {
- return;
- }
- mUnscaledBitmap = response.getBitmap();
- mIconBitmap = response.getBitmap();
- mDominantColor = response.getColor();
- mScalingExpected = allowScaling;
-
- // Possibly update the display.
- formatImage();
- }
-
- private void showNoImage() {
- setImageDrawable(null);
- mDominantColor = 0;
- }
-
- /**
- * Clear image and background shown by this view.
- */
- public void clearImage() {
- showNoImage();
- mUnscaledBitmap = null;
- mIconBitmap = null;
- mDominantColor = 0;
- mScalingExpected = false;
- }
-
- /**
- * Update the displayed image and apply the scaling logic.
- * The scaling logic will attempt to resize the image to fit correctly inside the view in a way
- * that avoids unreasonable levels of loss of quality.
- * Scaling is necessary only when the icon being provided is not drawn from the Favicon cache
- * introduced in Bug 914296.
- *
- * Due to Bug 913746, icons bundled for search engines are not available to the cache, so must
- * always have the scaling logic applied here. At the time of writing, this is the only case in
- * which the scaling logic here is applied.
- */
- public void updateAndScaleImage(IconResponse response) {
- updateImageInternal(response, true);
- }
-
- /**
- * Update the image displayed in the Favicon view without scaling. Images larger than the view
- * will be centrally cropped. Images smaller than the view will be placed centrally and the
- * extra space filled with the dominant colour of the provided image.
- */
- public void updateImage(IconResponse response) {
- updateImageInternal(response, false);
- }
-
- public Bitmap getBitmap() {
- return mIconBitmap;
- }
-
- /**
- * Create an IconCallback implementation that will update this view after an icon has been loaded.
- */
- public IconCallback createIconCallback() {
- return new Callback(this);
- }
-
- private static class Callback implements IconCallback {
- private final WeakReference<FaviconView> viewReference;
-
- private Callback(FaviconView view) {
- this.viewReference = new WeakReference<FaviconView>(view);
- }
-
- @Override
- public void onIconResponse(IconResponse response) {
- final FaviconView view = viewReference.get();
- if (view == null) {
- return;
- }
-
- view.updateImage(response);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/FilledCardView.java b/mobile/android/base/java/org/mozilla/gecko/widget/FilledCardView.java
deleted file mode 100644
index f1662896e..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/FilledCardView.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-package org.mozilla.gecko.widget;
-
-import android.content.Context;
-import android.support.v7.widget.CardView;
-import android.util.AttributeSet;
-
-import org.mozilla.gecko.AppConstants;
-
-/**
- * CardView that ensures its content can fill the entire card. Use this instead of CardView
- * if you want to fill the card with e.g. images, backgrounds, etc.
- *
- * On API < 21, CardView content isn't clipped for performance reasons. We work around this by disabling
- * rounded corners on those devices.
- */
-public class FilledCardView extends CardView {
-
- public FilledCardView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- // Disable corners on < lollipop:
- // CardView only supports clipping content on API >= 21 (for performance reasons). Without
- // content clipping, any cards that provide their own content that fills the card will look
- // ugly: by default there is a 2px white edge along the top and sides (i.e. an inset corresponding
- // to the corner radius), if we disable the inset then the corners overlap.
- // It's possible to implement custom clipping, however given that the support library
- // chose not to support this for performance reasons, we too have chosen to just disable
- // corners on < 21, see Bug 1271428.
- if (AppConstants.Versions.preLollipop) {
- setRadius(0);
- }
-
- setUseCompatPadding(true);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/FlowLayout.java b/mobile/android/base/java/org/mozilla/gecko/widget/FlowLayout.java
deleted file mode 100644
index 042e74851..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/FlowLayout.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-
-public class FlowLayout extends ViewGroup {
- private int mSpacing;
-
- public FlowLayout(Context context) {
- super(context);
- }
-
- public FlowLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- TypedArray a = context.obtainStyledAttributes(attrs, org.mozilla.gecko.R.styleable.FlowLayout);
- mSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_spacing, (int) context.getResources().getDimension(R.dimen.flow_layout_spacing));
- a.recycle();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
- final int childCount = getChildCount();
- int rowWidth = 0;
- int totalWidth = 0;
- int totalHeight = 0;
- boolean firstChild = true;
-
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() == GONE)
- continue;
-
- measureChild(child, widthMeasureSpec, heightMeasureSpec);
-
- final int childWidth = child.getMeasuredWidth();
- final int childHeight = child.getMeasuredHeight();
-
- if (firstChild || (rowWidth + childWidth > parentWidth)) {
- rowWidth = 0;
- totalHeight += childHeight;
- if (!firstChild)
- totalHeight += mSpacing;
- firstChild = false;
- }
-
- rowWidth += childWidth;
-
- if (rowWidth > totalWidth)
- totalWidth = rowWidth;
-
- rowWidth += mSpacing;
- }
-
- setMeasuredDimension(totalWidth, totalHeight);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- final int childCount = getChildCount();
- final int totalWidth = r - l;
- int x = 0;
- int y = 0;
- int prevChildHeight = 0;
-
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() == GONE)
- continue;
-
- final int childWidth = child.getMeasuredWidth();
- final int childHeight = child.getMeasuredHeight();
- if (x + childWidth > totalWidth) {
- x = 0;
- y += prevChildHeight + mSpacing;
- }
- prevChildHeight = childHeight;
- child.layout(x, y, x + childWidth, y + childHeight);
- x += childWidth + mSpacing;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/GeckoActionProvider.java b/mobile/android/base/java/org/mozilla/gecko/widget/GeckoActionProvider.java
deleted file mode 100644
index d864792a6..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/GeckoActionProvider.java
+++ /dev/null
@@ -1,360 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import android.app.Activity;
-import android.net.Uri;
-import android.support.design.widget.Snackbar;
-import android.util.Base64;
-import android.view.Menu;
-
-import org.mozilla.gecko.GeckoApp;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.SnackbarBuilder;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.overlays.ui.ShareDialog;
-import org.mozilla.gecko.menu.MenuItemSwitcherLayout;
-import org.mozilla.gecko.util.IOUtils;
-import org.mozilla.gecko.util.IntentUtils;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.view.MenuItem;
-import android.view.MenuItem.OnMenuItemClickListener;
-import android.view.SubMenu;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.text.TextUtils;
-import android.webkit.MimeTypeMap;
-import android.webkit.URLUtil;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.HashMap;
-
-public class GeckoActionProvider {
- private static final int MAX_HISTORY_SIZE_DEFAULT = 2;
-
- /**
- * A listener to know when a target was selected.
- * When setting a provider, the activity can listen to this,
- * to close the menu.
- */
- public interface OnTargetSelectedListener {
- public void onTargetSelected();
- }
-
- final Context mContext;
-
- public final static String DEFAULT_MIME_TYPE = "text/plain";
-
- public static final String DEFAULT_HISTORY_FILE_NAME = "history.xml";
-
- // History file.
- String mHistoryFileName = DEFAULT_HISTORY_FILE_NAME;
-
- OnTargetSelectedListener mOnTargetListener;
-
- private final Callbacks mCallbacks = new Callbacks();
-
- private static final HashMap<String, GeckoActionProvider> mProviders = new HashMap<String, GeckoActionProvider>();
-
- private static String getFilenameFromMimeType(String mimeType) {
- String[] mime = mimeType.split("/");
-
- // All text mimetypes use the default provider
- if ("text".equals(mime[0])) {
- return DEFAULT_HISTORY_FILE_NAME;
- }
-
- return "history-" + mime[0] + ".xml";
- }
-
- // Gets the action provider for a particular mimetype
- public static GeckoActionProvider getForType(String mimeType, Context context) {
- if (!mProviders.keySet().contains(mimeType)) {
- GeckoActionProvider provider = new GeckoActionProvider(context);
-
- // For empty types, we just return a default provider
- if (TextUtils.isEmpty(mimeType)) {
- return provider;
- }
-
- provider.setHistoryFileName(getFilenameFromMimeType(mimeType));
- mProviders.put(mimeType, provider);
- }
- return mProviders.get(mimeType);
- }
-
- public GeckoActionProvider(Context context) {
- mContext = context;
- }
-
- /**
- * Creates the action view using the default history size.
- */
- public View onCreateActionView(final ActionViewType viewType) {
- return onCreateActionView(MAX_HISTORY_SIZE_DEFAULT, viewType);
- }
-
- public View onCreateActionView(final int maxHistorySize, final ActionViewType viewType) {
- // Create the view and set its data model.
- ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
- final MenuItemSwitcherLayout view;
- switch (viewType) {
- case DEFAULT:
- view = new MenuItemSwitcherLayout(mContext, null);
- break;
-
- case CONTEXT_MENU:
- view = new MenuItemSwitcherLayout(mContext, null);
- view.initContextMenuStyles();
- break;
-
- default:
- throw new IllegalArgumentException(
- "Unknown " + ActionViewType.class.getSimpleName() + ": " + viewType);
- }
- view.addActionButtonClickListener(mCallbacks);
-
- final PackageManager packageManager = mContext.getPackageManager();
- int historySize = dataModel.getDistinctActivityCountInHistory();
- if (historySize > maxHistorySize) {
- historySize = maxHistorySize;
- }
-
- // Historical data is dependent on past selection of activities.
- // Activity count is determined by the number of activities that can handle
- // the particular intent. When no intent is set, the activity count is 0,
- // while the history count can be a valid number.
- if (historySize > dataModel.getActivityCount()) {
- return view;
- }
-
- for (int i = 0; i < historySize; i++) {
- view.addActionButton(dataModel.getActivity(i).loadIcon(packageManager),
- dataModel.getActivity(i).loadLabel(packageManager));
- }
-
- return view;
- }
-
- public boolean hasSubMenu() {
- return true;
- }
-
- public void onPrepareSubMenu(SubMenu subMenu) {
- // Clear since the order of items may change.
- subMenu.clear();
-
- ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
- PackageManager packageManager = mContext.getPackageManager();
-
- // Populate the sub-menu with a sub set of the activities.
- final String shareDialogClassName = ShareDialog.class.getCanonicalName();
- final String sendTabLabel = mContext.getResources().getString(R.string.overlay_share_send_other);
- final int count = dataModel.getActivityCount();
- for (int i = 0; i < count; i++) {
- ResolveInfo activity = dataModel.getActivity(i);
- final CharSequence activityLabel = activity.loadLabel(packageManager);
-
- // Pin internal actions to the top. Note:
- // the order here does not affect quick share.
- final int order;
- if (shareDialogClassName.equals(activity.activityInfo.name) &&
- sendTabLabel.equals(activityLabel)) {
- order = Menu.FIRST + i;
- } else {
- order = Menu.FIRST + (i | Menu.CATEGORY_SECONDARY);
- }
-
- subMenu.add(0, i, order, activityLabel)
- .setIcon(activity.loadIcon(packageManager))
- .setOnMenuItemClickListener(mCallbacks);
- }
- }
-
- public void setHistoryFileName(String historyFile) {
- mHistoryFileName = historyFile;
- }
-
- public Intent getIntent() {
- ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
- return dataModel.getIntent();
- }
-
- public void setIntent(Intent intent) {
- ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
- dataModel.setIntent(intent);
-
- // Inform the target listener to refresh it's UI, if needed.
- if (mOnTargetListener != null) {
- mOnTargetListener.onTargetSelected();
- }
- }
-
- public void setOnTargetSelectedListener(OnTargetSelectedListener listener) {
- mOnTargetListener = listener;
- }
-
- public ArrayList<ResolveInfo> getSortedActivities() {
- ArrayList<ResolveInfo> infos = new ArrayList<ResolveInfo>();
-
- ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
-
- // Populate the sub-menu with a sub set of the activities.
- final int count = dataModel.getActivityCount();
- for (int i = 0; i < count; i++) {
- infos.add(dataModel.getActivity(i));
- }
-
- return infos;
- }
-
- public void chooseActivity(int position) {
- mCallbacks.chooseActivity(position);
- }
-
- /**
- * Listener for handling default activity / menu item clicks.
- */
- private class Callbacks implements OnMenuItemClickListener,
- OnClickListener {
- void chooseActivity(int index) {
- final ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName);
- final Intent launchIntent = dataModel.chooseActivity(index);
- if (launchIntent != null) {
- // This may cause a download to happen. Make sure we're on the background thread.
- ThreadUtils.postToBackgroundThread(new Runnable() {
- @Override
- public void run() {
- // Share image downloads the image before sharing it.
- String type = launchIntent.getType();
- if (Intent.ACTION_SEND.equals(launchIntent.getAction()) && type != null && type.startsWith("image/")) {
- downloadImageForIntent(launchIntent);
- }
-
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
- mContext.startActivity(launchIntent);
- }
- });
- }
-
- if (mOnTargetListener != null) {
- mOnTargetListener.onTargetSelected();
- }
- }
-
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- chooseActivity(item.getItemId());
-
- // Context: Sharing via chrome mainmenu list (no explicit session is active)
- Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "actionprovider");
- return true;
- }
-
- @Override
- public void onClick(View view) {
- Integer index = (Integer) view.getTag();
- chooseActivity(index);
-
- // Context: Sharing via chrome mainmenu and content contextmenu quickshare (no explicit session is active)
- Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.BUTTON, "actionprovider");
- }
- }
-
- public enum ActionViewType {
- DEFAULT,
- CONTEXT_MENU,
- }
-
-
- /**
- * Downloads the URI pointed to by a share intent, and alters the intent to point to the
- * locally stored file.
- *
- * @param intent share intent to alter in place.
- */
- public void downloadImageForIntent(final Intent intent) {
- final String src = IntentUtils.getStringExtraSafe(intent, Intent.EXTRA_TEXT);
- final File dir = GeckoApp.getTempDirectory();
-
- if (src == null || dir == null) {
- // We should be, but currently aren't, statically guaranteed an Activity context.
- // Try our best.
- if (mContext instanceof Activity) {
- SnackbarBuilder.builder((Activity) mContext)
- .message(mContext.getApplicationContext().getString(R.string.share_image_failed))
- .duration(Snackbar.LENGTH_LONG)
- .buildAndShow();
- }
- return;
- }
-
- GeckoApp.deleteTempFiles();
-
- String type = intent.getType();
- OutputStream os = null;
- try {
- // Create a temporary file for the image
- if (src.startsWith("data:")) {
- final int dataStart = src.indexOf(",");
-
- String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(type);
-
- // If we weren't given an explicit mimetype, try to dig one out of the data uri.
- if (TextUtils.isEmpty(extension) && dataStart > 5) {
- type = src.substring(5, dataStart).replace(";base64", "");
- extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(type);
- }
-
- final File imageFile = File.createTempFile("image", "." + extension, dir);
- os = new FileOutputStream(imageFile);
-
- byte[] buf = Base64.decode(src.substring(dataStart + 1), Base64.DEFAULT);
- os.write(buf);
-
- // Only alter the intent when we're sure everything has worked
- intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(imageFile));
- } else {
- InputStream is = null;
- try {
- final byte[] buf = new byte[2048];
- final URL url = new URL(src);
- final String filename = URLUtil.guessFileName(src, null, type);
- is = url.openStream();
-
- final File imageFile = new File(dir, filename);
- os = new FileOutputStream(imageFile);
-
- int length;
- while ((length = is.read(buf)) != -1) {
- os.write(buf, 0, length);
- }
-
- // Only alter the intent when we're sure everything has worked
- intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(imageFile));
- } finally {
- IOUtils.safeStreamClose(is);
- }
- }
- } catch (IOException ex) {
- // If something went wrong, we'll just leave the intent un-changed
- } finally {
- IOUtils.safeStreamClose(os);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/GeckoPopupMenu.java b/mobile/android/base/java/org/mozilla/gecko/widget/GeckoPopupMenu.java
deleted file mode 100644
index 7e7f50662..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/GeckoPopupMenu.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import org.mozilla.gecko.menu.GeckoMenu;
-import org.mozilla.gecko.menu.GeckoMenuInflater;
-import org.mozilla.gecko.menu.MenuPanel;
-import org.mozilla.gecko.menu.MenuPopup;
-
-import android.content.Context;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-
-/**
- * A PopupMenu that uses the custom GeckoMenu. This menu is
- * usually tied to an anchor, and show as a dropdrown from the anchor.
- */
-public class GeckoPopupMenu implements GeckoMenu.Callback,
- GeckoMenu.MenuPresenter {
-
- // An interface for listeners for dismissal.
- public static interface OnDismissListener {
- public boolean onDismiss(GeckoMenu menu);
- }
-
- // An interface for listeners for menu item click events.
- public static interface OnMenuItemClickListener {
- public boolean onMenuItemClick(MenuItem item);
- }
-
- // An interface for listeners for menu item long click events.
- public static interface OnMenuItemLongClickListener {
- public boolean onMenuItemLongClick(MenuItem item);
- }
-
- private View mAnchor;
-
- private MenuPopup mMenuPopup;
- private MenuPanel mMenuPanel;
-
- private GeckoMenu mMenu;
- private GeckoMenuInflater mMenuInflater;
-
- private OnDismissListener mDismissListener;
- private OnMenuItemClickListener mClickListener;
- private OnMenuItemLongClickListener mLongClickListener;
-
- public GeckoPopupMenu(Context context) {
- initialize(context, null);
- }
-
- public GeckoPopupMenu(Context context, View anchor) {
- initialize(context, anchor);
- }
-
- /**
- * This method creates an empty menu and attaches the necessary listeners.
- * If an anchor is supplied, it is stored as well.
- */
- private void initialize(Context context, View anchor) {
- mMenu = new GeckoMenu(context, null);
- mMenu.setCallback(this);
- mMenu.setMenuPresenter(this);
- mMenuInflater = new GeckoMenuInflater(context);
-
- mMenuPopup = new MenuPopup(context);
- mMenuPanel = new MenuPanel(context, null);
-
- mMenuPanel.addView(mMenu);
- mMenuPopup.setPanelView(mMenuPanel);
-
- setAnchor(anchor);
- }
-
- /**
- * Returns the menu that is current being shown.
- *
- * @return The menu being shown.
- */
- public GeckoMenu getMenu() {
- return mMenu;
- }
-
- /**
- * Returns the menu inflater that was used to create the menu.
- *
- * @return The menu inflater used.
- */
- public MenuInflater getMenuInflater() {
- return mMenuInflater;
- }
-
- /**
- * Inflates a menu resource to the menu using the menu inflater.
- *
- * @param menuRes The menu resource to be inflated.
- */
- public void inflate(int menuRes) {
- if (menuRes > 0) {
- mMenuInflater.inflate(menuRes, mMenu);
- }
- }
-
- /**
- * Set a different anchor after the menu is inflated.
- *
- * @param anchor The new anchor for the popup.
- */
- public void setAnchor(View anchor) {
- mAnchor = anchor;
-
- // Reposition the popup if the anchor changes while it's showing.
- if (mMenuPopup.isShowing()) {
- mMenuPopup.dismiss();
- mMenuPopup.showAsDropDown(mAnchor);
- }
- }
-
- public void setOnDismissListener(OnDismissListener listener) {
- mDismissListener = listener;
- }
-
- public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
- mClickListener = listener;
- }
-
- public void setOnMenuItemLongClickListener(OnMenuItemLongClickListener listener) {
- mLongClickListener = listener;
- }
-
- /**
- * Show the inflated menu.
- */
- public void show() {
- if (!mMenuPopup.isShowing())
- mMenuPopup.showAsDropDown(mAnchor);
- }
-
- /**
- * Hide the inflated menu.
- */
- public void dismiss() {
- if (mMenuPopup.isShowing()) {
- mMenuPopup.dismiss();
-
- if (mDismissListener != null)
- mDismissListener.onDismiss(mMenu);
- }
- }
-
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- if (mClickListener != null) {
- return mClickListener.onMenuItemClick(item);
- }
- return false;
- }
-
- @Override
- public boolean onMenuItemLongClick(MenuItem item) {
- if (mLongClickListener != null) {
- return mLongClickListener.onMenuItemLongClick(item);
- }
- return false;
- }
-
- @Override
- public void openMenu() {
- show();
- }
-
- @Override
- public void showMenu(View menu) {
- mMenuPanel.removeAllViews();
- mMenuPanel.addView(menu);
-
- openMenu();
- }
-
- @Override
- public void closeMenu() {
- dismiss();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/HistoryDividerItemDecoration.java b/mobile/android/base/java/org/mozilla/gecko/widget/HistoryDividerItemDecoration.java
deleted file mode 100644
index 9c98e8a0d..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/HistoryDividerItemDecoration.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.support.v4.content.ContextCompat;
-import android.support.v7.widget.RecyclerView;
-import android.view.View;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.home.CombinedHistoryItem;
-
-public class HistoryDividerItemDecoration extends RecyclerView.ItemDecoration {
- private final int mDividerHeight;
- private final Paint mDividerPaint;
-
- public HistoryDividerItemDecoration(Context context) {
- mDividerHeight = (int) context.getResources().getDimension(R.dimen.page_row_divider_height);
-
- mDividerPaint = new Paint();
- mDividerPaint.setColor(ContextCompat.getColor(context, R.color.toolbar_divider_grey));
- mDividerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
- }
-
- @Override
- public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
- final int position = parent.getChildAdapterPosition(view);
- if (position == RecyclerView.NO_POSITION) {
- // This view is no longer corresponds to an adapter position (pending changes).
- return;
- }
-
- if (parent.getAdapter().getItemViewType(position) !=
- CombinedHistoryItem.ItemType.itemTypeToViewType(CombinedHistoryItem.ItemType.SECTION_HEADER)) {
- outRect.set(0, 0, 0, mDividerHeight);
- }
- }
-
- @Override
- public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
- if (parent.getChildCount() == 0) {
- return;
- }
-
- for (int i = 0; i < parent.getChildCount(); i++) {
- final View child = parent.getChildAt(i);
- final int position = parent.getChildAdapterPosition(child);
-
- if (position == RecyclerView.NO_POSITION) {
- // This view is no longer corresponds to an adapter position (pending changes).
- continue;
- }
-
- if (parent.getAdapter().getItemViewType(position) !=
- CombinedHistoryItem.ItemType.itemTypeToViewType(CombinedHistoryItem.ItemType.SECTION_HEADER)) {
- final float bottom = child.getBottom() + child.getTranslationY();
- c.drawRect(0, bottom, parent.getWidth(), bottom + mDividerHeight, mDividerPaint);
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/IconTabWidget.java b/mobile/android/base/java/org/mozilla/gecko/widget/IconTabWidget.java
deleted file mode 100644
index 71987bf8c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/IconTabWidget.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageButton;
-import android.widget.TabWidget;
-import android.widget.TextView;
-
-public class IconTabWidget extends TabWidget {
- OnTabChangedListener mListener;
- private final int mButtonLayoutId;
- private final boolean mIsIcon;
-
- public static interface OnTabChangedListener {
- public void onTabChanged(int tabIndex);
- }
-
- public IconTabWidget(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.IconTabWidget);
- mButtonLayoutId = a.getResourceId(R.styleable.IconTabWidget_android_layout, 0);
- mIsIcon = (a.getInt(R.styleable.IconTabWidget_display, 0x00) == 0x00);
- a.recycle();
-
- if (mButtonLayoutId == 0) {
- throw new RuntimeException("You must supply layout attribute");
- }
- }
-
- public View addTab(final int imageResId, final int stringResId) {
- View button = LayoutInflater.from(getContext()).inflate(mButtonLayoutId, this, false);
- if (mIsIcon) {
- ((ImageButton) button).setImageResource(imageResId);
- button.setContentDescription(getContext().getString(stringResId));
- } else {
- ((TextView) button).setText(getContext().getString(stringResId));
- }
-
- addView(button);
- button.setOnClickListener(new TabClickListener(getTabCount() - 1));
- button.setOnFocusChangeListener(this);
- return button;
- }
-
- public void setTabSelectionListener(OnTabChangedListener listener) {
- mListener = listener;
- }
-
- @Override
- public void onFocusChange(View view, boolean hasFocus) {
- }
-
- private class TabClickListener implements OnClickListener {
- private final int mIndex;
-
- public TabClickListener(int index) {
- mIndex = index;
- }
-
- @Override
- public void onClick(View view) {
- if (mListener != null)
- mListener.onTabChanged(mIndex);
- }
- }
-
- /**
- * Fetch the Drawable icon corresponding to the given panel.
- * @param panel to fetch icon for.
- * @return Drawable instance, or null if no icon is being displayed, or the icon does not exist.
- */
- public Drawable getIconDrawable(int index) {
- if (!mIsIcon) {
- return null;
- }
- // We can have multiple views in the tabs panel for each child. This finds the
- // first view corresponding to the given tab. This varies by Android
- // version. The first view should always be our ImageButton, but let's
- // be safe.
- final View view = getChildTabViewAt(index);
- if (view instanceof ImageButton) {
- return ((ImageButton) view).getDrawable();
- }
- return null;
- }
-
- public void setIconDrawable(int index, int resource) {
- if (!mIsIcon) {
- return;
- }
- // We can have multiple views in the tabs panel for each child. This finds the
- // first view corresponding to the given tab. This varies by Android
- // version. The first view should always be our ImageButton, but let's
- // be safe.
- final View view = getChildTabViewAt(index);
- if (view instanceof ImageButton) {
- ((ImageButton) view).setImageResource(resource);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/LoginDoorHanger.java b/mobile/android/base/java/org/mozilla/gecko/widget/LoginDoorHanger.java
deleted file mode 100644
index 232674813..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/LoginDoorHanger.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.text.Html;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.method.PasswordTransformationMethod;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.Button;
-import android.widget.CheckBox;
-import android.widget.CompoundButton;
-import android.widget.EditText;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-
-import java.util.Locale;
-
-public class LoginDoorHanger extends DoorHanger {
- private static final String LOGTAG = "LoginDoorHanger";
- private enum ActionType { EDIT, SELECT }
-
- private final TextView mMessage;
- private final DoorhangerConfig.ButtonConfig mButtonConfig;
-
- public LoginDoorHanger(Context context, DoorhangerConfig config) {
- super(context, config, Type.LOGIN);
-
- mMessage = (TextView) findViewById(R.id.doorhanger_message);
- mIcon.setImageResource(R.drawable.icon_key);
- mIcon.setVisibility(View.VISIBLE);
-
- mButtonConfig = config.getPositiveButtonConfig();
-
- loadConfig(config);
- }
-
- private void setMessage(String message) {
- Spanned markupMessage = Html.fromHtml(message);
- mMessage.setText(markupMessage);
- }
-
- @Override
- protected void loadConfig(DoorhangerConfig config) {
- setOptions(config.getOptions());
- setMessage(config.getMessage());
- // Store the positive callback id for nested dialogs that need the same callback id.
- addButtonsToLayout(config);
- }
-
- @Override
- protected int getContentResource() {
- return R.layout.login_doorhanger;
- }
-
- @Override
- protected void setOptions(final JSONObject options) {
- super.setOptions(options);
-
- final JSONObject actionText = options.optJSONObject("actionText");
- addActionText(actionText);
- }
-
- @Override
- protected OnClickListener makeOnButtonClickListener(final int id, final String telemetryExtra) {
- return new Button.OnClickListener() {
- @Override
- public void onClick(View v) {
- final String expandedExtra = mType.toString().toLowerCase(Locale.US) + "-" + telemetryExtra;
- Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.DOORHANGER, expandedExtra);
-
- final JSONObject response = new JSONObject();
- try {
- response.put("callback", id);
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error making doorhanger response message", e);
- }
- mOnButtonClickListener.onButtonClick(response, LoginDoorHanger.this);
- }
- };
- }
-
- /**
- * Add sub-text to the doorhanger and add the click action.
- *
- * If the parsing the action from the JSON throws, the text is left visible, but there is no
- * click action.
- * @param actionTextObj JSONObject containing blob for making an action.
- */
- private void addActionText(JSONObject actionTextObj) {
- if (actionTextObj == null) {
- mLink.setVisibility(View.GONE);
- return;
- }
-
- // Make action.
- try {
- final JSONObject bundle = actionTextObj.getJSONObject("bundle");
- final ActionType type = ActionType.valueOf(actionTextObj.getString("type"));
- final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
-
- switch (type) {
- case EDIT:
- builder.setTitle(mResources.getString(R.string.doorhanger_login_edit_title));
-
- final View view = LayoutInflater.from(mContext).inflate(R.layout.login_edit_dialog, null);
- final EditText username = (EditText) view.findViewById(R.id.username_edit);
- username.setText(bundle.getString("username"));
- final EditText password = (EditText) view.findViewById(R.id.password_edit);
- password.setText(bundle.getString("password"));
- final CheckBox passwordCheckbox = (CheckBox) view.findViewById(R.id.checkbox_toggle_password);
- passwordCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- if (isChecked) {
- password.setTransformationMethod(null);
- } else {
- password.setTransformationMethod(PasswordTransformationMethod.getInstance());
- }
- }
- });
- builder.setView(view);
-
- builder.setPositiveButton(mButtonConfig.label, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- JSONObject response = new JSONObject();
- try {
- response.put("callback", mButtonConfig.callback);
- final JSONObject inputs = new JSONObject();
- inputs.put("username", username.getText());
- inputs.put("password", password.getText());
- response.put("inputs", inputs);
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error creating doorhanger reply message");
- response = null;
- Toast.makeText(mContext, mResources.getString(R.string.doorhanger_login_edit_toast_error), Toast.LENGTH_SHORT).show();
- }
- mOnButtonClickListener.onButtonClick(response, LoginDoorHanger.this);
- }
- });
- builder.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- });
- String text = actionTextObj.optString("text");
- if (TextUtils.isEmpty(text)) {
- text = mResources.getString(R.string.doorhanger_login_no_username);
- }
- mLink.setText(text);
- mLink.setVisibility(View.VISIBLE);
- break;
-
- case SELECT:
- try {
- builder.setTitle(mResources.getString(R.string.doorhanger_login_select_title));
- final JSONArray logins = bundle.getJSONArray("logins");
- final int numLogins = logins.length();
- final CharSequence[] usernames = new CharSequence[numLogins];
- final String[] passwords = new String[numLogins];
- final String noUser = mResources.getString(R.string.doorhanger_login_no_username);
- for (int i = 0; i < numLogins; i++) {
- final JSONObject login = (JSONObject) logins.get(i);
- String user = login.getString("username");
- if (TextUtils.isEmpty(user)) {
- user = noUser;
- }
- usernames[i] = user;
- passwords[i] = login.getString("password");
- }
- builder.setItems(usernames, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- final JSONObject response = new JSONObject();
- try {
- response.put("callback", mButtonConfig.callback);
- response.put("password", passwords[which]);
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error making login select dialog JSON", e);
- }
- mOnButtonClickListener.onButtonClick(response, LoginDoorHanger.this);
- }
- });
- builder.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- });
- mLink.setText(R.string.doorhanger_login_select_action_text);
- mLink.setVisibility(View.VISIBLE);
- } catch (JSONException e) {
- Log.e(LOGTAG, "Problem creating list of logins");
- }
- break;
- }
-
- final Dialog dialog = builder.create();
- mLink.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- dialog.show();
- }
- });
-
- } catch (JSONException e) {
- Log.e(LOGTAG, "Error fetching actionText from JSON", e);
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/RecyclerViewClickSupport.java b/mobile/android/base/java/org/mozilla/gecko/widget/RecyclerViewClickSupport.java
deleted file mode 100644
index a0c6049c5..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/RecyclerViewClickSupport.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import android.support.v7.widget.RecyclerView;
-import android.view.View;
-
-import org.mozilla.gecko.R;
-
-/**
- * {@link RecyclerViewClickSupport} implementation that will notify an OnClickListener about clicks and long clicks
- * on items displayed by the RecyclerView.
- * @see <a href="http://www.littlerobots.nl/blog/Handle-Android-RecyclerView-Clicks/">littlerobots.nl</a>
- */
-public class RecyclerViewClickSupport {
- private final RecyclerView mRecyclerView;
- private OnItemClickListener mOnItemClickListener;
- private OnItemLongClickListener mOnItemLongClickListener;
- private View.OnClickListener mOnClickListener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mOnItemClickListener != null) {
- RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
- mOnItemClickListener.onItemClicked(mRecyclerView, holder.getAdapterPosition(), v);
- }
- }
- };
- private View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- if (mOnItemLongClickListener != null) {
- RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
- return mOnItemLongClickListener.onItemLongClicked(mRecyclerView, holder.getAdapterPosition(), v);
- }
- return false;
- }
- };
- private RecyclerView.OnChildAttachStateChangeListener mAttachListener
- = new RecyclerView.OnChildAttachStateChangeListener() {
- @Override
- public void onChildViewAttachedToWindow(View view) {
- if (mOnItemClickListener != null) {
- view.setOnClickListener(mOnClickListener);
- }
- if (mOnItemLongClickListener != null) {
- view.setOnLongClickListener(mOnLongClickListener);
- }
- }
-
- @Override
- public void onChildViewDetachedFromWindow(View view) {
-
- }
- };
-
- private RecyclerViewClickSupport(RecyclerView recyclerView) {
- mRecyclerView = recyclerView;
- mRecyclerView.setTag(R.id.recycler_view_click_support, this);
- mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener);
- }
-
- public static RecyclerViewClickSupport addTo(RecyclerView view) {
- RecyclerViewClickSupport support = (RecyclerViewClickSupport) view.getTag(R.id.recycler_view_click_support);
- if (support == null) {
- support = new RecyclerViewClickSupport(view);
- }
- return support;
- }
-
- public static RecyclerViewClickSupport removeFrom(RecyclerView view) {
- RecyclerViewClickSupport support = (RecyclerViewClickSupport) view.getTag(R.id.recycler_view_click_support);
- if (support != null) {
- support.detach(view);
- }
- return support;
- }
-
- public RecyclerViewClickSupport setOnItemClickListener(OnItemClickListener listener) {
- mOnItemClickListener = listener;
- return this;
- }
-
- public RecyclerViewClickSupport setOnItemLongClickListener(OnItemLongClickListener listener) {
- mOnItemLongClickListener = listener;
- return this;
- }
-
- private void detach(RecyclerView view) {
- view.removeOnChildAttachStateChangeListener(mAttachListener);
- view.setTag(R.id.recycler_view_click_support, null);
- }
-
- public interface OnItemClickListener {
-
- void onItemClicked(RecyclerView recyclerView, int position, View v);
- }
-
- public interface OnItemLongClickListener {
-
- boolean onItemLongClicked(RecyclerView recyclerView, int position, View v);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/ResizablePathDrawable.java b/mobile/android/base/java/org/mozilla/gecko/widget/ResizablePathDrawable.java
deleted file mode 100644
index ff0709cb7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/ResizablePathDrawable.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import android.content.res.ColorStateList;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.Shape;
-
-public class ResizablePathDrawable extends ShapeDrawable {
- // An attribute mirroring the super class' value. getAlpha() is only
- // available in API 19+ so to use that alpha value, we have to mirror it.
- private int alpha = 255;
-
- private final ColorStateList colorStateList;
- private int currentColor;
-
- public ResizablePathDrawable(NonScaledPathShape shape, int color) {
- this(shape, ColorStateList.valueOf(color));
- }
-
- public ResizablePathDrawable(NonScaledPathShape shape, ColorStateList colorStateList) {
- super(shape);
- this.colorStateList = colorStateList;
- updateColor(getState());
- }
-
- private boolean updateColor(int[] stateSet) {
- int newColor = colorStateList.getColorForState(stateSet, Color.WHITE);
- if (newColor != currentColor) {
- currentColor = newColor;
- alpha = Color.alpha(currentColor);
- invalidateSelf();
- return true;
- }
-
- return false;
- }
-
- public Path getPath() {
- final NonScaledPathShape shape = (NonScaledPathShape) getShape();
- return shape.path;
- }
-
- @Override
- public boolean isStateful() {
- return true;
- }
-
- @Override
- protected void onDraw(Shape shape, Canvas canvas, Paint paint) {
- paint.setColor(currentColor);
- // setAlpha overrides the alpha value in set color. Since we just set the color,
- // the alpha value is reset: override the alpha value with the old value. We don't
- // set alpha if the color is transparent.
- //
- // Note: We *should* be able to call Shape.setAlpha, rather than Paint.setAlpha, but
- // then the opacity doesn't change - dunno why but probably not worth the time.
- if (currentColor != Color.TRANSPARENT) {
- paint.setAlpha(alpha);
- }
-
- super.onDraw(shape, canvas, paint);
- }
-
- @Override
- public void setAlpha(final int alpha) {
- super.setAlpha(alpha);
- this.alpha = alpha;
- }
-
- @Override
- protected boolean onStateChange(int[] stateSet) {
- return updateColor(stateSet);
- }
-
- /**
- * Path-based shape implementation that re-creates the path
- * when it gets resized as opposed to PathShape's scaling
- * behaviour.
- */
- public static class NonScaledPathShape extends Shape {
- private Path path;
-
- public NonScaledPathShape() {
- path = new Path();
- }
-
- @Override
- public void draw(Canvas canvas, Paint paint) {
- // No point in drawing the shape if it's not
- // going to be visible.
- if (paint.getColor() == Color.TRANSPARENT) {
- return;
- }
-
- canvas.drawPath(path, paint);
- }
-
- protected Path getPath() {
- return path;
- }
-
- @Override
- public NonScaledPathShape clone() throws CloneNotSupportedException {
- final NonScaledPathShape clonedShape = (NonScaledPathShape) super.clone();
- clonedShape.path = new Path(path);
- return clonedShape;
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/RoundedCornerLayout.java b/mobile/android/base/java/org/mozilla/gecko/widget/RoundedCornerLayout.java
deleted file mode 100644
index a102981ee..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/RoundedCornerLayout.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
-import android.widget.LinearLayout;
-
-public class RoundedCornerLayout extends LinearLayout {
- private static final String LOGTAG = "Gecko" + RoundedCornerLayout.class.getSimpleName();
- private float cornerRadius;
-
- private Path path;
- boolean cannotClipPath;
-
- public RoundedCornerLayout(Context context) {
- super(context);
- init(context);
- }
-
- public RoundedCornerLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- init(context);
- }
-
- public RoundedCornerLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init(context);
- }
-
- private void init(Context context) {
- // Bug 1201081 - clipPath with hardware acceleration crashes on r11-18.
- cannotClipPath = !AppConstants.Versions.feature19Plus;
-
- final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
-
- cornerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
- getResources().getDimensionPixelSize(R.dimen.doorhanger_rounded_corner_radius), metrics);
-
- setWillNotDraw(false);
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- if (cannotClipPath) {
- return;
- }
-
- final RectF r = new RectF(0, 0, w, h);
- path = new Path();
- path.addRoundRect(r, cornerRadius, cornerRadius, Path.Direction.CW);
- path.close();
- }
-
- @Override
- public void draw(Canvas canvas) {
- if (cannotClipPath) {
- super.draw(canvas);
- return;
- }
-
- canvas.save();
- canvas.clipPath(path);
- super.draw(canvas);
- canvas.restore();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/SiteLogins.java b/mobile/android/base/java/org/mozilla/gecko/widget/SiteLogins.java
deleted file mode 100644
index 4d4a92275..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/SiteLogins.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.mozilla.gecko.widget;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-
-public class SiteLogins {
- private final JSONArray logins;
-
- public SiteLogins(JSONArray logins) {
- this.logins = logins;
- }
-
- public JSONArray getLogins() {
- return logins;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/SquaredImageView.java b/mobile/android/base/java/org/mozilla/gecko/widget/SquaredImageView.java
deleted file mode 100644
index 0b77e9d1c..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/SquaredImageView.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package org.mozilla.gecko.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.ImageView;
-
-final class SquaredImageView extends ImageView {
- public SquaredImageView(Context context) {
- super(context);
- }
-
- public SquaredImageView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
- }
-} \ No newline at end of file
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/SquaredRelativeLayout.java b/mobile/android/base/java/org/mozilla/gecko/widget/SquaredRelativeLayout.java
deleted file mode 100644
index c0dca0bec..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/SquaredRelativeLayout.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.RelativeLayout;
-
-public class SquaredRelativeLayout extends RelativeLayout {
- public SquaredRelativeLayout(Context context) {
- super(context);
- }
-
- public SquaredRelativeLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public SquaredRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- int squareMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
-
- super.onMeasure(squareMeasureSpec, squareMeasureSpec);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/SwipeDismissListViewTouchListener.java b/mobile/android/base/java/org/mozilla/gecko/widget/SwipeDismissListViewTouchListener.java
deleted file mode 100644
index 8267fe8a3..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/SwipeDismissListViewTouchListener.java
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- * Copyright 2012 Roman Nurik
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.mozilla.gecko.widget;
-
-import android.graphics.Rect;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.AbsListView.RecyclerListener;
-import android.widget.ListView;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.view.ViewPropertyAnimator;
-
-import org.mozilla.gecko.R;
-
-/**
- * This code is based off of Jake Wharton's NOA port (https://github.com/JakeWharton/SwipeToDismissNOA)
- * of Roman Nurik's SwipeToDismiss library. It has been modified for better support with async
- * adapters.
- *
- * A {@link android.view.View.OnTouchListener} that makes the list items in a {@link ListView}
- * dismissable. {@link ListView} is given special treatment because by default it handles touches
- * for its list items... i.e. it's in charge of drawing the pressed state (the list selector),
- * handling list item clicks, etc.
- *
- * <p>After creating the listener, the caller should also call
- * {@link ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener)}, passing
- * in the scroll listener returned by {@link #makeScrollListener()}. If a scroll listener is
- * already assigned, the caller should still pass scroll changes through to this listener. This will
- * ensure that this {@link SwipeDismissListViewTouchListener} is paused during list view
- * scrolling.</p>
- *
- * <p>Example usage:</p>
- *
- * <pre>
- * SwipeDismissListViewTouchListener touchListener =
- * new SwipeDismissListViewTouchListener(
- * listView,
- * new SwipeDismissListViewTouchListener.OnDismissCallback() {
- * public void onDismiss(ListView listView, int[] reverseSortedPositions) {
- * for (int position : reverseSortedPositions) {
- * adapter.remove(adapter.getItem(position));
- * }
- * adapter.notifyDataSetChanged();
- * }
- * });
- * listView.setOnTouchListener(touchListener);
- * listView.setOnScrollListener(touchListener.makeScrollListener());
- * </pre>
- *
- * <p>For a generalized {@link android.view.View.OnTouchListener} that makes any view dismissable,
- * see {@link SwipeDismissTouchListener}.</p>
- *
- * @see SwipeDismissTouchListener
- */
-public class SwipeDismissListViewTouchListener implements View.OnTouchListener {
- // Cached ViewConfiguration and system-wide constant values
- private final int mSlop;
- private final int mMinFlingVelocity;
- private final int mMaxFlingVelocity;
- private final long mAnimationTime;
-
- // Fixed properties
- private final ListView mListView;
- private final OnDismissCallback mCallback;
- private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
-
- // Transient properties
- private float mDownX;
- private boolean mSwiping;
- private VelocityTracker mVelocityTracker;
- private int mDownPosition;
- private View mDownView;
- private boolean mPaused;
- private boolean mDismissing;
-
- /**
- * The callback interface used by {@link SwipeDismissListViewTouchListener} to inform its client
- * about a successful dismissal of a list item.
- */
- public interface OnDismissCallback {
- /**
- * Called when the user has indicated they she would like to dismiss one or more list item
- * positions.
- *
- * @param listView The originating {@link ListView}.
- * @param position The position being dismissed.
- */
- void onDismiss(ListView listView, int position);
- }
-
- /**
- * Constructs a new swipe-to-dismiss touch listener for the given list view.
- *
- * @param listView The list view whose items should be dismissable.
- * @param callback The callback to trigger when the user has indicated that she would like to
- * dismiss one or more list items.
- */
- public SwipeDismissListViewTouchListener(ListView listView, OnDismissCallback callback) {
- ViewConfiguration vc = ViewConfiguration.get(listView.getContext());
- mSlop = vc.getScaledTouchSlop();
- mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
- mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
- mAnimationTime = listView.getContext().getResources().getInteger(
- android.R.integer.config_shortAnimTime);
- mListView = listView;
- mCallback = callback;
- }
-
- /**
- * Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures.
- *
- * @param enabled Whether or not to watch for gestures.
- */
- public void setEnabled(boolean enabled) {
- mPaused = !enabled;
- }
-
- /**
- * Returns an {@link android.widget.AbsListView.OnScrollListener} to be added to the
- * {@link ListView} using
- * {@link ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener)}.
- * If a scroll listener is already assigned, the caller should still pass scroll changes
- * through to this listener. This will ensure that this
- * {@link SwipeDismissListViewTouchListener} is paused during list view scrolling.</p>
- *
- * @see {@link SwipeDismissListViewTouchListener}
- */
- public AbsListView.OnScrollListener makeScrollListener() {
- return new AbsListView.OnScrollListener() {
- @Override
- public void onScrollStateChanged(AbsListView absListView, int scrollState) {
- setEnabled(scrollState != AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
- }
-
- @Override
- public void onScroll(AbsListView absListView, int i, int i1, int i2) {
- }
- };
- }
-
- /**
- * Returns a {@link android.widget.AbsListView.RecyclerListener} to be added to the
- * {@link ListView} using {@link ListView#setRecyclerListener(RecyclerListener)}.
- */
- public AbsListView.RecyclerListener makeRecyclerListener() {
- return new AbsListView.RecyclerListener() {
- @Override
- public void onMovedToScrapHeap(View view) {
- final Object tag = view.getTag(R.id.original_height);
-
- // To reset the view to the correct height after its animation, the view's height
- // is stored in its tag. Reset the view here.
- if (tag instanceof Integer) {
- view.setAlpha(1f);
- view.setTranslationX(0);
- final ViewGroup.LayoutParams lp = view.getLayoutParams();
- lp.height = (int) tag;
- view.setLayoutParams(lp);
- view.setTag(R.id.original_height, null);
- }
- }
- };
- }
-
- @Override
- public boolean onTouch(View view, MotionEvent motionEvent) {
- if (mViewWidth < 2) {
- mViewWidth = mListView.getWidth();
- }
-
- switch (motionEvent.getActionMasked()) {
- case MotionEvent.ACTION_DOWN: {
- if (mPaused) {
- return false;
- }
-
- if (mDismissing) {
- return true;
- }
-
- // TODO: ensure this is a finger, and set a flag
-
- // Find the child view that was touched (perform a hit test)
- Rect rect = new Rect();
- int childCount = mListView.getChildCount();
- int[] listViewCoords = new int[2];
- mListView.getLocationOnScreen(listViewCoords);
- int x = (int) motionEvent.getRawX() - listViewCoords[0];
- int y = (int) motionEvent.getRawY() - listViewCoords[1];
- View child;
- for (int i = 0; i < childCount; i++) {
- child = mListView.getChildAt(i);
- child.getHitRect(rect);
- if (rect.contains(x, y)) {
- mDownView = child;
- break;
- }
- }
-
- if (mDownView != null) {
- mDownX = motionEvent.getRawX();
- mDownPosition = mListView.getPositionForView(mDownView);
-
- mVelocityTracker = VelocityTracker.obtain();
- mVelocityTracker.addMovement(motionEvent);
- }
- view.onTouchEvent(motionEvent);
- return true;
- }
-
- case MotionEvent.ACTION_UP: {
- if (mVelocityTracker == null) {
- break;
- }
-
- float deltaX = motionEvent.getRawX() - mDownX;
- mVelocityTracker.addMovement(motionEvent);
- mVelocityTracker.computeCurrentVelocity(1000);
- float velocityX = Math.abs(mVelocityTracker.getXVelocity());
- float velocityY = Math.abs(mVelocityTracker.getYVelocity());
- boolean dismiss = false;
- boolean dismissRight = false;
- if (Math.abs(deltaX) > mViewWidth / 2) {
- dismiss = true;
- dismissRight = deltaX > 0;
- } else if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity
- && velocityY < velocityX) {
- dismiss = true;
- dismissRight = mVelocityTracker.getXVelocity() > 0;
- }
- if (dismiss) {
- // dismiss
- mDismissing = true;
- final View downView = mDownView; // mDownView gets null'd before animation ends
- final int downPosition = mDownPosition;
- mDownView.animate()
- .translationX(dismissRight ? mViewWidth : -mViewWidth)
- .alpha(0)
- .setDuration(mAnimationTime)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- performDismiss(downView, downPosition);
- }
- });
- } else {
- // cancel
- mDownView.animate()
- .translationX(0)
- .alpha(1)
- .setDuration(mAnimationTime)
- .setListener(null);
- }
-
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
-
- mDownX = 0;
- mDownView = null;
- mDownPosition = ListView.INVALID_POSITION;
- mSwiping = false;
- break;
- }
-
- case MotionEvent.ACTION_MOVE: {
- if (mVelocityTracker == null || mPaused) {
- break;
- }
-
- mVelocityTracker.addMovement(motionEvent);
- float deltaX = motionEvent.getRawX() - mDownX;
- if (Math.abs(deltaX) > mSlop) {
- mSwiping = true;
- mListView.requestDisallowInterceptTouchEvent(true);
-
- // Cancel ListView's touch (un-highlighting the item)
- MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
- cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
- (motionEvent.getActionIndex()
- << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
- mListView.onTouchEvent(cancelEvent);
- cancelEvent.recycle();
- }
-
- if (mSwiping) {
- mDownView.setTranslationX(deltaX);
- mDownView.setAlpha(Math.max(0f, Math.min(1f, 1f - 2f * Math.abs(deltaX) / mViewWidth)));
- return true;
- }
- break;
- }
- }
- return false;
- }
-
- /**
- * Animate the dismissed list item to zero-height and fire the dismiss callback when it finishes.
- *
- * @param dismissView ListView item to dismiss
- * @param dismissPosition Position of dismissed item
- */
- private void performDismiss(final View dismissView, final int dismissPosition) {
- final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();
- final int originalHeight = lp.height;
-
- ValueAnimator animator = ValueAnimator.ofInt(dismissView.getHeight(), 1).setDuration(mAnimationTime);
-
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- // Since the view is still a part of the ListView, we can't reset the animated
- // properties yet; otherwise, the view would briefly reappear. Store the original
- // height in the view's tag to flag it for the recycler. This is racy since the user
- // could scroll the dismissed view off the screen, then back on the screen, before
- // it's removed from the adapter, causing the dismissed view to briefly reappear.
- dismissView.setTag(R.id.original_height, originalHeight);
-
- mCallback.onDismiss(mListView, dismissPosition);
- mDismissing = false;
- }
- });
-
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator valueAnimator) {
- lp.height = (Integer) valueAnimator.getAnimatedValue();
- dismissView.setLayoutParams(lp);
- }
- });
-
- animator.start();
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/TabThumbnailWrapper.java b/mobile/android/base/java/org/mozilla/gecko/widget/TabThumbnailWrapper.java
deleted file mode 100644
index 848e2f6ed..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/TabThumbnailWrapper.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.mozilla.gecko.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.widget.themed.ThemedRelativeLayout;
-
-
-public class TabThumbnailWrapper extends ThemedRelativeLayout {
- private boolean mRecording;
- private static final int[] STATE_RECORDING = { R.attr.state_recording };
-
- public TabThumbnailWrapper(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public TabThumbnailWrapper(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public int[] onCreateDrawableState(int extraSpace) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
-
- if (mRecording) {
- mergeDrawableStates(drawableState, STATE_RECORDING);
- }
- return drawableState;
- }
-
- public void setRecording(boolean recording) {
- if (mRecording != recording) {
- mRecording = recording;
- refreshDrawableState();
- }
- }
-
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/ThumbnailView.java b/mobile/android/base/java/org/mozilla/gecko/widget/ThumbnailView.java
deleted file mode 100644
index 5ab00ea7f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/ThumbnailView.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import org.mozilla.gecko.R;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.widget.themed.ThemedImageView;
-
-/* Special version of ImageView for thumbnails. Scales a thumbnail so that it maintains its aspect
- * ratio and so that the images width and height are the same size or greater than the view size
- */
-public class ThumbnailView extends ThemedImageView {
- private static final String LOGTAG = "GeckoThumbnailView";
-
- final private Matrix mMatrix;
- private int mWidthSpec = -1;
- private int mHeightSpec = -1;
- private boolean mLayoutChanged;
- private boolean mScale = false;
-
- public ThumbnailView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mMatrix = new Matrix();
- mLayoutChanged = true;
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- if (!mScale) {
- super.onDraw(canvas);
- return;
- }
-
- Drawable d = getDrawable();
- if (mLayoutChanged) {
- int w1 = d.getIntrinsicWidth();
- int h1 = d.getIntrinsicHeight();
- int w2 = getWidth();
- int h2 = getHeight();
-
- float scale = ((w2 / h2) < (w1 / h1)) ? (float) h2 / h1 : (float) w2 / w1;
- mMatrix.setScale(scale, scale);
- }
-
- int saveCount = canvas.save();
- canvas.concat(mMatrix);
- d.draw(canvas);
- canvas.restoreToCount(saveCount);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // OnLayout.changed isn't a reliable measure of whether or not the size of this view has changed
- // neither is onSizeChanged called often enough. Instead, we track changes in size ourselves, and
- // only invalidate this matrix if we have a new width/height spec
- if (widthMeasureSpec != mWidthSpec || heightMeasureSpec != mHeightSpec) {
- mWidthSpec = widthMeasureSpec;
- mHeightSpec = heightMeasureSpec;
- mLayoutChanged = true;
- }
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public void setImageDrawable(Drawable drawable) {
- if (drawable == null) {
- drawable = ContextCompat.getDrawable(getContext(), R.drawable.tab_panel_tab_background);
- setScaleType(ScaleType.FIT_XY);
- mScale = false;
- } else {
- mScale = true;
- setScaleType(ScaleType.FIT_CENTER);
- }
-
- super.setImageDrawable(drawable);
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/TouchDelegateWithReset.java b/mobile/android/base/java/org/mozilla/gecko/widget/TouchDelegateWithReset.java
deleted file mode 100644
index 52e0b1fd0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/TouchDelegateWithReset.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget;
-
-import android.graphics.Rect;
-import android.view.MotionEvent;
-import android.view.TouchDelegate;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-/**
- * This is a copy of TouchDelegate from
- * https://github.com/android/platform_frameworks_base/blob/4b1a8f46d6ec55796bf77fd8921a5a242a219278/core/java/android/view/TouchDelegate.java
- * with a fix to reset mDelegateTargeted on each new gesture - the sole substantive change is a new
- * else leg in the ACTION_DOWN case of onTouchEvent marked by "START|END BUG FIX" comments.
- */
-
-/**
- * Helper class to handle situations where you want a view to have a larger touch area than its
- * actual view bounds. The view whose touch area is changed is called the delegate view. This
- * class should be used by an ancestor of the delegate. To use a TouchDelegate, first create an
- * instance that specifies the bounds that should be mapped to the delegate and the delegate
- * view itself.
- * <p>
- * The ancestor should then forward all of its touch events received in its
- * {@link android.view.View#onTouchEvent(MotionEvent)} to {@link #onTouchEvent(MotionEvent)}.
- * </p>
- */
-public class TouchDelegateWithReset extends TouchDelegate {
-
- /**
- * View that should receive forwarded touch events
- */
- private View mDelegateView;
-
- /**
- * Bounds in local coordinates of the containing view that should be mapped to the delegate
- * view. This rect is used for initial hit testing.
- */
- private Rect mBounds;
-
- /**
- * mBounds inflated to include some slop. This rect is to track whether the motion events
- * should be considered to be be within the delegate view.
- */
- private Rect mSlopBounds;
-
- /**
- * True if the delegate had been targeted on a down event (intersected mBounds).
- */
- private boolean mDelegateTargeted;
-
- private int mSlop;
-
- /**
- * Constructor
- *
- * @param bounds Bounds in local coordinates of the containing view that should be mapped to
- * the delegate view
- * @param delegateView The view that should receive motion events
- */
- public TouchDelegateWithReset(Rect bounds, View delegateView) {
- super(bounds, delegateView);
-
- mBounds = bounds;
-
- mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();
- mSlopBounds = new Rect(bounds);
- mSlopBounds.inset(-mSlop, -mSlop);
- mDelegateView = delegateView;
- }
-
- /**
- * Will forward touch events to the delegate view if the event is within the bounds
- * specified in the constructor.
- *
- * @param event The touch event to forward
- * @return True if the event was forwarded to the delegate, false otherwise.
- */
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- int x = (int)event.getX();
- int y = (int)event.getY();
- boolean sendToDelegate = false;
- boolean hit = true;
- boolean handled = false;
-
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- Rect bounds = mBounds;
-
- if (bounds.contains(x, y)) {
- mDelegateTargeted = true;
- sendToDelegate = true;
- } /* START BUG FIX */
- else {
- mDelegateTargeted = false;
- }
- /* END BUG FIX */
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_MOVE:
- sendToDelegate = mDelegateTargeted;
- if (sendToDelegate) {
- Rect slopBounds = mSlopBounds;
- if (!slopBounds.contains(x, y)) {
- hit = false;
- }
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- sendToDelegate = mDelegateTargeted;
- mDelegateTargeted = false;
- break;
- }
- if (sendToDelegate) {
- final View delegateView = mDelegateView;
-
- if (hit) {
- // Offset event coordinates to be inside the target view
- event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2);
- } else {
- // Offset event coordinates to be outside the target view (in case it does
- // something like tracking pressed state)
- int slop = mSlop;
- event.setLocation(-(slop * 2), -(slop * 2));
- }
- handled = delegateView.dispatchTouchEvent(event);
- }
- return handled;
- }
-} \ No newline at end of file
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/TwoWayView.java b/mobile/android/base/java/org/mozilla/gecko/widget/TwoWayView.java
deleted file mode 100644
index b5ad36ab7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/TwoWayView.java
+++ /dev/null
@@ -1,7191 +0,0 @@
-/*
- * Copyright (C) 2013 Lucas Rocha
- *
- * This code is based on bits and pieces of Android's AbsListView,
- * Listview, and StaggeredGridView.
- *
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.mozilla.gecko.widget;
-
-import org.mozilla.gecko.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.database.DataSetObserver;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.TransitionDrawable;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.SystemClock;
-import android.support.v4.util.LongSparseArray;
-import android.support.v4.util.SparseArrayCompat;
-import android.support.v4.view.AccessibilityDelegateCompat;
-import android.support.v4.view.KeyEventCompat;
-import android.support.v4.view.MotionEventCompat;
-import android.support.v4.view.VelocityTrackerCompat;
-import android.support.v4.view.ViewCompat;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
-import android.support.v4.widget.EdgeEffectCompat;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseBooleanArray;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.FocusFinder;
-import android.view.HapticFeedbackConstants;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.SoundEffectConstants;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.ViewTreeObserver;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.AdapterView;
-import android.widget.Checkable;
-import android.widget.ListAdapter;
-import android.widget.Scroller;
-
-import static android.os.Build.VERSION_CODES.HONEYCOMB;
-
-/*
- * Implementation Notes:
- *
- * Some terminology:
- *
- * index - index of the items that are currently visible
- * position - index of the items in the cursor
- *
- * Given the bi-directional nature of this view, the source code
- * usually names variables with 'start' to mean 'top' or 'left'; and
- * 'end' to mean 'bottom' or 'right', depending on the current
- * orientation of the widget.
- */
-
-/**
- * A view that shows items in a vertical or horizontal scrolling list.
- * The items come from the {@link ListAdapter} associated with this view.
- */
-public class TwoWayView extends AdapterView<ListAdapter> implements
- ViewTreeObserver.OnTouchModeChangeListener {
- private static final String LOGTAG = "TwoWayView";
-
- private static final int NO_POSITION = -1;
- private static final int INVALID_POINTER = -1;
-
- public static final int[] STATE_NOTHING = new int[] { 0 };
-
- private static final int TOUCH_MODE_REST = -1;
- private static final int TOUCH_MODE_DOWN = 0;
- private static final int TOUCH_MODE_TAP = 1;
- private static final int TOUCH_MODE_DONE_WAITING = 2;
- private static final int TOUCH_MODE_DRAGGING = 3;
- private static final int TOUCH_MODE_FLINGING = 4;
- private static final int TOUCH_MODE_OVERSCROLL = 5;
-
- private static final int TOUCH_MODE_UNKNOWN = -1;
- private static final int TOUCH_MODE_ON = 0;
- private static final int TOUCH_MODE_OFF = 1;
-
- private static final int LAYOUT_NORMAL = 0;
- private static final int LAYOUT_FORCE_TOP = 1;
- private static final int LAYOUT_SET_SELECTION = 2;
- private static final int LAYOUT_FORCE_BOTTOM = 3;
- private static final int LAYOUT_SPECIFIC = 4;
- private static final int LAYOUT_SYNC = 5;
- private static final int LAYOUT_MOVE_SELECTION = 6;
-
- private static final int SYNC_SELECTED_POSITION = 0;
- private static final int SYNC_FIRST_POSITION = 1;
-
- private static final int SYNC_MAX_DURATION_MILLIS = 100;
-
- private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
-
- private static final float MAX_SCROLL_FACTOR = 0.33f;
-
- private static final int MIN_SCROLL_PREVIEW_PIXELS = 10;
-
- public static enum ChoiceMode {
- NONE,
- SINGLE,
- MULTIPLE
- }
-
- public static enum Orientation {
- HORIZONTAL,
- VERTICAL
- }
-
- private final Context mContext;
-
- private ListAdapter mAdapter;
-
- private boolean mIsVertical;
-
- private int mItemMargin;
-
- private boolean mInLayout;
- private boolean mBlockLayoutRequests;
-
- private boolean mIsAttached;
-
- private final RecycleBin mRecycler;
- private AdapterDataSetObserver mDataSetObserver;
-
- private boolean mItemsCanFocus;
-
- final boolean[] mIsScrap = new boolean[1];
-
- private boolean mDataChanged;
- private int mItemCount;
- private int mOldItemCount;
- private boolean mHasStableIds;
- private boolean mAreAllItemsSelectable;
-
- private int mFirstPosition;
- private int mSpecificStart;
-
- private SavedState mPendingSync;
-
- private PositionScroller mPositionScroller;
- private Runnable mPositionScrollAfterLayout;
-
- private final int mTouchSlop;
- private final int mMaximumVelocity;
- private final int mFlingVelocity;
- private float mLastTouchPos;
- private float mTouchRemainderPos;
- private int mActivePointerId;
-
- private final Rect mTempRect;
-
- private final ArrowScrollFocusResult mArrowScrollFocusResult;
-
- private Rect mTouchFrame;
- private int mMotionPosition;
- private CheckForTap mPendingCheckForTap;
- private CheckForLongPress mPendingCheckForLongPress;
- private CheckForKeyLongPress mPendingCheckForKeyLongPress;
- private PerformClick mPerformClick;
- private Runnable mTouchModeReset;
- private int mResurrectToPosition;
-
- private boolean mIsChildViewEnabled;
-
- private boolean mDrawSelectorOnTop;
- private Drawable mSelector;
- private int mSelectorPosition;
- private final Rect mSelectorRect;
-
- private int mOverScroll;
- private final int mOverscrollDistance;
-
- private boolean mDesiredFocusableState;
- private boolean mDesiredFocusableInTouchModeState;
-
- private SelectionNotifier mSelectionNotifier;
-
- private boolean mNeedSync;
- private int mSyncMode;
- private int mSyncPosition;
- private long mSyncRowId;
- private long mSyncSize;
- private int mSelectedStart;
-
- private int mNextSelectedPosition;
- private long mNextSelectedRowId;
- private int mSelectedPosition;
- private long mSelectedRowId;
- private int mOldSelectedPosition;
- private long mOldSelectedRowId;
-
- private ChoiceMode mChoiceMode;
- private int mCheckedItemCount;
- private SparseBooleanArray mCheckStates;
- LongSparseArray<Integer> mCheckedIdStates;
-
- private ContextMenuInfo mContextMenuInfo;
-
- private int mLayoutMode;
- private int mTouchMode;
- private int mLastTouchMode;
- private VelocityTracker mVelocityTracker;
- private final Scroller mScroller;
-
- private EdgeEffectCompat mStartEdge;
- private EdgeEffectCompat mEndEdge;
-
- private OnScrollListener mOnScrollListener;
- private int mLastScrollState;
-
- private View mEmptyView;
-
- private ListItemAccessibilityDelegate mAccessibilityDelegate;
-
- private int mLastAccessibilityScrollEventFromIndex;
- private int mLastAccessibilityScrollEventToIndex;
-
- public interface OnScrollListener {
-
- /**
- * The view is not scrolling. Note navigating the list using the trackball counts as
- * being in the idle state since these transitions are not animated.
- */
- public static int SCROLL_STATE_IDLE = 0;
-
- /**
- * The user is scrolling using touch, and their finger is still on the screen
- */
- public static int SCROLL_STATE_TOUCH_SCROLL = 1;
-
- /**
- * The user had previously been scrolling using touch and had performed a fling. The
- * animation is now coasting to a stop
- */
- public static int SCROLL_STATE_FLING = 2;
-
- /**
- * Callback method to be invoked while the list view or grid view is being scrolled. If the
- * view is being scrolled, this method will be called before the next frame of the scroll is
- * rendered. In particular, it will be called before any calls to
- * {@link android.widget.Adapter#getView(int, View, ViewGroup)}.
- *
- * @param view The view whose scroll state is being reported
- *
- * @param scrollState The current scroll state. One of {@link #SCROLL_STATE_IDLE},
- * {@link #SCROLL_STATE_TOUCH_SCROLL} or {@link #SCROLL_STATE_IDLE}.
- */
- public void onScrollStateChanged(TwoWayView view, int scrollState);
-
- /**
- * Callback method to be invoked when the list or grid has been scrolled. This will be
- * called after the scroll has completed
- * @param view The view whose scroll state is being reported
- * @param firstVisibleItem the index of the first visible cell (ignore if
- * visibleItemCount == 0)
- * @param visibleItemCount the number of visible cells
- * @param totalItemCount the number of items in the list adaptor
- */
- public void onScroll(TwoWayView view, int firstVisibleItem, int visibleItemCount,
- int totalItemCount);
- }
-
- /**
- * A RecyclerListener is used to receive a notification whenever a View is placed
- * inside the RecycleBin's scrap heap. This listener is used to free resources
- * associated to Views placed in the RecycleBin.
- *
- * @see TwoWayView.RecycleBin
- * @see TwoWayView#setRecyclerListener(TwoWayView.RecyclerListener)
- */
- public static interface RecyclerListener {
- /**
- * Indicates that the specified View was moved into the recycler's scrap heap.
- * The view is not displayed on screen any more and any expensive resource
- * associated with the view should be discarded.
- *
- * @param view
- */
- void onMovedToScrapHeap(View view);
- }
-
- public TwoWayView(Context context) {
- this(context, null);
- }
-
- public TwoWayView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public TwoWayView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- mContext = context;
-
- mLayoutMode = LAYOUT_NORMAL;
- mTouchMode = TOUCH_MODE_REST;
- mLastTouchMode = TOUCH_MODE_UNKNOWN;
-
- mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
-
- final ViewConfiguration vc = ViewConfiguration.get(context);
- mTouchSlop = vc.getScaledTouchSlop();
- mMaximumVelocity = vc.getScaledMaximumFlingVelocity();
- mFlingVelocity = vc.getScaledMinimumFlingVelocity();
- mOverscrollDistance = getScaledOverscrollDistance(vc);
-
- mScroller = new Scroller(context);
-
- mIsVertical = true;
-
- mTempRect = new Rect();
-
- mArrowScrollFocusResult = new ArrowScrollFocusResult();
-
- mSelectorPosition = INVALID_POSITION;
-
- mSelectorRect = new Rect();
-
- mResurrectToPosition = INVALID_POSITION;
-
- mNextSelectedPosition = INVALID_POSITION;
- mNextSelectedRowId = INVALID_ROW_ID;
- mSelectedPosition = INVALID_POSITION;
- mSelectedRowId = INVALID_ROW_ID;
- mOldSelectedPosition = INVALID_POSITION;
- mOldSelectedRowId = INVALID_ROW_ID;
-
- mChoiceMode = ChoiceMode.NONE;
-
- mRecycler = new RecycleBin();
-
- mAreAllItemsSelectable = true;
-
- setClickable(true);
- setFocusableInTouchMode(true);
- setWillNotDraw(false);
- setAlwaysDrawnWithCacheEnabled(false);
- setWillNotDraw(false);
- setClipToPadding(false);
-
- ViewCompat.setOverScrollMode(this, ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS);
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TwoWayView, defStyle, 0);
-
- mDrawSelectorOnTop = a.getBoolean(
- R.styleable.TwoWayView_android_drawSelectorOnTop, false);
-
- Drawable d = a.getDrawable(R.styleable.TwoWayView_android_listSelector);
- if (d != null) {
- setSelector(d);
- }
-
- int orientation = a.getInt(R.styleable.TwoWayView_android_orientation, -1);
- if (orientation >= 0) {
- setOrientation(Orientation.values()[orientation]);
- }
-
- int choiceMode = a.getInt(R.styleable.TwoWayView_android_choiceMode, -1);
- if (choiceMode >= 0) {
- setChoiceMode(ChoiceMode.values()[choiceMode]);
- }
-
- a.recycle();
- }
-
- public void setOrientation(Orientation orientation) {
- final boolean isVertical = (orientation == Orientation.VERTICAL);
- if (mIsVertical == isVertical) {
- return;
- }
-
- mIsVertical = isVertical;
-
- resetState();
- mRecycler.clear();
-
- requestLayout();
- }
-
- public Orientation getOrientation() {
- return (mIsVertical ? Orientation.VERTICAL : Orientation.HORIZONTAL);
- }
-
- public void setItemMargin(int itemMargin) {
- if (mItemMargin == itemMargin) {
- return;
- }
-
- mItemMargin = itemMargin;
- requestLayout();
- }
-
- @SuppressWarnings("unused")
- public int getItemMargin() {
- return mItemMargin;
- }
-
- /**
- * Indicates that the views created by the ListAdapter can contain focusable
- * items.
- *
- * @param itemsCanFocus true if items can get focus, false otherwise
- */
- @SuppressWarnings("unused")
- public void setItemsCanFocus(boolean itemsCanFocus) {
- mItemsCanFocus = itemsCanFocus;
- if (!itemsCanFocus) {
- setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
- }
- }
-
- /**
- * @return Whether the views created by the ListAdapter can contain focusable
- * items.
- */
- @SuppressWarnings("unused")
- public boolean getItemsCanFocus() {
- return mItemsCanFocus;
- }
-
- /**
- * Set the listener that will receive notifications every time the list scrolls.
- *
- * @param l the scroll listener
- */
- public void setOnScrollListener(OnScrollListener l) {
- mOnScrollListener = l;
- invokeOnItemScrollListener();
- }
-
- /**
- * Sets the recycler listener to be notified whenever a View is set aside in
- * the recycler for later reuse. This listener can be used to free resources
- * associated to the View.
- *
- * @param l The recycler listener to be notified of views set aside
- * in the recycler.
- *
- * @see TwoWayView.RecycleBin
- * @see TwoWayView.RecyclerListener
- */
- public void setRecyclerListener(RecyclerListener l) {
- mRecycler.mRecyclerListener = l;
- }
-
- /**
- * Controls whether the selection highlight drawable should be drawn on top of the item or
- * behind it.
- *
- * @param drawSelectorOnTop If true, the selector will be drawn on the item it is highlighting.
- * The default is false.
- *
- * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
- */
- @SuppressWarnings("unused")
- public void setDrawSelectorOnTop(boolean drawSelectorOnTop) {
- mDrawSelectorOnTop = drawSelectorOnTop;
- }
-
- /**
- * Set a Drawable that should be used to highlight the currently selected item.
- *
- * @param resID A Drawable resource to use as the selection highlight.
- *
- * @attr ref android.R.styleable#AbsListView_listSelector
- */
- @SuppressWarnings("unused")
- public void setSelector(int resID) {
- setSelector(getResources().getDrawable(resID));
- }
-
- /**
- * Set a Drawable that should be used to highlight the currently selected item.
- *
- * @param selector A Drawable to use as the selection highlight.
- *
- * @attr ref android.R.styleable#AbsListView_listSelector
- */
- public void setSelector(Drawable selector) {
- if (mSelector != null) {
- mSelector.setCallback(null);
- unscheduleDrawable(mSelector);
- }
-
- mSelector = selector;
- Rect padding = new Rect();
- selector.getPadding(padding);
-
- selector.setCallback(this);
- updateSelectorState();
- }
-
- /**
- * Returns the selector {@link android.graphics.drawable.Drawable} that is used to draw the
- * selection in the list.
- *
- * @return the drawable used to display the selector
- */
- @SuppressWarnings("unused")
- public Drawable getSelector() {
- return mSelector;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int getSelectedItemPosition() {
- return mNextSelectedPosition;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public long getSelectedItemId() {
- return mNextSelectedRowId;
- }
-
- /**
- * Returns the number of items currently selected. This will only be valid
- * if the choice mode is not {@link ChoiceMode#NONE} (default).
- *
- * <p>To determine the specific items that are currently selected, use one of
- * the <code>getChecked*</code> methods.
- *
- * @return The number of items currently selected
- *
- * @see #getCheckedItemPosition()
- * @see #getCheckedItemPositions()
- * @see #getCheckedItemIds()
- */
- @SuppressWarnings("unused")
- public int getCheckedItemCount() {
- return mCheckedItemCount;
- }
-
- /**
- * Returns the checked state of the specified position. The result is only
- * valid if the choice mode has been set to {@link ChoiceMode#SINGLE}
- * or {@link ChoiceMode#MULTIPLE}.
- *
- * @param position The item whose checked state to return
- * @return The item's checked state or <code>false</code> if choice mode
- * is invalid
- *
- * @see #setChoiceMode(ChoiceMode)
- */
- public boolean isItemChecked(int position) {
- if (mChoiceMode == ChoiceMode.NONE && mCheckStates != null) {
- return mCheckStates.get(position);
- }
-
- return false;
- }
-
- /**
- * Returns the currently checked item. The result is only valid if the choice
- * mode has been set to {@link ChoiceMode#SINGLE}.
- *
- * @return The position of the currently checked item or
- * {@link #INVALID_POSITION} if nothing is selected
- *
- * @see #setChoiceMode(ChoiceMode)
- */
- public int getCheckedItemPosition() {
- if (mChoiceMode == ChoiceMode.SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
- return mCheckStates.keyAt(0);
- }
-
- return INVALID_POSITION;
- }
-
- /**
- * Returns the set of checked items in the list. The result is only valid if
- * the choice mode has not been set to {@link ChoiceMode#NONE}.
- *
- * @return A SparseBooleanArray which will return true for each call to
- * get(int position) where position is a position in the list,
- * or <code>null</code> if the choice mode is set to
- * {@link ChoiceMode#NONE}.
- */
- public SparseBooleanArray getCheckedItemPositions() {
- if (mChoiceMode != ChoiceMode.NONE) {
- return mCheckStates;
- }
-
- return null;
- }
-
- /**
- * Returns the set of checked items ids. The result is only valid if the
- * choice mode has not been set to {@link ChoiceMode#NONE} and the adapter
- * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true})
- *
- * @return A new array which contains the id of each checked item in the
- * list.
- */
- public long[] getCheckedItemIds() {
- if (mChoiceMode == ChoiceMode.NONE || mCheckedIdStates == null || mAdapter == null) {
- return new long[0];
- }
-
- final LongSparseArray<Integer> idStates = mCheckedIdStates;
- final int count = idStates.size();
- final long[] ids = new long[count];
-
- for (int i = 0; i < count; i++) {
- ids[i] = idStates.keyAt(i);
- }
-
- return ids;
- }
-
- /**
- * Sets the checked state of the specified position. The is only valid if
- * the choice mode has been set to {@link ChoiceMode#SINGLE} or
- * {@link ChoiceMode#MULTIPLE}.
- *
- * @param position The item whose checked state is to be checked
- * @param value The new checked state for the item
- */
- @SuppressWarnings("unused")
- public void setItemChecked(int position, boolean value) {
- if (mChoiceMode == ChoiceMode.NONE) {
- return;
- }
-
- if (mChoiceMode == ChoiceMode.MULTIPLE) {
- boolean oldValue = mCheckStates.get(position);
- mCheckStates.put(position, value);
-
- if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
- if (value) {
- mCheckedIdStates.put(mAdapter.getItemId(position), position);
- } else {
- mCheckedIdStates.delete(mAdapter.getItemId(position));
- }
- }
-
- if (oldValue != value) {
- if (value) {
- mCheckedItemCount++;
- } else {
- mCheckedItemCount--;
- }
- }
- } else {
- boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
-
- // Clear all values if we're checking something, or unchecking the currently
- // selected item
- if (value || isItemChecked(position)) {
- mCheckStates.clear();
-
- if (updateIds) {
- mCheckedIdStates.clear();
- }
- }
-
- // This may end up selecting the value we just cleared but this way
- // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
- if (value) {
- mCheckStates.put(position, true);
-
- if (updateIds) {
- mCheckedIdStates.put(mAdapter.getItemId(position), position);
- }
-
- mCheckedItemCount = 1;
- } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
- mCheckedItemCount = 0;
- }
- }
-
- // Do not generate a data change while we are in the layout phase
- if (!mInLayout && !mBlockLayoutRequests) {
- mDataChanged = true;
- rememberSyncState();
- requestLayout();
- }
- }
-
- /**
- * Clear any choices previously set
- */
- @SuppressWarnings("unused")
- public void clearChoices() {
- if (mCheckStates != null) {
- mCheckStates.clear();
- }
-
- if (mCheckedIdStates != null) {
- mCheckedIdStates.clear();
- }
-
- mCheckedItemCount = 0;
- }
-
- /**
- * @see #setChoiceMode(ChoiceMode)
- *
- * @return The current choice mode
- */
- @SuppressWarnings("unused")
- public ChoiceMode getChoiceMode() {
- return mChoiceMode;
- }
-
- /**
- * Defines the choice behavior for the List. By default, Lists do not have any choice behavior
- * ({@link ChoiceMode#NONE}). By setting the choiceMode to {@link ChoiceMode#SINGLE}, the
- * List allows up to one item to be in a chosen state. By setting the choiceMode to
- * {@link ChoiceMode#MULTIPLE}, the list allows any number of items to be chosen.
- *
- * @param choiceMode One of {@link ChoiceMode#NONE}, {@link ChoiceMode#SINGLE}, or
- * {@link ChoiceMode#MULTIPLE}
- */
- public void setChoiceMode(ChoiceMode choiceMode) {
- mChoiceMode = choiceMode;
-
- if (mChoiceMode != ChoiceMode.NONE) {
- if (mCheckStates == null) {
- mCheckStates = new SparseBooleanArray();
- }
-
- if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
- mCheckedIdStates = new LongSparseArray<Integer>();
- }
- }
- }
-
- @Override
- public ListAdapter getAdapter() {
- return mAdapter;
- }
-
- @Override
- public void setAdapter(ListAdapter adapter) {
- if (mAdapter != null && mDataSetObserver != null) {
- mAdapter.unregisterDataSetObserver(mDataSetObserver);
- }
-
- resetState();
- mRecycler.clear();
-
- mAdapter = adapter;
- mDataChanged = true;
-
- mOldSelectedPosition = INVALID_POSITION;
- mOldSelectedRowId = INVALID_ROW_ID;
-
- if (mCheckStates != null) {
- mCheckStates.clear();
- }
-
- if (mCheckedIdStates != null) {
- mCheckedIdStates.clear();
- }
-
- if (mAdapter != null) {
- mOldItemCount = mItemCount;
- mItemCount = adapter.getCount();
-
- mDataSetObserver = new AdapterDataSetObserver();
- mAdapter.registerDataSetObserver(mDataSetObserver);
-
- mRecycler.setViewTypeCount(adapter.getViewTypeCount());
-
- mHasStableIds = adapter.hasStableIds();
- mAreAllItemsSelectable = adapter.areAllItemsEnabled();
-
- if (mChoiceMode != ChoiceMode.NONE && mHasStableIds && mCheckedIdStates == null) {
- mCheckedIdStates = new LongSparseArray<Integer>();
- }
-
- final int position = lookForSelectablePosition(0);
- setSelectedPositionInt(position);
- setNextSelectedPositionInt(position);
-
- if (mItemCount == 0) {
- checkSelectionChanged();
- }
- } else {
- mItemCount = 0;
- mHasStableIds = false;
- mAreAllItemsSelectable = true;
-
- checkSelectionChanged();
- }
-
- checkFocus();
- requestLayout();
- }
-
- @Override
- public int getFirstVisiblePosition() {
- return mFirstPosition;
- }
-
- @Override
- public int getLastVisiblePosition() {
- return mFirstPosition + getChildCount() - 1;
- }
-
- @Override
- public int getCount() {
- return mItemCount;
- }
-
- @Override
- public int getPositionForView(View view) {
- View child = view;
- try {
- View v;
- while (!(v = (View) child.getParent()).equals(this)) {
- child = v;
- }
- } catch (ClassCastException e) {
- // We made it up to the window without find this list view
- return INVALID_POSITION;
- }
-
- // Search the children for the list item
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- if (getChildAt(i).equals(child)) {
- return mFirstPosition + i;
- }
- }
-
- // Child not found!
- return INVALID_POSITION;
- }
-
- @Override
- public void getFocusedRect(Rect r) {
- View view = getSelectedView();
-
- if (view != null && view.getParent() == this) {
- // The focused rectangle of the selected view offset into the
- // coordinate space of this view.
- view.getFocusedRect(r);
- offsetDescendantRectToMyCoords(view, r);
- } else {
- super.getFocusedRect(r);
- }
- }
-
- @Override
- protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
-
- if (gainFocus && mSelectedPosition < 0 && !isInTouchMode()) {
- if (!mIsAttached && mAdapter != null) {
- // Data may have changed while we were detached and it's valid
- // to change focus while detached. Refresh so we don't die.
- mDataChanged = true;
- mOldItemCount = mItemCount;
- mItemCount = mAdapter.getCount();
- }
-
- resurrectSelection();
- }
-
- final ListAdapter adapter = mAdapter;
- int closetChildIndex = INVALID_POSITION;
- int closestChildStart = 0;
-
- if (adapter != null && gainFocus && previouslyFocusedRect != null) {
- previouslyFocusedRect.offset(getScrollX(), getScrollY());
-
- // Don't cache the result of getChildCount or mFirstPosition here,
- // it could change in layoutChildren.
- if (adapter.getCount() < getChildCount() + mFirstPosition) {
- mLayoutMode = LAYOUT_NORMAL;
- layoutChildren();
- }
-
- // Figure out which item should be selected based on previously
- // focused rect.
- Rect otherRect = mTempRect;
- int minDistance = Integer.MAX_VALUE;
- final int childCount = getChildCount();
- final int firstPosition = mFirstPosition;
-
- for (int i = 0; i < childCount; i++) {
- // Only consider selectable views
- if (!adapter.isEnabled(firstPosition + i)) {
- continue;
- }
-
- View other = getChildAt(i);
- other.getDrawingRect(otherRect);
- offsetDescendantRectToMyCoords(other, otherRect);
- int distance = getDistance(previouslyFocusedRect, otherRect, direction);
-
- if (distance < minDistance) {
- minDistance = distance;
- closetChildIndex = i;
- closestChildStart = getChildStartEdge(other);
- }
- }
- }
-
- if (closetChildIndex >= 0) {
- setSelectionFromOffset(closetChildIndex + mFirstPosition, closestChildStart);
- } else {
- requestLayout();
- }
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- final ViewTreeObserver treeObserver = getViewTreeObserver();
- treeObserver.addOnTouchModeChangeListener(this);
-
- if (mAdapter != null && mDataSetObserver == null) {
- mDataSetObserver = new AdapterDataSetObserver();
- mAdapter.registerDataSetObserver(mDataSetObserver);
-
- // Data may have changed while we were detached. Refresh.
- mDataChanged = true;
- mOldItemCount = mItemCount;
- mItemCount = mAdapter.getCount();
- }
-
- mIsAttached = true;
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- // Detach any view left in the scrap heap
- mRecycler.clear();
-
- final ViewTreeObserver treeObserver = getViewTreeObserver();
- treeObserver.removeOnTouchModeChangeListener(this);
-
- if (mAdapter != null) {
- mAdapter.unregisterDataSetObserver(mDataSetObserver);
- mDataSetObserver = null;
- }
-
- if (mPerformClick != null) {
- removeCallbacks(mPerformClick);
- }
-
- if (mTouchModeReset != null) {
- removeCallbacks(mTouchModeReset);
- mTouchModeReset.run();
- }
-
- finishSmoothScrolling();
-
- mIsAttached = false;
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- super.onWindowFocusChanged(hasWindowFocus);
-
- final int touchMode = isInTouchMode() ? TOUCH_MODE_ON : TOUCH_MODE_OFF;
-
- if (!hasWindowFocus) {
- if (!mScroller.isFinished()) {
- finishSmoothScrolling();
- if (mOverScroll != 0) {
- mOverScroll = 0;
- finishEdgeGlows();
- invalidate();
- }
- }
-
- if (touchMode == TOUCH_MODE_OFF) {
- // Remember the last selected element
- mResurrectToPosition = mSelectedPosition;
- }
- } else {
- // If we changed touch mode since the last time we had focus
- if (touchMode != mLastTouchMode && mLastTouchMode != TOUCH_MODE_UNKNOWN) {
- // If we come back in trackball mode, we bring the selection back
- if (touchMode == TOUCH_MODE_OFF) {
- // This will trigger a layout
- resurrectSelection();
-
- // If we come back in touch mode, then we want to hide the selector
- } else {
- hideSelector();
- mLayoutMode = LAYOUT_NORMAL;
- layoutChildren();
- }
- }
- }
-
- mLastTouchMode = touchMode;
- }
-
- @Override
- protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
- boolean needsInvalidate = false;
-
- if (mIsVertical && mOverScroll != scrollY) {
- onScrollChanged(getScrollX(), scrollY, getScrollX(), mOverScroll);
- mOverScroll = scrollY;
- needsInvalidate = true;
- } else if (!mIsVertical && mOverScroll != scrollX) {
- onScrollChanged(scrollX, getScrollY(), mOverScroll, getScrollY());
- mOverScroll = scrollX;
- needsInvalidate = true;
- }
-
- if (needsInvalidate) {
- invalidate();
- awakenScrollbarsInternal();
- }
- }
-
- @TargetApi(9)
- private boolean overScrollByInternal(int deltaX, int deltaY,
- int scrollX, int scrollY,
- int scrollRangeX, int scrollRangeY,
- int maxOverScrollX, int maxOverScrollY,
- boolean isTouchEvent) {
- if (Build.VERSION.SDK_INT < 9) {
- return false;
- }
-
- return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
- scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
- }
-
- @Override
- @TargetApi(9)
- public void setOverScrollMode(int mode) {
- if (Build.VERSION.SDK_INT < 9) {
- return;
- }
-
- if (mode != ViewCompat.OVER_SCROLL_NEVER) {
- if (mStartEdge == null) {
- Context context = getContext();
-
- mStartEdge = new EdgeEffectCompat(context);
- mEndEdge = new EdgeEffectCompat(context);
- }
- } else {
- mStartEdge = null;
- mEndEdge = null;
- }
-
- super.setOverScrollMode(mode);
- }
-
- public int pointToPosition(int x, int y) {
- Rect frame = mTouchFrame;
- if (frame == null) {
- mTouchFrame = new Rect();
- frame = mTouchFrame;
- }
-
- final int count = getChildCount();
- for (int i = count - 1; i >= 0; i--) {
- final View child = getChildAt(i);
-
- if (child.getVisibility() == View.VISIBLE) {
- child.getHitRect(frame);
-
- if (frame.contains(x, y)) {
- return mFirstPosition + i;
- }
- }
- }
- return INVALID_POSITION;
- }
-
- @Override
- protected float getTopFadingEdgeStrength() {
- if (!mIsVertical) {
- return 0f;
- }
-
- final float fadingEdge = super.getTopFadingEdgeStrength();
-
- final int childCount = getChildCount();
- if (childCount == 0) {
- return fadingEdge;
- } else {
- if (mFirstPosition > 0) {
- return 1.0f;
- }
-
- final int top = getChildAt(0).getTop();
- final int paddingTop = getPaddingTop();
-
- final float length = (float) getVerticalFadingEdgeLength();
-
- return (top < paddingTop ? (float) -(top - paddingTop) / length : fadingEdge);
- }
- }
-
- @Override
- protected float getBottomFadingEdgeStrength() {
- if (!mIsVertical) {
- return 0f;
- }
-
- final float fadingEdge = super.getBottomFadingEdgeStrength();
-
- final int childCount = getChildCount();
- if (childCount == 0) {
- return fadingEdge;
- } else {
- if (mFirstPosition + childCount - 1 < mItemCount - 1) {
- return 1.0f;
- }
-
- final int bottom = getChildAt(childCount - 1).getBottom();
- final int paddingBottom = getPaddingBottom();
-
- final int height = getHeight();
- final float length = (float) getVerticalFadingEdgeLength();
-
- return (bottom > height - paddingBottom ?
- (float) (bottom - height + paddingBottom) / length : fadingEdge);
- }
- }
-
- @Override
- protected float getLeftFadingEdgeStrength() {
- if (mIsVertical) {
- return 0f;
- }
-
- final float fadingEdge = super.getLeftFadingEdgeStrength();
-
- final int childCount = getChildCount();
- if (childCount == 0) {
- return fadingEdge;
- } else {
- if (mFirstPosition > 0) {
- return 1.0f;
- }
-
- final int left = getChildAt(0).getLeft();
- final int paddingLeft = getPaddingLeft();
-
- final float length = (float) getHorizontalFadingEdgeLength();
-
- return (left < paddingLeft ? (float) -(left - paddingLeft) / length : fadingEdge);
- }
- }
-
- @Override
- protected float getRightFadingEdgeStrength() {
- if (mIsVertical) {
- return 0f;
- }
-
- final float fadingEdge = super.getRightFadingEdgeStrength();
-
- final int childCount = getChildCount();
- if (childCount == 0) {
- return fadingEdge;
- } else {
- if (mFirstPosition + childCount - 1 < mItemCount - 1) {
- return 1.0f;
- }
-
- final int right = getChildAt(childCount - 1).getRight();
- final int paddingRight = getPaddingRight();
-
- final int width = getWidth();
- final float length = (float) getHorizontalFadingEdgeLength();
-
- return (right > width - paddingRight ?
- (float) (right - width + paddingRight) / length : fadingEdge);
- }
- }
-
- @Override
- protected int computeVerticalScrollExtent() {
- final int count = getChildCount();
- if (count == 0) {
- return 0;
- }
-
- int extent = count * 100;
-
- View child = getChildAt(0);
- final int childTop = child.getTop();
-
- int childHeight = child.getHeight();
- if (childHeight > 0) {
- extent += (childTop * 100) / childHeight;
- }
-
- child = getChildAt(count - 1);
- final int childBottom = child.getBottom();
-
- childHeight = child.getHeight();
- if (childHeight > 0) {
- extent -= ((childBottom - getHeight()) * 100) / childHeight;
- }
-
- return extent;
- }
-
- @Override
- protected int computeHorizontalScrollExtent() {
- final int count = getChildCount();
- if (count == 0) {
- return 0;
- }
-
- int extent = count * 100;
-
- View child = getChildAt(0);
- final int childLeft = child.getLeft();
-
- int childWidth = child.getWidth();
- if (childWidth > 0) {
- extent += (childLeft * 100) / childWidth;
- }
-
- child = getChildAt(count - 1);
- final int childRight = child.getRight();
-
- childWidth = child.getWidth();
- if (childWidth > 0) {
- extent -= ((childRight - getWidth()) * 100) / childWidth;
- }
-
- return extent;
- }
-
- @Override
- protected int computeVerticalScrollOffset() {
- final int firstPosition = mFirstPosition;
- final int childCount = getChildCount();
-
- if (firstPosition < 0 || childCount == 0) {
- return 0;
- }
-
- final View child = getChildAt(0);
- final int childTop = child.getTop();
-
- int childHeight = child.getHeight();
- if (childHeight > 0) {
- return Math.max(firstPosition * 100 - (childTop * 100) / childHeight, 0);
- }
-
- return 0;
- }
-
- @Override
- protected int computeHorizontalScrollOffset() {
- final int firstPosition = mFirstPosition;
- final int childCount = getChildCount();
-
- if (firstPosition < 0 || childCount == 0) {
- return 0;
- }
-
- final View child = getChildAt(0);
- final int childLeft = child.getLeft();
-
- int childWidth = child.getWidth();
- if (childWidth > 0) {
- return Math.max(firstPosition * 100 - (childLeft * 100) / childWidth, 0);
- }
-
- return 0;
- }
-
- @Override
- protected int computeVerticalScrollRange() {
- int result = Math.max(mItemCount * 100, 0);
-
- if (mIsVertical && mOverScroll != 0) {
- // Compensate for overscroll
- result += Math.abs((int) ((float) mOverScroll / getHeight() * mItemCount * 100));
- }
-
- return result;
- }
-
- @Override
- protected int computeHorizontalScrollRange() {
- int result = Math.max(mItemCount * 100, 0);
-
- if (!mIsVertical && mOverScroll != 0) {
- // Compensate for overscroll
- result += Math.abs((int) ((float) mOverScroll / getWidth() * mItemCount * 100));
- }
-
- return result;
- }
-
- @Override
- public boolean showContextMenuForChild(View originalView) {
- final int longPressPosition = getPositionForView(originalView);
- if (longPressPosition >= 0) {
- final long longPressId = mAdapter.getItemId(longPressPosition);
- boolean handled = false;
-
- OnItemLongClickListener listener = getOnItemLongClickListener();
- if (listener != null) {
- handled = listener.onItemLongClick(TwoWayView.this, originalView,
- longPressPosition, longPressId);
- }
-
- if (!handled) {
- mContextMenuInfo = createContextMenuInfo(
- getChildAt(longPressPosition - mFirstPosition),
- longPressPosition, longPressId);
-
- handled = super.showContextMenuForChild(originalView);
- }
-
- return handled;
- }
-
- return false;
- }
-
- @Override
- public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
- if (disallowIntercept) {
- recycleVelocityTracker();
- }
-
- super.requestDisallowInterceptTouchEvent(disallowIntercept);
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (!mIsAttached || mAdapter == null) {
- return false;
- }
-
- final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- initOrResetVelocityTracker();
- mVelocityTracker.addMovement(ev);
-
- mScroller.abortAnimation();
- if (mPositionScroller != null) {
- mPositionScroller.stop();
- }
-
- final float x = ev.getX();
- final float y = ev.getY();
-
- mLastTouchPos = (mIsVertical ? y : x);
-
- final int motionPosition = findMotionRowOrColumn((int) mLastTouchPos);
-
- mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
- mTouchRemainderPos = 0;
-
- if (mTouchMode == TOUCH_MODE_FLINGING) {
- return true;
- } else if (motionPosition >= 0) {
- mMotionPosition = motionPosition;
- mTouchMode = TOUCH_MODE_DOWN;
- }
-
- break;
-
- case MotionEvent.ACTION_MOVE: {
- if (mTouchMode != TOUCH_MODE_DOWN) {
- break;
- }
-
- initVelocityTrackerIfNotExists();
- mVelocityTracker.addMovement(ev);
-
- final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
- if (index < 0) {
- Log.e(LOGTAG, "onInterceptTouchEvent could not find pointer with id " +
- mActivePointerId + " - did TwoWayView receive an inconsistent " +
- "event stream?");
- return false;
- }
-
- final float pos;
- if (mIsVertical) {
- pos = MotionEventCompat.getY(ev, index);
- } else {
- pos = MotionEventCompat.getX(ev, index);
- }
-
- final float diff = pos - mLastTouchPos + mTouchRemainderPos;
- final int delta = (int) diff;
- mTouchRemainderPos = diff - delta;
-
- if (maybeStartScrolling(delta)) {
- return true;
- }
-
- break;
- }
-
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- mActivePointerId = INVALID_POINTER;
- mTouchMode = TOUCH_MODE_REST;
- recycleVelocityTracker();
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
-
- break;
- }
-
- return false;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- if (!isEnabled()) {
- // A disabled view that is clickable still consumes the touch
- // events, it just doesn't respond to them.
- return isClickable() || isLongClickable();
- }
-
- if (!mIsAttached || mAdapter == null) {
- return false;
- }
-
- boolean needsInvalidate = false;
-
- initVelocityTrackerIfNotExists();
- mVelocityTracker.addMovement(ev);
-
- final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
- switch (action) {
- case MotionEvent.ACTION_DOWN: {
- if (mDataChanged) {
- break;
- }
-
- mVelocityTracker.clear();
- mScroller.abortAnimation();
- if (mPositionScroller != null) {
- mPositionScroller.stop();
- }
-
- final float x = ev.getX();
- final float y = ev.getY();
-
- mLastTouchPos = (mIsVertical ? y : x);
-
- int motionPosition = pointToPosition((int) x, (int) y);
-
- mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
- mTouchRemainderPos = 0;
-
- if (mDataChanged) {
- break;
- }
-
- if (mTouchMode == TOUCH_MODE_FLINGING) {
- mTouchMode = TOUCH_MODE_DRAGGING;
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
- motionPosition = findMotionRowOrColumn((int) mLastTouchPos);
- } else if (mMotionPosition >= 0 && mAdapter.isEnabled(mMotionPosition)) {
- mTouchMode = TOUCH_MODE_DOWN;
- triggerCheckForTap();
- }
-
- mMotionPosition = motionPosition;
-
- break;
- }
-
- case MotionEvent.ACTION_MOVE: {
- final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
- if (index < 0) {
- Log.e(LOGTAG, "onInterceptTouchEvent could not find pointer with id " +
- mActivePointerId + " - did TwoWayView receive an inconsistent " +
- "event stream?");
- return false;
- }
-
- final float pos;
- if (mIsVertical) {
- pos = MotionEventCompat.getY(ev, index);
- } else {
- pos = MotionEventCompat.getX(ev, index);
- }
-
- if (mDataChanged) {
- // Re-sync everything if data has been changed
- // since the scroll operation can query the adapter.
- layoutChildren();
- }
-
- final float diff = pos - mLastTouchPos + mTouchRemainderPos;
- final int delta = (int) diff;
- mTouchRemainderPos = diff - delta;
-
- switch (mTouchMode) {
- case TOUCH_MODE_DOWN:
- case TOUCH_MODE_TAP:
- case TOUCH_MODE_DONE_WAITING:
- // Check if we have moved far enough that it looks more like a
- // scroll than a tap
- maybeStartScrolling(delta);
- break;
-
- case TOUCH_MODE_DRAGGING:
- case TOUCH_MODE_OVERSCROLL:
- mLastTouchPos = pos;
- maybeScroll(delta);
- break;
- }
-
- break;
- }
-
- case MotionEvent.ACTION_CANCEL:
- cancelCheckForTap();
- mTouchMode = TOUCH_MODE_REST;
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
-
- setPressed(false);
- View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
- if (motionView != null) {
- motionView.setPressed(false);
- }
-
- if (mStartEdge != null && mEndEdge != null) {
- needsInvalidate = mStartEdge.onRelease() | mEndEdge.onRelease();
- }
-
- recycleVelocityTracker();
-
- break;
-
- case MotionEvent.ACTION_UP: {
- switch (mTouchMode) {
- case TOUCH_MODE_DOWN:
- case TOUCH_MODE_TAP:
- case TOUCH_MODE_DONE_WAITING: {
- final int motionPosition = mMotionPosition;
- final View child = getChildAt(motionPosition - mFirstPosition);
-
- final float x = ev.getX();
- final float y = ev.getY();
-
- final boolean inList;
- if (mIsVertical) {
- inList = x > getPaddingLeft() && x < getWidth() - getPaddingRight();
- } else {
- inList = y > getPaddingTop() && y < getHeight() - getPaddingBottom();
- }
-
- if (child != null && !child.hasFocusable() && inList) {
- if (mTouchMode != TOUCH_MODE_DOWN) {
- child.setPressed(false);
- }
-
- if (mPerformClick == null) {
- mPerformClick = new PerformClick();
- }
-
- final PerformClick performClick = mPerformClick;
- performClick.mClickMotionPosition = motionPosition;
- performClick.rememberWindowAttachCount();
-
- mResurrectToPosition = motionPosition;
-
- if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
- if (mTouchMode == TOUCH_MODE_DOWN) {
- cancelCheckForTap();
- } else {
- cancelCheckForLongPress();
- }
-
- mLayoutMode = LAYOUT_NORMAL;
-
- if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
- mTouchMode = TOUCH_MODE_TAP;
-
- setPressed(true);
- positionSelector(mMotionPosition, child);
- child.setPressed(true);
-
- if (mSelector != null) {
- Drawable d = mSelector.getCurrent();
- if (d != null && d instanceof TransitionDrawable) {
- ((TransitionDrawable) d).resetTransition();
- }
- }
-
- if (mTouchModeReset != null) {
- removeCallbacks(mTouchModeReset);
- }
-
- mTouchModeReset = new Runnable() {
- @Override
- public void run() {
- mTouchMode = TOUCH_MODE_REST;
-
- setPressed(false);
- child.setPressed(false);
-
- if (!mDataChanged) {
- performClick.run();
- }
-
- mTouchModeReset = null;
- }
- };
-
- postDelayed(mTouchModeReset,
- ViewConfiguration.getPressedStateDuration());
- } else {
- mTouchMode = TOUCH_MODE_REST;
- updateSelectorState();
- }
- } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
- performClick.run();
- }
- }
-
- mTouchMode = TOUCH_MODE_REST;
-
- finishSmoothScrolling();
- updateSelectorState();
-
- break;
- }
-
- case TOUCH_MODE_DRAGGING:
- if (contentFits()) {
- mTouchMode = TOUCH_MODE_REST;
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
- break;
- }
-
- mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
-
- final float velocity;
- if (mIsVertical) {
- velocity = VelocityTrackerCompat.getYVelocity(mVelocityTracker,
- mActivePointerId);
- } else {
- velocity = VelocityTrackerCompat.getXVelocity(mVelocityTracker,
- mActivePointerId);
- }
-
- if (Math.abs(velocity) >= mFlingVelocity) {
- mTouchMode = TOUCH_MODE_FLINGING;
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
-
- mScroller.fling(0, 0,
- (int) (mIsVertical ? 0 : velocity),
- (int) (mIsVertical ? velocity : 0),
- (mIsVertical ? 0 : Integer.MIN_VALUE),
- (mIsVertical ? 0 : Integer.MAX_VALUE),
- (mIsVertical ? Integer.MIN_VALUE : 0),
- (mIsVertical ? Integer.MAX_VALUE : 0));
-
- mLastTouchPos = 0;
- needsInvalidate = true;
- } else {
- mTouchMode = TOUCH_MODE_REST;
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
- }
-
- break;
-
- case TOUCH_MODE_OVERSCROLL:
- mTouchMode = TOUCH_MODE_REST;
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
- break;
- }
-
- cancelCheckForTap();
- cancelCheckForLongPress();
- setPressed(false);
-
- if (mStartEdge != null && mEndEdge != null) {
- needsInvalidate |= mStartEdge.onRelease() | mEndEdge.onRelease();
- }
-
- recycleVelocityTracker();
-
- break;
- }
- }
-
- if (needsInvalidate) {
- ViewCompat.postInvalidateOnAnimation(this);
- }
-
- return true;
- }
-
- @Override
- public void onTouchModeChanged(boolean isInTouchMode) {
- if (isInTouchMode) {
- // Get rid of the selection when we enter touch mode
- hideSelector();
-
- // Layout, but only if we already have done so previously.
- // (Otherwise may clobber a LAYOUT_SYNC layout that was requested to restore
- // state.)
- if (getWidth() > 0 && getHeight() > 0 && getChildCount() > 0) {
- layoutChildren();
- }
-
- updateSelectorState();
- } else {
- final int touchMode = mTouchMode;
- if (touchMode == TOUCH_MODE_OVERSCROLL) {
- finishSmoothScrolling();
- if (mOverScroll != 0) {
- mOverScroll = 0;
- finishEdgeGlows();
- invalidate();
- }
- }
- }
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- return handleKeyEvent(keyCode, 1, event);
- }
-
- @Override
- public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
- return handleKeyEvent(keyCode, repeatCount, event);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- return handleKeyEvent(keyCode, 1, event);
- }
-
- @Override
- public void sendAccessibilityEvent(int eventType) {
- // Since this class calls onScrollChanged even if the mFirstPosition and the
- // child count have not changed we will avoid sending duplicate accessibility
- // events.
- if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
- final int firstVisiblePosition = getFirstVisiblePosition();
- final int lastVisiblePosition = getLastVisiblePosition();
-
- if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
- && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
- return;
- } else {
- mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
- mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
- }
- }
-
- super.sendAccessibilityEvent(eventType);
- }
-
- @Override
- @TargetApi(14)
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(TwoWayView.class.getName());
- }
-
- @Override
- @TargetApi(14)
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(TwoWayView.class.getName());
-
- AccessibilityNodeInfoCompat infoCompat = new AccessibilityNodeInfoCompat(info);
-
- if (isEnabled()) {
- if (getFirstVisiblePosition() > 0) {
- infoCompat.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
- }
-
- if (getLastVisiblePosition() < getCount() - 1) {
- infoCompat.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
- }
- }
- }
-
- @Override
- @TargetApi(16)
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (super.performAccessibilityAction(action, arguments)) {
- return true;
- }
-
- switch (action) {
- case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
- if (isEnabled() && getLastVisiblePosition() < getCount() - 1) {
- // TODO: Use some form of smooth scroll instead
- scrollListItemsBy(getAvailableSize());
- return true;
- }
- return false;
-
- case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
- if (isEnabled() && mFirstPosition > 0) {
- // TODO: Use some form of smooth scroll instead
- scrollListItemsBy(-getAvailableSize());
- return true;
- }
- return false;
- }
-
- return false;
- }
-
- /**
- * Return true if child is an ancestor of parent, (or equal to the parent).
- */
- private boolean isViewAncestorOf(View child, View parent) {
- if (child == parent) {
- return true;
- }
-
- final ViewParent theParent = child.getParent();
-
- return (theParent instanceof ViewGroup) &&
- isViewAncestorOf((View) theParent, parent);
- }
-
- private void forceValidFocusDirection(int direction) {
- if (mIsVertical && direction != View.FOCUS_UP && direction != View.FOCUS_DOWN) {
- throw new IllegalArgumentException("Focus direction must be one of"
- + " {View.FOCUS_UP, View.FOCUS_DOWN} for vertical orientation");
- } else if (!mIsVertical && direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT) {
- throw new IllegalArgumentException("Focus direction must be one of"
- + " {View.FOCUS_LEFT, View.FOCUS_RIGHT} for vertical orientation");
- }
- }
-
- private void forceValidInnerFocusDirection(int direction) {
- if (mIsVertical && direction != View.FOCUS_LEFT && direction != View.FOCUS_RIGHT) {
- throw new IllegalArgumentException("Direction must be one of"
- + " {View.FOCUS_LEFT, View.FOCUS_RIGHT} for vertical orientation");
- } else if (!mIsVertical && direction != View.FOCUS_UP && direction != View.FOCUS_DOWN) {
- throw new IllegalArgumentException("direction must be one of"
- + " {View.FOCUS_UP, View.FOCUS_DOWN} for horizontal orientation");
- }
- }
-
- /**
- * Scrolls up or down by the number of items currently present on screen.
- *
- * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
- * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
- * current view orientation.
- *
- * @return whether selection was moved
- */
- boolean pageScroll(int direction) {
- forceValidFocusDirection(direction);
-
- boolean forward = false;
- int nextPage = -1;
-
- if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT) {
- nextPage = Math.max(0, mSelectedPosition - getChildCount() - 1);
- } else if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) {
- nextPage = Math.min(mItemCount - 1, mSelectedPosition + getChildCount() - 1);
- forward = true;
- }
-
- if (nextPage < 0) {
- return false;
- }
-
- final int position = lookForSelectablePosition(nextPage, forward);
- if (position >= 0) {
- mLayoutMode = LAYOUT_SPECIFIC;
- mSpecificStart = getStartEdge() + getFadingEdgeLength();
-
- if (forward && position > mItemCount - getChildCount()) {
- mLayoutMode = LAYOUT_FORCE_BOTTOM;
- }
-
- if (!forward && position < getChildCount()) {
- mLayoutMode = LAYOUT_FORCE_TOP;
- }
-
- setSelectionInt(position);
- invokeOnItemScrollListener();
-
- if (!awakenScrollbarsInternal()) {
- invalidate();
- }
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Go to the last or first item if possible (not worrying about panning across or navigating
- * within the internal focus of the currently selected item.)
- *
- * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
- * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
- * current view orientation.
- *
- * @return whether selection was moved
- */
- boolean fullScroll(int direction) {
- forceValidFocusDirection(direction);
-
- boolean moved = false;
- if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT) {
- if (mSelectedPosition != 0) {
- int position = lookForSelectablePosition(0, true);
- if (position >= 0) {
- mLayoutMode = LAYOUT_FORCE_TOP;
- setSelectionInt(position);
- invokeOnItemScrollListener();
- }
-
- moved = true;
- }
- } else if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) {
- if (mSelectedPosition < mItemCount - 1) {
- int position = lookForSelectablePosition(mItemCount - 1, true);
- if (position >= 0) {
- mLayoutMode = LAYOUT_FORCE_BOTTOM;
- setSelectionInt(position);
- invokeOnItemScrollListener();
- }
-
- moved = true;
- }
- }
-
- if (moved && !awakenScrollbarsInternal()) {
- awakenScrollbarsInternal();
- invalidate();
- }
-
- return moved;
- }
-
- /**
- * To avoid horizontal/vertical focus searches changing the selected item,
- * we manually focus search within the selected item (as applicable), and
- * prevent focus from jumping to something within another item.
- *
- * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
- * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
- * current view orientation.
- *
- * @return Whether this consumes the key event.
- */
- private boolean handleFocusWithinItem(int direction) {
- forceValidInnerFocusDirection(direction);
-
- final int numChildren = getChildCount();
-
- if (mItemsCanFocus && numChildren > 0 && mSelectedPosition != INVALID_POSITION) {
- final View selectedView = getSelectedView();
-
- if (selectedView != null && selectedView.hasFocus() &&
- selectedView instanceof ViewGroup) {
-
- final View currentFocus = selectedView.findFocus();
- final View nextFocus = FocusFinder.getInstance().findNextFocus(
- (ViewGroup) selectedView, currentFocus, direction);
-
- if (nextFocus != null) {
- // Do the math to get interesting rect in next focus' coordinates
- currentFocus.getFocusedRect(mTempRect);
- offsetDescendantRectToMyCoords(currentFocus, mTempRect);
- offsetRectIntoDescendantCoords(nextFocus, mTempRect);
-
- if (nextFocus.requestFocus(direction, mTempRect)) {
- return true;
- }
- }
-
- // We are blocking the key from being handled (by returning true)
- // if the global result is going to be some other view within this
- // list. This is to achieve the overall goal of having horizontal/vertical
- // d-pad navigation remain in the current item depending on the current
- // orientation in this view.
- final View globalNextFocus = FocusFinder.getInstance().findNextFocus(
- (ViewGroup) getRootView(), currentFocus, direction);
-
- if (globalNextFocus != null) {
- return isViewAncestorOf(globalNextFocus, this);
- }
- }
- }
-
- return false;
- }
-
- /**
- * Scrolls to the next or previous item if possible.
- *
- * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
- * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
- * current view orientation.
- *
- * @return whether selection was moved
- */
- private boolean arrowScroll(int direction) {
- forceValidFocusDirection(direction);
-
- try {
- mInLayout = true;
-
- final boolean handled = arrowScrollImpl(direction);
- if (handled) {
- playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
- }
-
- return handled;
- } finally {
- mInLayout = false;
- }
- }
-
- /**
- * When selection changes, it is possible that the previously selected or the
- * next selected item will change its size. If so, we need to offset some folks,
- * and re-layout the items as appropriate.
- *
- * @param selectedView The currently selected view (before changing selection).
- * should be <code>null</code> if there was no previous selection.
- * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
- * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
- * current view orientation.
- * @param newSelectedPosition The position of the next selection.
- * @param newFocusAssigned whether new focus was assigned. This matters because
- * when something has focus, we don't want to show selection (ugh).
- */
- private void handleNewSelectionChange(View selectedView, int direction, int newSelectedPosition,
- boolean newFocusAssigned) {
- forceValidFocusDirection(direction);
-
- if (newSelectedPosition == INVALID_POSITION) {
- throw new IllegalArgumentException("newSelectedPosition needs to be valid");
- }
-
- // Whether or not we are moving down/right or up/left, we want to preserve the
- // top/left of whatever view is at the start:
- // - moving down/right: the view that had selection
- // - moving up/left: the view that is getting selection
- final int selectedIndex = mSelectedPosition - mFirstPosition;
- final int nextSelectedIndex = newSelectedPosition - mFirstPosition;
- int startViewIndex, endViewIndex;
- boolean topSelected = false;
- View startView;
- View endView;
-
- if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT) {
- startViewIndex = nextSelectedIndex;
- endViewIndex = selectedIndex;
- startView = getChildAt(startViewIndex);
- endView = selectedView;
- topSelected = true;
- } else {
- startViewIndex = selectedIndex;
- endViewIndex = nextSelectedIndex;
- startView = selectedView;
- endView = getChildAt(endViewIndex);
- }
-
- final int numChildren = getChildCount();
-
- // start with top view: is it changing size?
- if (startView != null) {
- startView.setSelected(!newFocusAssigned && topSelected);
- measureAndAdjustDown(startView, startViewIndex, numChildren);
- }
-
- // is the bottom view changing size?
- if (endView != null) {
- endView.setSelected(!newFocusAssigned && !topSelected);
- measureAndAdjustDown(endView, endViewIndex, numChildren);
- }
- }
-
- /**
- * Re-measure a child, and if its height changes, lay it out preserving its
- * top, and adjust the children below it appropriately.
- *
- * @param child The child
- * @param childIndex The view group index of the child.
- * @param numChildren The number of children in the view group.
- */
- private void measureAndAdjustDown(View child, int childIndex, int numChildren) {
- int oldSize = getChildSize(child);
- measureChild(child);
-
- if (getChildMeasuredSize(child) == oldSize) {
- return;
- }
-
- // lay out the view, preserving its top
- relayoutMeasuredChild(child);
-
- // adjust views below appropriately
- final int sizeDelta = getChildMeasuredSize(child) - oldSize;
- for (int i = childIndex + 1; i < numChildren; i++) {
- getChildAt(i).offsetTopAndBottom(sizeDelta);
- }
- }
-
- /**
- * Do an arrow scroll based on focus searching. If a new view is
- * given focus, return the selection delta and amount to scroll via
- * an {@link ArrowScrollFocusResult}, otherwise, return null.
- *
- * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
- * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
- * current view orientation.
- *
- * @return The result if focus has changed, or <code>null</code>.
- */
- private ArrowScrollFocusResult arrowScrollFocused(final int direction) {
- forceValidFocusDirection(direction);
-
- final View selectedView = getSelectedView();
- final View newFocus;
- final int searchPoint;
-
- if (selectedView != null && selectedView.hasFocus()) {
- View oldFocus = selectedView.findFocus();
- newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction);
- } else {
- if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) {
- boolean fadingEdgeShowing = (mFirstPosition > 0);
- final int start = getStartEdge() +
- (fadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
-
- final int selectedStart;
- if (selectedView != null) {
- selectedStart = getChildStartEdge(selectedView);
- } else {
- selectedStart = start;
- }
-
- searchPoint = Math.max(selectedStart, start);
- } else {
- final boolean fadingEdgeShowing =
- (mFirstPosition + getChildCount() - 1) < mItemCount;
- final int end = getEndEdge() - (fadingEdgeShowing ? getArrowScrollPreviewLength() : 0);
-
- final int selectedEnd;
- if (selectedView != null) {
- selectedEnd = getChildEndEdge(selectedView);
- } else {
- selectedEnd = end;
- }
-
- searchPoint = Math.min(selectedEnd, end);
- }
-
- final int x = (mIsVertical ? 0 : searchPoint);
- final int y = (mIsVertical ? searchPoint : 0);
- mTempRect.set(x, y, x, y);
-
- newFocus = FocusFinder.getInstance().findNextFocusFromRect(this, mTempRect, direction);
- }
-
- if (newFocus != null) {
- final int positionOfNewFocus = positionOfNewFocus(newFocus);
-
- // If the focus change is in a different new position, make sure
- // we aren't jumping over another selectable position.
- if (mSelectedPosition != INVALID_POSITION && positionOfNewFocus != mSelectedPosition) {
- final int selectablePosition = lookForSelectablePositionOnScreen(direction);
-
- final boolean movingForward =
- (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT);
- final boolean movingBackward =
- (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT);
-
- if (selectablePosition != INVALID_POSITION &&
- ((movingForward && selectablePosition < positionOfNewFocus) ||
- (movingBackward && selectablePosition > positionOfNewFocus))) {
- return null;
- }
- }
-
- int focusScroll = amountToScrollToNewFocus(direction, newFocus, positionOfNewFocus);
-
- final int maxScrollAmount = getMaxScrollAmount();
- if (focusScroll < maxScrollAmount) {
- // Not moving too far, safe to give next view focus
- newFocus.requestFocus(direction);
- mArrowScrollFocusResult.populate(positionOfNewFocus, focusScroll);
- return mArrowScrollFocusResult;
- } else if (distanceToView(newFocus) < maxScrollAmount) {
- // Case to consider:
- // Too far to get entire next focusable on screen, but by going
- // max scroll amount, we are getting it at least partially in view,
- // so give it focus and scroll the max amount.
- newFocus.requestFocus(direction);
- mArrowScrollFocusResult.populate(positionOfNewFocus, maxScrollAmount);
- return mArrowScrollFocusResult;
- }
- }
-
- return null;
- }
-
- /**
- * @return The maximum amount a list view will scroll in response to
- * an arrow event.
- */
- public int getMaxScrollAmount() {
- return (int) (MAX_SCROLL_FACTOR * getSize());
- }
-
- /**
- * @return The amount to preview next items when arrow scrolling.
- */
- private int getArrowScrollPreviewLength() {
- return mItemMargin + Math.max(MIN_SCROLL_PREVIEW_PIXELS, getFadingEdgeLength());
- }
-
- /**
- * @param newFocus The view that would have focus.
- * @return the position that contains newFocus
- */
- private int positionOfNewFocus(View newFocus) {
- final int numChildren = getChildCount();
-
- for (int i = 0; i < numChildren; i++) {
- final View child = getChildAt(i);
- if (isViewAncestorOf(newFocus, child)) {
- return mFirstPosition + i;
- }
- }
-
- throw new IllegalArgumentException("newFocus is not a child of any of the"
- + " children of the list!");
- }
-
- /**
- * Handle an arrow scroll going up or down. Take into account whether items are selectable,
- * whether there are focusable items, etc.
- *
- * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
- * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
- * current view orientation.
- *
- * @return Whether any scrolling, selection or focus change occurred.
- */
- private boolean arrowScrollImpl(int direction) {
- forceValidFocusDirection(direction);
-
- if (getChildCount() <= 0) {
- return false;
- }
-
- View selectedView = getSelectedView();
- int selectedPos = mSelectedPosition;
-
- int nextSelectedPosition = lookForSelectablePositionOnScreen(direction);
- int amountToScroll = amountToScroll(direction, nextSelectedPosition);
-
- // If we are moving focus, we may OVERRIDE the default behaviour
- final ArrowScrollFocusResult focusResult = (mItemsCanFocus ? arrowScrollFocused(direction) : null);
- if (focusResult != null) {
- nextSelectedPosition = focusResult.getSelectedPosition();
- amountToScroll = focusResult.getAmountToScroll();
- }
-
- boolean needToRedraw = (focusResult != null);
- if (nextSelectedPosition != INVALID_POSITION) {
- handleNewSelectionChange(selectedView, direction, nextSelectedPosition, focusResult != null);
-
- setSelectedPositionInt(nextSelectedPosition);
- setNextSelectedPositionInt(nextSelectedPosition);
-
- selectedView = getSelectedView();
- selectedPos = nextSelectedPosition;
-
- if (mItemsCanFocus && focusResult == null) {
- // There was no new view found to take focus, make sure we
- // don't leave focus with the old selection.
- final View focused = getFocusedChild();
- if (focused != null) {
- focused.clearFocus();
- }
- }
-
- needToRedraw = true;
- checkSelectionChanged();
- }
-
- if (amountToScroll > 0) {
- scrollListItemsBy(direction == View.FOCUS_UP || direction == View.FOCUS_LEFT ?
- amountToScroll : -amountToScroll);
- needToRedraw = true;
- }
-
- // If we didn't find a new focusable, make sure any existing focused
- // item that was panned off screen gives up focus.
- if (mItemsCanFocus && focusResult == null &&
- selectedView != null && selectedView.hasFocus()) {
- final View focused = selectedView.findFocus();
- if (!isViewAncestorOf(focused, this) || distanceToView(focused) > 0) {
- focused.clearFocus();
- }
- }
-
- // If the current selection is panned off, we need to remove the selection
- if (nextSelectedPosition == INVALID_POSITION && selectedView != null
- && !isViewAncestorOf(selectedView, this)) {
- selectedView = null;
- hideSelector();
-
- // But we don't want to set the ressurect position (that would make subsequent
- // unhandled key events bring back the item we just scrolled off)
- mResurrectToPosition = INVALID_POSITION;
- }
-
- if (needToRedraw) {
- if (selectedView != null) {
- positionSelector(selectedPos, selectedView);
- mSelectedStart = getChildStartEdge(selectedView);
- }
-
- if (!awakenScrollbarsInternal()) {
- invalidate();
- }
-
- invokeOnItemScrollListener();
- return true;
- }
-
- return false;
- }
-
- /**
- * Determine how much we need to scroll in order to get the next selected view
- * visible. The amount is capped at {@link #getMaxScrollAmount()}.
- *
- * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
- * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
- * current view orientation.
- * @param nextSelectedPosition The position of the next selection, or
- * {@link #INVALID_POSITION} if there is no next selectable position
- *
- * @return The amount to scroll. Note: this is always positive! Direction
- * needs to be taken into account when actually scrolling.
- */
- private int amountToScroll(int direction, int nextSelectedPosition) {
- forceValidFocusDirection(direction);
-
- final int numChildren = getChildCount();
-
- if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) {
- final int end = getEndEdge();
-
- int indexToMakeVisible = numChildren - 1;
- if (nextSelectedPosition != INVALID_POSITION) {
- indexToMakeVisible = nextSelectedPosition - mFirstPosition;
- }
-
- final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
- final View viewToMakeVisible = getChildAt(indexToMakeVisible);
-
- int goalEnd = end;
- if (positionToMakeVisible < mItemCount - 1) {
- goalEnd -= getArrowScrollPreviewLength();
- }
-
- final int viewToMakeVisibleStart = getChildStartEdge(viewToMakeVisible);
- final int viewToMakeVisibleEnd = getChildEndEdge(viewToMakeVisible);
-
- if (viewToMakeVisibleEnd <= goalEnd) {
- // Target item is fully visible
- return 0;
- }
-
- if (nextSelectedPosition != INVALID_POSITION &&
- (goalEnd - viewToMakeVisibleStart) >= getMaxScrollAmount()) {
- // Item already has enough of it visible, changing selection is good enough
- return 0;
- }
-
- int amountToScroll = (viewToMakeVisibleEnd - goalEnd);
-
- if (mFirstPosition + numChildren == mItemCount) {
- final int lastChildEnd = getChildEndEdge(getChildAt(numChildren - 1));
-
- // Last is last in list -> Make sure we don't scroll past it
- final int max = lastChildEnd - end;
- amountToScroll = Math.min(amountToScroll, max);
- }
-
- return Math.min(amountToScroll, getMaxScrollAmount());
- } else {
- final int start = getStartEdge();
-
- int indexToMakeVisible = 0;
- if (nextSelectedPosition != INVALID_POSITION) {
- indexToMakeVisible = nextSelectedPosition - mFirstPosition;
- }
-
- final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
- final View viewToMakeVisible = getChildAt(indexToMakeVisible);
-
- int goalStart = start;
- if (positionToMakeVisible > 0) {
- goalStart += getArrowScrollPreviewLength();
- }
-
- final int viewToMakeVisibleStart = getChildStartEdge(viewToMakeVisible);
- final int viewToMakeVisibleEnd = getChildEndEdge(viewToMakeVisible);
-
- if (viewToMakeVisibleStart >= goalStart) {
- // Item is fully visible
- return 0;
- }
-
- if (nextSelectedPosition != INVALID_POSITION &&
- (viewToMakeVisibleEnd - goalStart) >= getMaxScrollAmount()) {
- // Item already has enough of it visible, changing selection is good enough
- return 0;
- }
-
- int amountToScroll = (goalStart - viewToMakeVisibleStart);
-
- if (mFirstPosition == 0) {
- final int firstChildStart = getChildStartEdge(getChildAt(0));
-
- // First is first in list -> make sure we don't scroll past it
- final int max = start - firstChildStart;
- amountToScroll = Math.min(amountToScroll, max);
- }
-
- return Math.min(amountToScroll, getMaxScrollAmount());
- }
- }
-
- /**
- * Determine how much we need to scroll in order to get newFocus in view.
- *
- * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
- * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
- * current view orientation.
- * @param newFocus The view that would take focus.
- * @param positionOfNewFocus The position of the list item containing newFocus
- *
- * @return The amount to scroll. Note: this is always positive! Direction
- * needs to be taken into account when actually scrolling.
- */
- private int amountToScrollToNewFocus(int direction, View newFocus, int positionOfNewFocus) {
- forceValidFocusDirection(direction);
-
- int amountToScroll = 0;
-
- newFocus.getDrawingRect(mTempRect);
- offsetDescendantRectToMyCoords(newFocus, mTempRect);
-
- if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT) {
- final int start = getStartEdge();
- final int newFocusStart = (mIsVertical ? mTempRect.top : mTempRect.left);
-
- if (newFocusStart < start) {
- amountToScroll = start - newFocusStart;
- if (positionOfNewFocus > 0) {
- amountToScroll += getArrowScrollPreviewLength();
- }
- }
- } else {
- final int end = getEndEdge();
- final int newFocusEnd = (mIsVertical ? mTempRect.bottom : mTempRect.right);
-
- if (newFocusEnd > end) {
- amountToScroll = newFocusEnd - end;
- if (positionOfNewFocus < mItemCount - 1) {
- amountToScroll += getArrowScrollPreviewLength();
- }
- }
- }
-
- return amountToScroll;
- }
-
- /**
- * Determine the distance to the nearest edge of a view in a particular
- * direction.
- *
- * @param descendant A descendant of this list.
- * @return The distance, or 0 if the nearest edge is already on screen.
- */
- private int distanceToView(View descendant) {
- descendant.getDrawingRect(mTempRect);
- offsetDescendantRectToMyCoords(descendant, mTempRect);
-
- final int start = getStartEdge();
- final int end = getEndEdge();
-
- final int viewStart = (mIsVertical ? mTempRect.top : mTempRect.left);
- final int viewEnd = (mIsVertical ? mTempRect.bottom : mTempRect.right);
-
- int distance = 0;
- if (viewEnd < start) {
- distance = start - viewEnd;
- } else if (viewStart > end) {
- distance = viewStart - end;
- }
-
- return distance;
- }
-
- private boolean handleKeyScroll(KeyEvent event, int count, int direction) {
- boolean handled = false;
-
- if (KeyEventCompat.hasNoModifiers(event)) {
- handled = resurrectSelectionIfNeeded();
- if (!handled) {
- while (count-- > 0) {
- if (arrowScroll(direction)) {
- handled = true;
- } else {
- break;
- }
- }
- }
- } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_ALT_ON)) {
- handled = resurrectSelectionIfNeeded() || fullScroll(direction);
- }
-
- return handled;
- }
-
- private boolean handleKeyEvent(int keyCode, int count, KeyEvent event) {
- if (mAdapter == null || !mIsAttached) {
- return false;
- }
-
- if (mDataChanged) {
- layoutChildren();
- }
-
- boolean handled = false;
- final int action = event.getAction();
-
- if (action != KeyEvent.ACTION_UP) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_UP:
- if (mIsVertical) {
- handled = handleKeyScroll(event, count, View.FOCUS_UP);
- } else if (KeyEventCompat.hasNoModifiers(event)) {
- handled = handleFocusWithinItem(View.FOCUS_UP);
- }
- break;
-
- case KeyEvent.KEYCODE_DPAD_DOWN: {
- if (mIsVertical) {
- handled = handleKeyScroll(event, count, View.FOCUS_DOWN);
- } else if (KeyEventCompat.hasNoModifiers(event)) {
- handled = handleFocusWithinItem(View.FOCUS_DOWN);
- }
- break;
- }
-
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if (!mIsVertical) {
- handled = handleKeyScroll(event, count, View.FOCUS_LEFT);
- } else if (KeyEventCompat.hasNoModifiers(event)) {
- handled = handleFocusWithinItem(View.FOCUS_LEFT);
- }
- break;
-
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (!mIsVertical) {
- handled = handleKeyScroll(event, count, View.FOCUS_RIGHT);
- } else if (KeyEventCompat.hasNoModifiers(event)) {
- handled = handleFocusWithinItem(View.FOCUS_RIGHT);
- }
- break;
-
- case KeyEvent.KEYCODE_DPAD_CENTER:
- case KeyEvent.KEYCODE_ENTER:
- if (KeyEventCompat.hasNoModifiers(event)) {
- handled = resurrectSelectionIfNeeded();
- if (!handled
- && event.getRepeatCount() == 0 && getChildCount() > 0) {
- keyPressed();
- handled = true;
- }
- }
- break;
-
- case KeyEvent.KEYCODE_SPACE:
- if (KeyEventCompat.hasNoModifiers(event)) {
- handled = resurrectSelectionIfNeeded() ||
- pageScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT);
- } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
- handled = resurrectSelectionIfNeeded() ||
- fullScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT);
- }
-
- handled = true;
- break;
-
- case KeyEvent.KEYCODE_PAGE_UP:
- if (KeyEventCompat.hasNoModifiers(event)) {
- handled = resurrectSelectionIfNeeded() ||
- pageScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT);
- } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_ALT_ON)) {
- handled = resurrectSelectionIfNeeded() ||
- fullScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT);
- }
- break;
-
- case KeyEvent.KEYCODE_PAGE_DOWN:
- if (KeyEventCompat.hasNoModifiers(event)) {
- handled = resurrectSelectionIfNeeded() ||
- pageScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT);
- } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_ALT_ON)) {
- handled = resurrectSelectionIfNeeded() ||
- fullScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT);
- }
- break;
-
- case KeyEvent.KEYCODE_MOVE_HOME:
- if (KeyEventCompat.hasNoModifiers(event)) {
- handled = resurrectSelectionIfNeeded() ||
- fullScroll(mIsVertical ? View.FOCUS_UP : View.FOCUS_LEFT);
- }
- break;
-
- case KeyEvent.KEYCODE_MOVE_END:
- if (KeyEventCompat.hasNoModifiers(event)) {
- handled = resurrectSelectionIfNeeded() ||
- fullScroll(mIsVertical ? View.FOCUS_DOWN : View.FOCUS_RIGHT);
- }
- break;
- }
- }
-
- if (handled) {
- return true;
- }
-
- switch (action) {
- case KeyEvent.ACTION_DOWN:
- return super.onKeyDown(keyCode, event);
-
- case KeyEvent.ACTION_UP:
- if (!isEnabled()) {
- return true;
- }
-
- if (isClickable() && isPressed() &&
- mSelectedPosition >= 0 && mAdapter != null &&
- mSelectedPosition < mAdapter.getCount()) {
-
- final View child = getChildAt(mSelectedPosition - mFirstPosition);
- if (child != null) {
- performItemClick(child, mSelectedPosition, mSelectedRowId);
- child.setPressed(false);
- }
-
- setPressed(false);
- return true;
- }
-
- return false;
-
- case KeyEvent.ACTION_MULTIPLE:
- return super.onKeyMultiple(keyCode, count, event);
-
- default:
- return false;
- }
- }
-
- private void initOrResetVelocityTracker() {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- } else {
- mVelocityTracker.clear();
- }
- }
-
- private void initVelocityTrackerIfNotExists() {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- }
-
- private void recycleVelocityTracker() {
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- }
-
- /**
- * Notify our scroll listener (if there is one) of a change in scroll state
- */
- private void invokeOnItemScrollListener() {
- if (mOnScrollListener != null) {
- mOnScrollListener.onScroll(this, mFirstPosition, getChildCount(), mItemCount);
- }
-
- // Dummy values, View's implementation does not use these.
- onScrollChanged(0, 0, 0, 0);
- }
-
- private void reportScrollStateChange(int newState) {
- if (newState == mLastScrollState) {
- return;
- }
-
- if (mOnScrollListener != null) {
- mLastScrollState = newState;
- mOnScrollListener.onScrollStateChanged(this, newState);
- }
- }
-
- private boolean maybeStartScrolling(int delta) {
- final boolean isOverScroll = (mOverScroll != 0);
- if (Math.abs(delta) <= mTouchSlop && !isOverScroll) {
- return false;
- }
-
- if (isOverScroll) {
- mTouchMode = TOUCH_MODE_OVERSCROLL;
- } else {
- mTouchMode = TOUCH_MODE_DRAGGING;
- }
-
- // Time to start stealing events! Once we've stolen them, don't
- // let anyone steal from us.
- final ViewParent parent = getParent();
- if (parent != null) {
- parent.requestDisallowInterceptTouchEvent(true);
- }
-
- cancelCheckForLongPress();
-
- setPressed(false);
- View motionView = getChildAt(mMotionPosition - mFirstPosition);
- if (motionView != null) {
- motionView.setPressed(false);
- }
-
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
-
- return true;
- }
-
- private void maybeScroll(int delta) {
- if (mTouchMode == TOUCH_MODE_DRAGGING) {
- handleDragChange(delta);
- } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
- handleOverScrollChange(delta);
- }
- }
-
- private void handleDragChange(int delta) {
- // Time to start stealing events! Once we've stolen them, don't
- // let anyone steal from us.
- final ViewParent parent = getParent();
- if (parent != null) {
- parent.requestDisallowInterceptTouchEvent(true);
- }
-
- final int motionIndex;
- if (mMotionPosition >= 0) {
- motionIndex = mMotionPosition - mFirstPosition;
- } else {
- // If we don't have a motion position that we can reliably track,
- // pick something in the middle to make a best guess at things below.
- motionIndex = getChildCount() / 2;
- }
-
- int motionViewPrevStart = 0;
- View motionView = this.getChildAt(motionIndex);
- if (motionView != null) {
- motionViewPrevStart = getChildStartEdge(motionView);
- }
-
- boolean atEdge = scrollListItemsBy(delta);
-
- motionView = this.getChildAt(motionIndex);
- if (motionView != null) {
- final int motionViewRealStart = getChildStartEdge(motionView);
-
- if (atEdge) {
- final int overscroll = -delta - (motionViewRealStart - motionViewPrevStart);
- updateOverScrollState(delta, overscroll);
- }
- }
- }
-
- private void updateOverScrollState(int delta, int overscroll) {
- overScrollByInternal((mIsVertical ? 0 : overscroll),
- (mIsVertical ? overscroll : 0),
- (mIsVertical ? 0 : mOverScroll),
- (mIsVertical ? mOverScroll : 0),
- 0, 0,
- (mIsVertical ? 0 : mOverscrollDistance),
- (mIsVertical ? mOverscrollDistance : 0),
- true);
-
- if (Math.abs(mOverscrollDistance) == Math.abs(mOverScroll)) {
- // Break fling velocity if we impacted an edge
- if (mVelocityTracker != null) {
- mVelocityTracker.clear();
- }
- }
-
- final int overscrollMode = ViewCompat.getOverScrollMode(this);
- if (overscrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
- (overscrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
- mTouchMode = TOUCH_MODE_OVERSCROLL;
-
- float pull = (float) overscroll / getSize();
- if (delta > 0) {
- mStartEdge.onPull(pull);
-
- if (!mEndEdge.isFinished()) {
- mEndEdge.onRelease();
- }
- } else if (delta < 0) {
- mEndEdge.onPull(pull);
-
- if (!mStartEdge.isFinished()) {
- mStartEdge.onRelease();
- }
- }
-
- if (delta != 0) {
- ViewCompat.postInvalidateOnAnimation(this);
- }
- }
- }
-
- private void handleOverScrollChange(int delta) {
- final int oldOverScroll = mOverScroll;
- final int newOverScroll = oldOverScroll - delta;
-
- int overScrollDistance = -delta;
- if ((newOverScroll < 0 && oldOverScroll >= 0) ||
- (newOverScroll > 0 && oldOverScroll <= 0)) {
- overScrollDistance = -oldOverScroll;
- delta += overScrollDistance;
- } else {
- delta = 0;
- }
-
- if (overScrollDistance != 0) {
- updateOverScrollState(delta, overScrollDistance);
- }
-
- if (delta != 0) {
- if (mOverScroll != 0) {
- mOverScroll = 0;
- ViewCompat.postInvalidateOnAnimation(this);
- }
-
- scrollListItemsBy(delta);
- mTouchMode = TOUCH_MODE_DRAGGING;
-
- // We did not scroll the full amount. Treat this essentially like the
- // start of a new touch scroll
- mMotionPosition = findClosestMotionRowOrColumn((int) mLastTouchPos);
- mTouchRemainderPos = 0;
- }
- }
-
- /**
- * What is the distance between the source and destination rectangles given the direction of
- * focus navigation between them? The direction basically helps figure out more quickly what is
- * self evident by the relationship between the rects...
- *
- * @param source the source rectangle
- * @param dest the destination rectangle
- * @param direction the direction
- * @return the distance between the rectangles
- */
- private static int getDistance(Rect source, Rect dest, int direction) {
- int sX, sY; // source x, y
- int dX, dY; // dest x, y
-
- switch (direction) {
- case View.FOCUS_RIGHT:
- sX = source.right;
- sY = source.top + source.height() / 2;
- dX = dest.left;
- dY = dest.top + dest.height() / 2;
- break;
-
- case View.FOCUS_DOWN:
- sX = source.left + source.width() / 2;
- sY = source.bottom;
- dX = dest.left + dest.width() / 2;
- dY = dest.top;
- break;
-
- case View.FOCUS_LEFT:
- sX = source.left;
- sY = source.top + source.height() / 2;
- dX = dest.right;
- dY = dest.top + dest.height() / 2;
- break;
-
- case View.FOCUS_UP:
- sX = source.left + source.width() / 2;
- sY = source.top;
- dX = dest.left + dest.width() / 2;
- dY = dest.bottom;
- break;
-
- case View.FOCUS_FORWARD:
- case View.FOCUS_BACKWARD:
- sX = source.right + source.width() / 2;
- sY = source.top + source.height() / 2;
- dX = dest.left + dest.width() / 2;
- dY = dest.top + dest.height() / 2;
- break;
-
- default:
- throw new IllegalArgumentException("direction must be one of "
- + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT, "
- + "FOCUS_FORWARD, FOCUS_BACKWARD}.");
- }
-
- int deltaX = dX - sX;
- int deltaY = dY - sY;
-
- return deltaY * deltaY + deltaX * deltaX;
- }
-
- private int findMotionRowOrColumn(int motionPos) {
- int childCount = getChildCount();
- if (childCount == 0) {
- return INVALID_POSITION;
- }
-
- for (int i = 0; i < childCount; i++) {
- final View v = getChildAt(i);
- if (motionPos <= getChildEndEdge(v)) {
- return mFirstPosition + i;
- }
- }
-
- return INVALID_POSITION;
- }
-
- private int findClosestMotionRowOrColumn(int motionPos) {
- final int childCount = getChildCount();
- if (childCount == 0) {
- return INVALID_POSITION;
- }
-
- final int motionRow = findMotionRowOrColumn(motionPos);
- if (motionRow != INVALID_POSITION) {
- return motionRow;
- } else {
- return mFirstPosition + childCount - 1;
- }
- }
-
- @TargetApi(9)
- private int getScaledOverscrollDistance(ViewConfiguration vc) {
- if (Build.VERSION.SDK_INT < 9) {
- return 0;
- }
-
- return vc.getScaledOverscrollDistance();
- }
-
- private int getStartEdge() {
- return (mIsVertical ? getPaddingTop() : getPaddingLeft());
- }
-
- private int getEndEdge() {
- if (mIsVertical) {
- return (getHeight() - getPaddingBottom());
- } else {
- return (getWidth() - getPaddingRight());
- }
- }
-
- private int getSize() {
- return (mIsVertical ? getHeight() : getWidth());
- }
-
- private int getAvailableSize() {
- if (mIsVertical) {
- return getHeight() - getPaddingBottom() - getPaddingTop();
- } else {
- return getWidth() - getPaddingRight() - getPaddingLeft();
- }
- }
-
- private int getChildStartEdge(View child) {
- return (mIsVertical ? child.getTop() : child.getLeft());
- }
-
- private int getChildEndEdge(View child) {
- return (mIsVertical ? child.getBottom() : child.getRight());
- }
-
- private int getChildSize(View child) {
- return (mIsVertical ? child.getHeight() : child.getWidth());
- }
-
- private int getChildMeasuredSize(View child) {
- return (mIsVertical ? child.getMeasuredHeight() : child.getMeasuredWidth());
- }
-
- private int getFadingEdgeLength() {
- return (mIsVertical ? getVerticalFadingEdgeLength() : getHorizontalFadingEdgeLength());
- }
-
- private int getMinSelectionPixel(int start, int fadingEdgeLength, int selectedPosition) {
- // First pixel we can draw the selection into.
- int selectionPixelStart = start;
- if (selectedPosition > 0) {
- selectionPixelStart += fadingEdgeLength;
- }
-
- return selectionPixelStart;
- }
-
- private int getMaxSelectionPixel(int end, int fadingEdgeLength,
- int selectedPosition) {
- int selectionPixelEnd = end;
- if (selectedPosition != mItemCount - 1) {
- selectionPixelEnd -= fadingEdgeLength;
- }
-
- return selectionPixelEnd;
- }
-
- private boolean contentFits() {
- final int childCount = getChildCount();
- if (childCount == 0) {
- return true;
- }
-
- if (childCount != mItemCount) {
- return false;
- }
-
- View first = getChildAt(0);
- View last = getChildAt(childCount - 1);
-
- return (getChildStartEdge(first) >= getStartEdge() &&
- getChildEndEdge(last) <= getEndEdge());
- }
-
- private void triggerCheckForTap() {
- if (mPendingCheckForTap == null) {
- mPendingCheckForTap = new CheckForTap();
- }
-
- postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
- }
-
- private void cancelCheckForTap() {
- if (mPendingCheckForTap == null) {
- return;
- }
-
- removeCallbacks(mPendingCheckForTap);
- }
-
- private void triggerCheckForLongPress() {
- if (mPendingCheckForLongPress == null) {
- mPendingCheckForLongPress = new CheckForLongPress();
- }
-
- mPendingCheckForLongPress.rememberWindowAttachCount();
-
- postDelayed(mPendingCheckForLongPress,
- ViewConfiguration.getLongPressTimeout());
- }
-
- private void cancelCheckForLongPress() {
- if (mPendingCheckForLongPress == null) {
- return;
- }
-
- removeCallbacks(mPendingCheckForLongPress);
- }
-
- private boolean scrollListItemsBy(int incrementalDelta) {
- final int childCount = getChildCount();
- if (childCount == 0) {
- return true;
- }
-
- final int firstStart = getChildStartEdge(getChildAt(0));
- final int lastEnd = getChildEndEdge(getChildAt(childCount - 1));
-
- final int paddingTop = getPaddingTop();
- final int paddingLeft = getPaddingLeft();
-
- final int paddingStart = (mIsVertical ? paddingTop : paddingLeft);
-
- final int spaceBefore = paddingStart - firstStart;
- final int end = getEndEdge();
- final int spaceAfter = lastEnd - end;
-
- final int size = getAvailableSize();
-
- if (incrementalDelta < 0) {
- incrementalDelta = Math.max(-(size - 1), incrementalDelta);
- } else {
- incrementalDelta = Math.min(size - 1, incrementalDelta);
- }
-
- final int firstPosition = mFirstPosition;
-
- final boolean cannotScrollDown = (firstPosition == 0 &&
- firstStart >= paddingStart && incrementalDelta >= 0);
- final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
- lastEnd <= end && incrementalDelta <= 0);
-
- if (cannotScrollDown || cannotScrollUp) {
- return incrementalDelta != 0;
- }
-
- final boolean inTouchMode = isInTouchMode();
- if (inTouchMode) {
- hideSelector();
- }
-
- int start = 0;
- int count = 0;
-
- final boolean down = (incrementalDelta < 0);
- if (down) {
- int childrenStart = -incrementalDelta + paddingStart;
-
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- final int childEnd = getChildEndEdge(child);
-
- if (childEnd >= childrenStart) {
- break;
- }
-
- count++;
- mRecycler.addScrapView(child, firstPosition + i);
- }
- } else {
- int childrenEnd = end - incrementalDelta;
-
- for (int i = childCount - 1; i >= 0; i--) {
- final View child = getChildAt(i);
- final int childStart = getChildStartEdge(child);
-
- if (childStart <= childrenEnd) {
- break;
- }
-
- start = i;
- count++;
- mRecycler.addScrapView(child, firstPosition + i);
- }
- }
-
- mBlockLayoutRequests = true;
-
- if (count > 0) {
- detachViewsFromParent(start, count);
- }
-
- // invalidate before moving the children to avoid unnecessary invalidate
- // calls to bubble up from the children all the way to the top
- if (!awakenScrollbarsInternal()) {
- invalidate();
- }
-
- offsetChildren(incrementalDelta);
-
- if (down) {
- mFirstPosition += count;
- }
-
- final int absIncrementalDelta = Math.abs(incrementalDelta);
- if (spaceBefore < absIncrementalDelta || spaceAfter < absIncrementalDelta) {
- fillGap(down);
- }
-
- if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
- final int childIndex = mSelectedPosition - mFirstPosition;
- if (childIndex >= 0 && childIndex < getChildCount()) {
- positionSelector(mSelectedPosition, getChildAt(childIndex));
- }
- } else if (mSelectorPosition != INVALID_POSITION) {
- final int childIndex = mSelectorPosition - mFirstPosition;
- if (childIndex >= 0 && childIndex < getChildCount()) {
- positionSelector(INVALID_POSITION, getChildAt(childIndex));
- }
- } else {
- mSelectorRect.setEmpty();
- }
-
- mBlockLayoutRequests = false;
-
- invokeOnItemScrollListener();
-
- return false;
- }
-
- @TargetApi(14)
- private final float getCurrVelocity() {
- if (Build.VERSION.SDK_INT >= 14) {
- return mScroller.getCurrVelocity();
- }
-
- return 0;
- }
-
- @TargetApi(5)
- private boolean awakenScrollbarsInternal() {
- return (Build.VERSION.SDK_INT >= 5) && super.awakenScrollBars();
- }
-
- @Override
- public void computeScroll() {
- if (!mScroller.computeScrollOffset()) {
- return;
- }
-
- final int pos;
- if (mIsVertical) {
- pos = mScroller.getCurrY();
- } else {
- pos = mScroller.getCurrX();
- }
-
- final int diff = (int) (pos - mLastTouchPos);
- mLastTouchPos = pos;
-
- final boolean stopped = scrollListItemsBy(diff);
-
- if (!stopped && !mScroller.isFinished()) {
- ViewCompat.postInvalidateOnAnimation(this);
- } else {
- if (stopped) {
- final int overScrollMode = ViewCompat.getOverScrollMode(this);
- if (overScrollMode != ViewCompat.OVER_SCROLL_NEVER) {
- final EdgeEffectCompat edge =
- (diff > 0 ? mStartEdge : mEndEdge);
-
- boolean needsInvalidate =
- edge.onAbsorb(Math.abs((int) getCurrVelocity()));
-
- if (needsInvalidate) {
- ViewCompat.postInvalidateOnAnimation(this);
- }
- }
-
- finishSmoothScrolling();
- }
-
- mTouchMode = TOUCH_MODE_REST;
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
- }
- }
-
- private void finishEdgeGlows() {
- if (mStartEdge != null) {
- mStartEdge.finish();
- }
-
- if (mEndEdge != null) {
- mEndEdge.finish();
- }
- }
-
- private boolean drawStartEdge(Canvas canvas) {
- if (mStartEdge.isFinished()) {
- return false;
- }
-
- if (mIsVertical) {
- return mStartEdge.draw(canvas);
- }
-
- final int restoreCount = canvas.save();
- final int height = getHeight();
-
- canvas.translate(0, height);
- canvas.rotate(270);
-
- final boolean needsInvalidate = mStartEdge.draw(canvas);
- canvas.restoreToCount(restoreCount);
- return needsInvalidate;
- }
-
- private boolean drawEndEdge(Canvas canvas) {
- if (mEndEdge.isFinished()) {
- return false;
- }
-
- final int restoreCount = canvas.save();
- final int width = getWidth();
- final int height = getHeight();
-
- if (mIsVertical) {
- canvas.translate(-width, height);
- canvas.rotate(180, width, 0);
- } else {
- canvas.translate(width, 0);
- canvas.rotate(90);
- }
-
- final boolean needsInvalidate = mEndEdge.draw(canvas);
- canvas.restoreToCount(restoreCount);
- return needsInvalidate;
- }
-
- private void finishSmoothScrolling() {
- mTouchMode = TOUCH_MODE_REST;
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
-
- mScroller.abortAnimation();
- if (mPositionScroller != null) {
- mPositionScroller.stop();
- }
- }
-
- private void drawSelector(Canvas canvas) {
- if (!mSelectorRect.isEmpty()) {
- final Drawable selector = mSelector;
- selector.setBounds(mSelectorRect);
- selector.draw(canvas);
- }
- }
-
- private void useDefaultSelector() {
- setSelector(getResources().getDrawable(
- android.R.drawable.list_selector_background));
- }
-
- private boolean shouldShowSelector() {
- return (hasFocus() && !isInTouchMode()) || touchModeDrawsInPressedState();
- }
-
- private void positionSelector(int position, View selected) {
- if (position != INVALID_POSITION) {
- mSelectorPosition = position;
- }
-
- mSelectorRect.set(selected.getLeft(), selected.getTop(), selected.getRight(),
- selected.getBottom());
-
- final boolean isChildViewEnabled = mIsChildViewEnabled;
- if (selected.isEnabled() != isChildViewEnabled) {
- mIsChildViewEnabled = !isChildViewEnabled;
-
- if (getSelectedItemPosition() != INVALID_POSITION) {
- refreshDrawableState();
- }
- }
- }
-
- private void hideSelector() {
- if (mSelectedPosition != INVALID_POSITION) {
- if (mLayoutMode != LAYOUT_SPECIFIC) {
- mResurrectToPosition = mSelectedPosition;
- }
-
- if (mNextSelectedPosition >= 0 && mNextSelectedPosition != mSelectedPosition) {
- mResurrectToPosition = mNextSelectedPosition;
- }
-
- setSelectedPositionInt(INVALID_POSITION);
- setNextSelectedPositionInt(INVALID_POSITION);
-
- mSelectedStart = 0;
- }
- }
-
- private void setSelectedPositionInt(int position) {
- mSelectedPosition = position;
- mSelectedRowId = getItemIdAtPosition(position);
- }
-
- private void setSelectionInt(int position) {
- setNextSelectedPositionInt(position);
- boolean awakeScrollbars = false;
-
- final int selectedPosition = mSelectedPosition;
- if (selectedPosition >= 0) {
- if (position == selectedPosition - 1) {
- awakeScrollbars = true;
- } else if (position == selectedPosition + 1) {
- awakeScrollbars = true;
- }
- }
-
- layoutChildren();
-
- if (awakeScrollbars) {
- awakenScrollbarsInternal();
- }
- }
-
- private void setNextSelectedPositionInt(int position) {
- mNextSelectedPosition = position;
- mNextSelectedRowId = getItemIdAtPosition(position);
-
- // If we are trying to sync to the selection, update that too
- if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
- mSyncPosition = position;
- mSyncRowId = mNextSelectedRowId;
- }
- }
-
- private boolean touchModeDrawsInPressedState() {
- switch (mTouchMode) {
- case TOUCH_MODE_TAP:
- case TOUCH_MODE_DONE_WAITING:
- return true;
- default:
- return false;
- }
- }
-
- /**
- * Sets the selector state to "pressed" and posts a CheckForKeyLongPress to see if
- * this is a long press.
- */
- private void keyPressed() {
- if (!isEnabled() || !isClickable()) {
- return;
- }
-
- final Drawable selector = mSelector;
- final Rect selectorRect = mSelectorRect;
-
- if (selector != null && (isFocused() || touchModeDrawsInPressedState())
- && !selectorRect.isEmpty()) {
-
- final View child = getChildAt(mSelectedPosition - mFirstPosition);
-
- if (child != null) {
- if (child.hasFocusable()) {
- return;
- }
-
- child.setPressed(true);
- }
-
- setPressed(true);
-
- final boolean longClickable = isLongClickable();
- final Drawable d = selector.getCurrent();
- if (d != null && d instanceof TransitionDrawable) {
- if (longClickable) {
- ((TransitionDrawable) d).startTransition(
- ViewConfiguration.getLongPressTimeout());
- } else {
- ((TransitionDrawable) d).resetTransition();
- }
- }
-
- if (longClickable && !mDataChanged) {
- if (mPendingCheckForKeyLongPress == null) {
- mPendingCheckForKeyLongPress = new CheckForKeyLongPress();
- }
-
- mPendingCheckForKeyLongPress.rememberWindowAttachCount();
- postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout());
- }
- }
- }
-
- private void updateSelectorState() {
- if (mSelector != null) {
- if (shouldShowSelector()) {
- mSelector.setState(getDrawableState());
- } else {
- mSelector.setState(STATE_NOTHING);
- }
- }
- }
-
- private void checkSelectionChanged() {
- if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
- selectionChanged();
- mOldSelectedPosition = mSelectedPosition;
- mOldSelectedRowId = mSelectedRowId;
- }
- }
-
- private void selectionChanged() {
- OnItemSelectedListener listener = getOnItemSelectedListener();
- if (listener == null) {
- return;
- }
-
- if (mInLayout || mBlockLayoutRequests) {
- // If we are in a layout traversal, defer notification
- // by posting. This ensures that the view tree is
- // in a consistent state and is able to accommodate
- // new layout or invalidate requests.
- if (mSelectionNotifier == null) {
- mSelectionNotifier = new SelectionNotifier();
- }
-
- post(mSelectionNotifier);
- } else {
- fireOnSelected();
- performAccessibilityActionsOnSelected();
- }
- }
-
- private void fireOnSelected() {
- OnItemSelectedListener listener = getOnItemSelectedListener();
- if (listener == null) {
- return;
- }
-
- final int selection = getSelectedItemPosition();
- if (selection >= 0) {
- View v = getSelectedView();
- listener.onItemSelected(this, v, selection,
- mAdapter.getItemId(selection));
- } else {
- listener.onNothingSelected(this);
- }
- }
-
- private void performAccessibilityActionsOnSelected() {
- final int position = getSelectedItemPosition();
- if (position >= 0) {
- // We fire selection events here not in View
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
- }
- }
-
- private int lookForSelectablePosition(int position) {
- return lookForSelectablePosition(position, true);
- }
-
- private int lookForSelectablePosition(int position, boolean lookDown) {
- final ListAdapter adapter = mAdapter;
- if (adapter == null || isInTouchMode()) {
- return INVALID_POSITION;
- }
-
- final int itemCount = mItemCount;
- if (!mAreAllItemsSelectable) {
- if (lookDown) {
- position = Math.max(0, position);
- while (position < itemCount && !adapter.isEnabled(position)) {
- position++;
- }
- } else {
- position = Math.min(position, itemCount - 1);
- while (position >= 0 && !adapter.isEnabled(position)) {
- position--;
- }
- }
-
- if (position < 0 || position >= itemCount) {
- return INVALID_POSITION;
- }
-
- return position;
- } else {
- if (position < 0 || position >= itemCount) {
- return INVALID_POSITION;
- }
-
- return position;
- }
- }
-
- /**
- * @param direction either {@link View#FOCUS_UP} or {@link View#FOCUS_DOWN} or
- * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT} depending on the
- * current view orientation.
- *
- * @return The position of the next selectable position of the views that
- * are currently visible, taking into account the fact that there might
- * be no selection. Returns {@link #INVALID_POSITION} if there is no
- * selectable view on screen in the given direction.
- */
- private int lookForSelectablePositionOnScreen(int direction) {
- forceValidFocusDirection(direction);
-
- final int firstPosition = mFirstPosition;
- final ListAdapter adapter = getAdapter();
-
- if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) {
- int startPos = (mSelectedPosition != INVALID_POSITION ?
- mSelectedPosition + 1 : firstPosition);
-
- if (startPos >= adapter.getCount()) {
- return INVALID_POSITION;
- }
-
- if (startPos < firstPosition) {
- startPos = firstPosition;
- }
-
- final int lastVisiblePos = getLastVisiblePosition();
-
- for (int pos = startPos; pos <= lastVisiblePos; pos++) {
- if (adapter.isEnabled(pos)
- && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
- return pos;
- }
- }
- } else {
- final int last = firstPosition + getChildCount() - 1;
-
- int startPos = (mSelectedPosition != INVALID_POSITION) ?
- mSelectedPosition - 1 : firstPosition + getChildCount() - 1;
-
- if (startPos < 0 || startPos >= adapter.getCount()) {
- return INVALID_POSITION;
- }
-
- if (startPos > last) {
- startPos = last;
- }
-
- for (int pos = startPos; pos >= firstPosition; pos--) {
- if (adapter.isEnabled(pos)
- && getChildAt(pos - firstPosition).getVisibility() == View.VISIBLE) {
- return pos;
- }
- }
- }
-
- return INVALID_POSITION;
- }
-
- @Override
- protected void drawableStateChanged() {
- super.drawableStateChanged();
- updateSelectorState();
- }
-
- @Override
- protected int[] onCreateDrawableState(int extraSpace) {
- // If the child view is enabled then do the default behavior.
- if (mIsChildViewEnabled) {
- // Common case
- return super.onCreateDrawableState(extraSpace);
- }
-
- // The selector uses this View's drawable state. The selected child view
- // is disabled, so we need to remove the enabled state from the drawable
- // states.
- final int enabledState = ENABLED_STATE_SET[0];
-
- // If we don't have any extra space, it will return one of the static state arrays,
- // and clearing the enabled state on those arrays is a bad thing! If we specify
- // we need extra space, it will create+copy into a new array that safely mutable.
- int[] state = super.onCreateDrawableState(extraSpace + 1);
- int enabledPos = -1;
- for (int i = state.length - 1; i >= 0; i--) {
- if (state[i] == enabledState) {
- enabledPos = i;
- break;
- }
- }
-
- // Remove the enabled state
- if (enabledPos >= 0) {
- System.arraycopy(state, enabledPos + 1, state, enabledPos,
- state.length - enabledPos - 1);
- }
-
- return state;
- }
-
- @Override
- protected boolean canAnimate() {
- return (super.canAnimate() && mItemCount > 0);
- }
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- final boolean drawSelectorOnTop = mDrawSelectorOnTop;
- if (!drawSelectorOnTop) {
- drawSelector(canvas);
- }
-
- super.dispatchDraw(canvas);
-
- if (drawSelectorOnTop) {
- drawSelector(canvas);
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
-
- boolean needsInvalidate = false;
-
- if (mStartEdge != null) {
- needsInvalidate |= drawStartEdge(canvas);
- }
-
- if (mEndEdge != null) {
- needsInvalidate |= drawEndEdge(canvas);
- }
-
- if (needsInvalidate) {
- ViewCompat.postInvalidateOnAnimation(this);
- }
- }
-
- @Override
- public void requestLayout() {
- if (!mInLayout && !mBlockLayoutRequests) {
- super.requestLayout();
- }
- }
-
- @Override
- public View getSelectedView() {
- if (mItemCount > 0 && mSelectedPosition >= 0) {
- return getChildAt(mSelectedPosition - mFirstPosition);
- } else {
- return null;
- }
- }
-
- @Override
- public void setSelection(int position) {
- setSelectionFromOffset(position, 0);
- }
-
- public void setSelectionFromOffset(int position, int offset) {
- if (mAdapter == null) {
- return;
- }
-
- if (!isInTouchMode()) {
- position = lookForSelectablePosition(position);
- if (position >= 0) {
- setNextSelectedPositionInt(position);
- }
- } else {
- mResurrectToPosition = position;
- }
-
- if (position >= 0) {
- mLayoutMode = LAYOUT_SPECIFIC;
-
- if (mIsVertical) {
- mSpecificStart = getPaddingTop() + offset;
- } else {
- mSpecificStart = getPaddingLeft() + offset;
- }
-
- if (mNeedSync) {
- mSyncPosition = position;
- mSyncRowId = mAdapter.getItemId(position);
- }
-
- requestLayout();
- }
- }
-
- public void scrollBy(int offset) {
- scrollListItemsBy(-offset);
- }
-
- /**
- * Smoothly scroll to the specified adapter position. The view will
- * scroll such that the indicated position is displayed.
- * @param position Scroll to this adapter position.
- */
- public void smoothScrollToPosition(int position) {
- if (mPositionScroller == null) {
- mPositionScroller = new PositionScroller();
- }
- mPositionScroller.start(position);
- }
-
- /**
- * Smoothly scroll to the specified adapter position. The view will scroll
- * such that the indicated position is displayed <code>offset</code> pixels from
- * the top/left edge of the view, according to the orientation. If this is
- * impossible, (e.g. the offset would scroll the first or last item beyond the boundaries
- * of the list) it will get as close as possible. The scroll will take
- * <code>duration</code> milliseconds to complete.
- *
- * @param position Position to scroll to
- * @param offset Desired distance in pixels of <code>position</code> from the top/left
- * of the view when scrolling is finished
- * @param duration Number of milliseconds to use for the scroll
- */
- public void smoothScrollToPositionFromOffset(int position, int offset, int duration) {
- if (mPositionScroller == null) {
- mPositionScroller = new PositionScroller();
- }
- mPositionScroller.startWithOffset(position, offset, duration);
- }
-
- /**
- * Smoothly scroll to the specified adapter position. The view will scroll
- * such that the indicated position is displayed <code>offset</code> pixels from
- * the top edge of the view. If this is impossible, (e.g. the offset would scroll
- * the first or last item beyond the boundaries of the list) it will get as close
- * as possible.
- *
- * @param position Position to scroll to
- * @param offset Desired distance in pixels of <code>position</code> from the top
- * of the view when scrolling is finished
- */
- public void smoothScrollToPositionFromOffset(int position, int offset) {
- if (mPositionScroller == null) {
- mPositionScroller = new PositionScroller();
- }
- mPositionScroller.startWithOffset(position, offset);
- }
-
- /**
- * Smoothly scroll to the specified adapter position. The view will
- * scroll such that the indicated position is displayed, but it will
- * stop early if scrolling further would scroll boundPosition out of
- * view.
- *
- * @param position Scroll to this adapter position.
- * @param boundPosition Do not scroll if it would move this adapter
- * position out of view.
- */
- public void smoothScrollToPosition(int position, int boundPosition) {
- if (mPositionScroller == null) {
- mPositionScroller = new PositionScroller();
- }
- mPositionScroller.start(position, boundPosition);
- }
-
- /**
- * Smoothly scroll by distance pixels over duration milliseconds.
- * @param distance Distance to scroll in pixels.
- * @param duration Duration of the scroll animation in milliseconds.
- */
- public void smoothScrollBy(int distance, int duration) {
- // No sense starting to scroll if we're not going anywhere
- final int firstPosition = mFirstPosition;
- final int childCount = getChildCount();
- final int lastPosition = firstPosition + childCount;
- final int start = getStartEdge();
- final int end = getEndEdge();
-
- if (distance == 0 || mItemCount == 0 || childCount == 0 ||
- (firstPosition == 0 && getChildStartEdge(getChildAt(0)) == start && distance < 0) ||
- (lastPosition == mItemCount &&
- getChildEndEdge(getChildAt(childCount - 1)) == end && distance > 0)) {
- finishSmoothScrolling();
- } else {
- mScroller.startScroll(0, 0,
- mIsVertical ? 0 : -distance,
- mIsVertical ? -distance : 0,
- duration);
-
- mLastTouchPos = 0;
-
- mTouchMode = TOUCH_MODE_FLINGING;
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
-
- ViewCompat.postInvalidateOnAnimation(this);
- }
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- // Dispatch in the normal way
- boolean handled = super.dispatchKeyEvent(event);
- if (!handled) {
- // If we didn't handle it...
- final View focused = getFocusedChild();
- if (focused != null && event.getAction() == KeyEvent.ACTION_DOWN) {
- // ... and our focused child didn't handle it
- // ... give it to ourselves so we can scroll if necessary
- handled = onKeyDown(event.getKeyCode(), event);
- }
- }
-
- return handled;
- }
-
- @Override
- protected void dispatchSetPressed(boolean pressed) {
- // Don't dispatch setPressed to our children. We call setPressed on ourselves to
- // get the selector in the right state, but we don't want to press each child.
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (mSelector == null) {
- useDefaultSelector();
- }
-
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-
- int childWidth = 0;
- int childHeight = 0;
-
- mItemCount = (mAdapter == null ? 0 : mAdapter.getCount());
- if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
- heightMode == MeasureSpec.UNSPECIFIED)) {
- final View child = obtainView(0, mIsScrap);
-
- final int secondaryMeasureSpec =
- (mIsVertical ? widthMeasureSpec : heightMeasureSpec);
-
- measureScrapChild(child, 0, secondaryMeasureSpec);
-
- childWidth = child.getMeasuredWidth();
- childHeight = child.getMeasuredHeight();
-
- if (recycleOnMeasure()) {
- mRecycler.addScrapView(child, -1);
- }
- }
-
- if (widthMode == MeasureSpec.UNSPECIFIED) {
- widthSize = getPaddingLeft() + getPaddingRight() + childWidth;
- if (mIsVertical) {
- widthSize += getVerticalScrollbarWidth();
- }
- }
-
- if (heightMode == MeasureSpec.UNSPECIFIED) {
- heightSize = getPaddingTop() + getPaddingBottom() + childHeight;
- if (!mIsVertical) {
- heightSize += getHorizontalScrollbarHeight();
- }
- }
-
- if (mIsVertical && heightMode == MeasureSpec.AT_MOST) {
- heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
- }
-
- if (!mIsVertical && widthMode == MeasureSpec.AT_MOST) {
- widthSize = measureWidthOfChildren(heightMeasureSpec, 0, NO_POSITION, widthSize, -1);
- }
-
- setMeasuredDimension(widthSize, heightSize);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- mInLayout = true;
-
- if (changed) {
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- getChildAt(i).forceLayout();
- }
-
- mRecycler.markChildrenDirty();
- }
-
- layoutChildren();
-
- mInLayout = false;
-
- final int width = r - l - getPaddingLeft() - getPaddingRight();
- final int height = b - t - getPaddingTop() - getPaddingBottom();
-
- if (mStartEdge != null && mEndEdge != null) {
- if (mIsVertical) {
- mStartEdge.setSize(width, height);
- mEndEdge.setSize(width, height);
- } else {
- mStartEdge.setSize(height, width);
- mEndEdge.setSize(height, width);
- }
- }
- }
-
- private void layoutChildren() {
- if (getWidth() == 0 || getHeight() == 0) {
- return;
- }
-
- final boolean blockLayoutRequests = mBlockLayoutRequests;
- if (!blockLayoutRequests) {
- mBlockLayoutRequests = true;
- } else {
- return;
- }
-
- try {
- invalidate();
-
- if (mAdapter == null) {
- resetState();
- return;
- }
-
- final int start = getStartEdge();
- final int end = getEndEdge();
-
- int childCount = getChildCount();
- int index = 0;
- int delta = 0;
-
- View focusLayoutRestoreView = null;
-
- View selected = null;
- View oldSelected = null;
- View newSelected = null;
- View oldFirstChild = null;
-
- switch (mLayoutMode) {
- case LAYOUT_SET_SELECTION:
- index = mNextSelectedPosition - mFirstPosition;
- if (index >= 0 && index < childCount) {
- newSelected = getChildAt(index);
- }
-
- break;
-
- case LAYOUT_FORCE_TOP:
- case LAYOUT_FORCE_BOTTOM:
- case LAYOUT_SPECIFIC:
- case LAYOUT_SYNC:
- break;
-
- case LAYOUT_MOVE_SELECTION:
- default:
- // Remember the previously selected view
- index = mSelectedPosition - mFirstPosition;
- if (index >= 0 && index < childCount) {
- oldSelected = getChildAt(index);
- }
-
- // Remember the previous first child
- oldFirstChild = getChildAt(0);
-
- if (mNextSelectedPosition >= 0) {
- delta = mNextSelectedPosition - mSelectedPosition;
- }
-
- // Caution: newSelected might be null
- newSelected = getChildAt(index + delta);
- }
-
- final boolean dataChanged = mDataChanged;
- if (dataChanged) {
- handleDataChanged();
- }
-
- // Handle the empty set by removing all views that are visible
- // and calling it a day
- if (mItemCount == 0) {
- resetState();
- return;
- } else if (mItemCount != mAdapter.getCount()) {
- throw new IllegalStateException("The content of the adapter has changed but "
- + "TwoWayView did not receive a notification. Make sure the content of "
- + "your adapter is not modified from a background thread, but only "
- + "from the UI thread. [in TwoWayView(" + getId() + ", " + getClass()
- + ") with Adapter(" + mAdapter.getClass() + ")]");
- }
-
- setSelectedPositionInt(mNextSelectedPosition);
-
- // Reset the focus restoration
- View focusLayoutRestoreDirectChild = null;
-
- // Pull all children into the RecycleBin.
- // These views will be reused if possible
- final int firstPosition = mFirstPosition;
- final RecycleBin recycleBin = mRecycler;
-
- if (dataChanged) {
- for (int i = 0; i < childCount; i++) {
- recycleBin.addScrapView(getChildAt(i), firstPosition + i);
- }
- } else {
- recycleBin.fillActiveViews(childCount, firstPosition);
- }
-
- // Take focus back to us temporarily to avoid the eventual
- // call to clear focus when removing the focused child below
- // from messing things up when ViewAncestor assigns focus back
- // to someone else.
- final View focusedChild = getFocusedChild();
- if (focusedChild != null) {
- // We can remember the focused view to restore after relayout if the
- // data hasn't changed, or if the focused position is a header or footer.
- if (!dataChanged) {
- focusLayoutRestoreDirectChild = focusedChild;
-
- // Remember the specific view that had focus
- focusLayoutRestoreView = findFocus();
- if (focusLayoutRestoreView != null) {
- // Tell it we are going to mess with it
- focusLayoutRestoreView.onStartTemporaryDetach();
- }
- }
-
- requestFocus();
- }
-
- // FIXME: We need a way to save current accessibility focus here
- // so that it can be restored after we re-attach the children on each
- // layout round.
-
- detachAllViewsFromParent();
-
- switch (mLayoutMode) {
- case LAYOUT_SET_SELECTION:
- if (newSelected != null) {
- final int newSelectedStart = getChildStartEdge(newSelected);
- selected = fillFromSelection(newSelectedStart, start, end);
- } else {
- selected = fillFromMiddle(start, end);
- }
-
- break;
-
- case LAYOUT_SYNC:
- selected = fillSpecific(mSyncPosition, mSpecificStart);
- break;
-
- case LAYOUT_FORCE_BOTTOM:
- selected = fillBefore(mItemCount - 1, end);
- adjustViewsStartOrEnd();
- break;
-
- case LAYOUT_FORCE_TOP:
- mFirstPosition = 0;
- selected = fillFromOffset(start);
- adjustViewsStartOrEnd();
- break;
-
- case LAYOUT_SPECIFIC:
- selected = fillSpecific(reconcileSelectedPosition(), mSpecificStart);
- break;
-
- case LAYOUT_MOVE_SELECTION:
- selected = moveSelection(oldSelected, newSelected, delta, start, end);
- break;
-
- default:
- if (childCount == 0) {
- final int position = lookForSelectablePosition(0);
- setSelectedPositionInt(position);
- selected = fillFromOffset(start);
- } else {
- if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
- int offset = start;
- if (oldSelected != null) {
- offset = getChildStartEdge(oldSelected);
- }
- selected = fillSpecific(mSelectedPosition, offset);
- } else if (mFirstPosition < mItemCount) {
- int offset = start;
- if (oldFirstChild != null) {
- offset = getChildStartEdge(oldFirstChild);
- }
-
- selected = fillSpecific(mFirstPosition, offset);
- } else {
- selected = fillSpecific(0, start);
- }
- }
-
- break;
-
- }
-
- recycleBin.scrapActiveViews();
-
- if (selected != null) {
- if (mItemsCanFocus && hasFocus() && !selected.hasFocus()) {
- final boolean focusWasTaken = (selected == focusLayoutRestoreDirectChild &&
- focusLayoutRestoreView != null &&
- focusLayoutRestoreView.requestFocus()) || selected.requestFocus();
-
- if (!focusWasTaken) {
- // Selected item didn't take focus, fine, but still want
- // to make sure something else outside of the selected view
- // has focus
- final View focused = getFocusedChild();
- if (focused != null) {
- focused.clearFocus();
- }
-
- positionSelector(INVALID_POSITION, selected);
- } else {
- selected.setSelected(false);
- mSelectorRect.setEmpty();
- }
- } else {
- positionSelector(INVALID_POSITION, selected);
- }
-
- mSelectedStart = getChildStartEdge(selected);
- } else {
- if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_DRAGGING) {
- View child = getChildAt(mMotionPosition - mFirstPosition);
-
- if (child != null) {
- positionSelector(mMotionPosition, child);
- }
- } else {
- mSelectedStart = 0;
- mSelectorRect.setEmpty();
- }
-
- // Even if there is not selected position, we may need to restore
- // focus (i.e. something focusable in touch mode)
- if (hasFocus() && focusLayoutRestoreView != null) {
- focusLayoutRestoreView.requestFocus();
- }
- }
-
- // Tell focus view we are done mucking with it, if it is still in
- // our view hierarchy.
- if (focusLayoutRestoreView != null
- && focusLayoutRestoreView.getWindowToken() != null) {
- focusLayoutRestoreView.onFinishTemporaryDetach();
- }
-
- mLayoutMode = LAYOUT_NORMAL;
- mDataChanged = false;
- mNeedSync = false;
-
- setNextSelectedPositionInt(mSelectedPosition);
- if (mItemCount > 0) {
- checkSelectionChanged();
- }
-
- invokeOnItemScrollListener();
- } finally {
- if (!blockLayoutRequests) {
- mBlockLayoutRequests = false;
- mDataChanged = false;
- }
- }
- }
-
- protected boolean recycleOnMeasure() {
- return true;
- }
-
- private void offsetChildren(int offset) {
- final int childCount = getChildCount();
-
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
-
- if (mIsVertical) {
- child.offsetTopAndBottom(offset);
- } else {
- child.offsetLeftAndRight(offset);
- }
- }
- }
-
- private View moveSelection(View oldSelected, View newSelected, int delta, int start,
- int end) {
- final int fadingEdgeLength = getFadingEdgeLength();
- final int selectedPosition = mSelectedPosition;
-
- final int oldSelectedStart = getChildStartEdge(oldSelected);
- final int oldSelectedEnd = getChildEndEdge(oldSelected);
-
- final int minStart = getMinSelectionPixel(start, fadingEdgeLength, selectedPosition);
- final int maxEnd = getMaxSelectionPixel(end, fadingEdgeLength, selectedPosition);
-
- View selected = null;
-
- if (delta > 0) {
- /*
- * Case 1: Scrolling down.
- */
-
- /*
- * Before After
- * | | | |
- * +-------+ +-------+
- * | A | | A |
- * | 1 | => +-------+
- * +-------+ | B |
- * | B | | 2 |
- * +-------+ +-------+
- * | | | |
- *
- * Try to keep the top of the previously selected item where it was.
- * oldSelected = A
- * selected = B
- */
-
- // Put oldSelected (A) where it belongs
- oldSelected = makeAndAddView(selectedPosition - 1, oldSelectedStart, true, false);
-
- final int itemMargin = mItemMargin;
-
- // Now put the new selection (B) below that
- selected = makeAndAddView(selectedPosition, oldSelectedEnd + itemMargin, true, true);
-
- final int selectedStart = getChildStartEdge(selected);
- final int selectedEnd = getChildEndEdge(selected);
-
- // Some of the newly selected item extends below the bottom of the list
- if (selectedEnd > end) {
- // Find space available above the selection into which we can scroll upwards
- final int spaceBefore = selectedStart - minStart;
-
- // Find space required to bring the bottom of the selected item fully into view
- final int spaceAfter = selectedEnd - maxEnd;
-
- // Don't scroll more than half the size of the list
- final int halfSpace = (end - start) / 2;
- int offset = Math.min(spaceBefore, spaceAfter);
- offset = Math.min(offset, halfSpace);
-
- if (mIsVertical) {
- oldSelected.offsetTopAndBottom(-offset);
- selected.offsetTopAndBottom(-offset);
- } else {
- oldSelected.offsetLeftAndRight(-offset);
- selected.offsetLeftAndRight(-offset);
- }
- }
-
- // Fill in views before and after
- fillBefore(mSelectedPosition - 2, selectedStart - itemMargin);
- adjustViewsStartOrEnd();
- fillAfter(mSelectedPosition + 1, selectedEnd + itemMargin);
- } else if (delta < 0) {
- /*
- * Case 2: Scrolling up.
- */
-
- /*
- * Before After
- * | | | |
- * +-------+ +-------+
- * | A | | A |
- * +-------+ => | 1 |
- * | B | +-------+
- * | 2 | | B |
- * +-------+ +-------+
- * | | | |
- *
- * Try to keep the top of the item about to become selected where it was.
- * newSelected = A
- * olSelected = B
- */
-
- if (newSelected != null) {
- // Try to position the top of newSel (A) where it was before it was selected
- final int newSelectedStart = getChildStartEdge(newSelected);
- selected = makeAndAddView(selectedPosition, newSelectedStart, true, true);
- } else {
- // If (A) was not on screen and so did not have a view, position
- // it above the oldSelected (B)
- selected = makeAndAddView(selectedPosition, oldSelectedStart, false, true);
- }
-
- final int selectedStart = getChildStartEdge(selected);
- final int selectedEnd = getChildEndEdge(selected);
-
- // Some of the newly selected item extends above the top of the list
- if (selectedStart < minStart) {
- // Find space required to bring the top of the selected item fully into view
- final int spaceBefore = minStart - selectedStart;
-
- // Find space available below the selection into which we can scroll downwards
- final int spaceAfter = maxEnd - selectedEnd;
-
- // Don't scroll more than half the height of the list
- final int halfSpace = (end - start) / 2;
- int offset = Math.min(spaceBefore, spaceAfter);
- offset = Math.min(offset, halfSpace);
-
- if (mIsVertical) {
- selected.offsetTopAndBottom(offset);
- } else {
- selected.offsetLeftAndRight(offset);
- }
- }
-
- // Fill in views above and below
- fillBeforeAndAfter(selected, selectedPosition);
- } else {
- /*
- * Case 3: Staying still
- */
-
- selected = makeAndAddView(selectedPosition, oldSelectedStart, true, true);
-
- final int selectedStart = getChildStartEdge(selected);
- final int selectedEnd = getChildEndEdge(selected);
-
- // We're staying still...
- if (oldSelectedStart < start) {
- // ... but the top of the old selection was off screen.
- // (This can happen if the data changes size out from under us)
- int newEnd = selectedEnd;
- if (newEnd < start + 20) {
- // Not enough visible -- bring it onscreen
- if (mIsVertical) {
- selected.offsetTopAndBottom(start - selectedStart);
- } else {
- selected.offsetLeftAndRight(start - selectedStart);
- }
- }
- }
-
- // Fill in views above and below
- fillBeforeAndAfter(selected, selectedPosition);
- }
-
- return selected;
- }
-
- void confirmCheckedPositionsById() {
- // Clear out the positional check states, we'll rebuild it below from IDs.
- mCheckStates.clear();
-
- for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
- final long id = mCheckedIdStates.keyAt(checkedIndex);
- final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
-
- final long lastPosId = mAdapter.getItemId(lastPos);
- if (id != lastPosId) {
- // Look around to see if the ID is nearby. If not, uncheck it.
- final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
- final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
- boolean found = false;
-
- for (int searchPos = start; searchPos < end; searchPos++) {
- final long searchId = mAdapter.getItemId(searchPos);
- if (id == searchId) {
- found = true;
- mCheckStates.put(searchPos, true);
- mCheckedIdStates.setValueAt(checkedIndex, searchPos);
- break;
- }
- }
-
- if (!found) {
- mCheckedIdStates.delete(id);
- checkedIndex--;
- mCheckedItemCount--;
- }
- } else {
- mCheckStates.put(lastPos, true);
- }
- }
- }
-
- private void handleDataChanged() {
- if (mChoiceMode != ChoiceMode.NONE && mAdapter != null && mAdapter.hasStableIds()) {
- confirmCheckedPositionsById();
- }
-
- mRecycler.clearTransientStateViews();
-
- final int itemCount = mItemCount;
- if (itemCount > 0) {
- int newPos;
- int selectablePos;
-
- // Find the row we are supposed to sync to
- if (mNeedSync) {
- // Update this first, since setNextSelectedPositionInt inspects it
- mNeedSync = false;
- mPendingSync = null;
-
- switch (mSyncMode) {
- case SYNC_SELECTED_POSITION:
- if (isInTouchMode()) {
- // We saved our state when not in touch mode. (We know this because
- // mSyncMode is SYNC_SELECTED_POSITION.) Now we are trying to
- // restore in touch mode. Just leave mSyncPosition as it is (possibly
- // adjusting if the available range changed) and return.
- mLayoutMode = LAYOUT_SYNC;
- mSyncPosition = Math.min(Math.max(0, mSyncPosition), itemCount - 1);
-
- return;
- } else {
- // See if we can find a position in the new data with the same
- // id as the old selection. This will change mSyncPosition.
- newPos = findSyncPosition();
- if (newPos >= 0) {
- // Found it. Now verify that new selection is still selectable
- selectablePos = lookForSelectablePosition(newPos, true);
- if (selectablePos == newPos) {
- // Same row id is selected
- mSyncPosition = newPos;
-
- if (mSyncSize == getSize()) {
- // If we are at the same height as when we saved state, try
- // to restore the scroll position too.
- mLayoutMode = LAYOUT_SYNC;
- } else {
- // We are not the same height as when the selection was saved, so
- // don't try to restore the exact position
- mLayoutMode = LAYOUT_SET_SELECTION;
- }
-
- // Restore selection
- setNextSelectedPositionInt(newPos);
- return;
- }
- }
- }
- break;
-
- case SYNC_FIRST_POSITION:
- // Leave mSyncPosition as it is -- just pin to available range
- mLayoutMode = LAYOUT_SYNC;
- mSyncPosition = Math.min(Math.max(0, mSyncPosition), itemCount - 1);
-
- return;
- }
- }
-
- if (!isInTouchMode()) {
- // We couldn't find matching data -- try to use the same position
- newPos = getSelectedItemPosition();
-
- // Pin position to the available range
- if (newPos >= itemCount) {
- newPos = itemCount - 1;
- }
- if (newPos < 0) {
- newPos = 0;
- }
-
- // Make sure we select something selectable -- first look down
- selectablePos = lookForSelectablePosition(newPos, true);
-
- if (selectablePos >= 0) {
- setNextSelectedPositionInt(selectablePos);
- return;
- } else {
- // Looking down didn't work -- try looking up
- selectablePos = lookForSelectablePosition(newPos, false);
- if (selectablePos >= 0) {
- setNextSelectedPositionInt(selectablePos);
- return;
- }
- }
- } else {
- // We already know where we want to resurrect the selection
- if (mResurrectToPosition >= 0) {
- return;
- }
- }
- }
-
- // Nothing is selected. Give up and reset everything.
- mLayoutMode = LAYOUT_FORCE_TOP;
- mSelectedPosition = INVALID_POSITION;
- mSelectedRowId = INVALID_ROW_ID;
- mNextSelectedPosition = INVALID_POSITION;
- mNextSelectedRowId = INVALID_ROW_ID;
- mNeedSync = false;
- mPendingSync = null;
- mSelectorPosition = INVALID_POSITION;
-
- checkSelectionChanged();
- }
-
- private int reconcileSelectedPosition() {
- int position = mSelectedPosition;
- if (position < 0) {
- position = mResurrectToPosition;
- }
-
- position = Math.max(0, position);
- position = Math.min(position, mItemCount - 1);
-
- return position;
- }
-
- boolean resurrectSelection() {
- final int childCount = getChildCount();
- if (childCount <= 0) {
- return false;
- }
-
- int selectedStart = 0;
- int selectedPosition;
-
- int start = getStartEdge();
- int end = getEndEdge();
-
- final int firstPosition = mFirstPosition;
- final int toPosition = mResurrectToPosition;
- boolean down = true;
-
- if (toPosition >= firstPosition && toPosition < firstPosition + childCount) {
- selectedPosition = toPosition;
-
- final View selected = getChildAt(selectedPosition - mFirstPosition);
- selectedStart = getChildStartEdge(selected);
-
- final int selectedEnd = getChildEndEdge(selected);
-
- // We are scrolled, don't get in the fade
- if (selectedStart < start) {
- selectedStart = start + getFadingEdgeLength();
- } else if (selectedEnd > end) {
- selectedStart = end - getChildMeasuredSize(selected) - getFadingEdgeLength();
- }
- } else if (toPosition < firstPosition) {
- // Default to selecting whatever is first
- selectedPosition = firstPosition;
-
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- final int childStart = getChildStartEdge(child);
-
- if (i == 0) {
- // Remember the position of the first item
- selectedStart = childStart;
-
- // See if we are scrolled at all
- if (firstPosition > 0 || childStart < start) {
- // If we are scrolled, don't select anything that is
- // in the fade region
- start += getFadingEdgeLength();
- }
- }
-
- if (childStart >= start) {
- // Found a view whose top is fully visible
- selectedPosition = firstPosition + i;
- selectedStart = childStart;
- break;
- }
- }
- } else {
- final int itemCount = mItemCount;
- selectedPosition = firstPosition + childCount - 1;
- down = false;
-
- for (int i = childCount - 1; i >= 0; i--) {
- final View child = getChildAt(i);
- final int childStart = getChildStartEdge(child);
- final int childEnd = getChildEndEdge(child);
-
- if (i == childCount - 1) {
- selectedStart = childStart;
-
- if (firstPosition + childCount < itemCount || childEnd > end) {
- end -= getFadingEdgeLength();
- }
- }
-
- if (childEnd <= end) {
- selectedPosition = firstPosition + i;
- selectedStart = childStart;
- break;
- }
- }
- }
-
- mResurrectToPosition = INVALID_POSITION;
-
- finishSmoothScrolling();
-
- mTouchMode = TOUCH_MODE_REST;
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
-
- mSpecificStart = selectedStart;
-
- selectedPosition = lookForSelectablePosition(selectedPosition, down);
- if (selectedPosition >= firstPosition && selectedPosition <= getLastVisiblePosition()) {
- mLayoutMode = LAYOUT_SPECIFIC;
- updateSelectorState();
- setSelectionInt(selectedPosition);
- invokeOnItemScrollListener();
- } else {
- selectedPosition = INVALID_POSITION;
- }
-
- return selectedPosition >= 0;
- }
-
- /**
- * If there is a selection returns false.
- * Otherwise resurrects the selection and returns true if resurrected.
- */
- boolean resurrectSelectionIfNeeded() {
- if (mSelectedPosition < 0 && resurrectSelection()) {
- updateSelectorState();
- return true;
- }
-
- return false;
- }
-
- private int getChildWidthMeasureSpec(LayoutParams lp) {
- if (!mIsVertical && lp.width == LayoutParams.WRAP_CONTENT) {
- return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- } else if (mIsVertical) {
- final int maxWidth = getWidth() - getPaddingLeft() - getPaddingRight();
- return MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY);
- } else {
- return MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
- }
- }
-
- private int getChildHeightMeasureSpec(LayoutParams lp) {
- if (mIsVertical && lp.height == LayoutParams.WRAP_CONTENT) {
- return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- } else if (!mIsVertical) {
- final int maxHeight = getHeight() - getPaddingTop() - getPaddingBottom();
- return MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY);
- } else {
- return MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
- }
- }
-
- private void measureChild(View child) {
- measureChild(child, (LayoutParams) child.getLayoutParams());
- }
-
- private void measureChild(View child, LayoutParams lp) {
- final int widthSpec = getChildWidthMeasureSpec(lp);
- final int heightSpec = getChildHeightMeasureSpec(lp);
- child.measure(widthSpec, heightSpec);
- }
-
- private void relayoutMeasuredChild(View child) {
- final int w = child.getMeasuredWidth();
- final int h = child.getMeasuredHeight();
-
- final int childLeft = getPaddingLeft();
- final int childRight = childLeft + w;
- final int childTop = child.getTop();
- final int childBottom = childTop + h;
-
- child.layout(childLeft, childTop, childRight, childBottom);
- }
-
- private void measureScrapChild(View scrapChild, int position, int secondaryMeasureSpec) {
- LayoutParams lp = (LayoutParams) scrapChild.getLayoutParams();
- if (lp == null) {
- lp = generateDefaultLayoutParams();
- scrapChild.setLayoutParams(lp);
- }
-
- lp.viewType = mAdapter.getItemViewType(position);
- lp.forceAdd = true;
-
- final int widthMeasureSpec;
- final int heightMeasureSpec;
- if (mIsVertical) {
- widthMeasureSpec = secondaryMeasureSpec;
- heightMeasureSpec = getChildHeightMeasureSpec(lp);
- } else {
- widthMeasureSpec = getChildWidthMeasureSpec(lp);
- heightMeasureSpec = secondaryMeasureSpec;
- }
-
- scrapChild.measure(widthMeasureSpec, heightMeasureSpec);
- }
-
- /**
- * Measures the height of the given range of children (inclusive) and
- * returns the height with this TwoWayView's padding and item margin heights
- * included. If maxHeight is provided, the measuring will stop when the
- * current height reaches maxHeight.
- *
- * @param widthMeasureSpec The width measure spec to be given to a child's
- * {@link View#measure(int, int)}.
- * @param startPosition The position of the first child to be shown.
- * @param endPosition The (inclusive) position of the last child to be
- * shown. Specify {@link #NO_POSITION} if the last child should be
- * the last available child from the adapter.
- * @param maxHeight The maximum height that will be returned (if all the
- * children don't fit in this value, this value will be
- * returned).
- * @param disallowPartialChildPosition In general, whether the returned
- * height should only contain entire children. This is more
- * powerful--it is the first inclusive position at which partial
- * children will not be allowed. Example: it looks nice to have
- * at least 3 completely visible children, and in portrait this
- * will most likely fit; but in landscape there could be times
- * when even 2 children can not be completely shown, so a value
- * of 2 (remember, inclusive) would be good (assuming
- * startPosition is 0).
- * @return The height of this TwoWayView with the given children.
- */
- private int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
- final int maxHeight, int disallowPartialChildPosition) {
-
- final int paddingTop = getPaddingTop();
- final int paddingBottom = getPaddingBottom();
-
- final ListAdapter adapter = mAdapter;
- if (adapter == null) {
- return paddingTop + paddingBottom;
- }
-
- // Include the padding of the list
- int returnedHeight = paddingTop + paddingBottom;
- final int itemMargin = mItemMargin;
-
- // The previous height value that was less than maxHeight and contained
- // no partial children
- int prevHeightWithoutPartialChild = 0;
- int i;
- View child;
-
- // mItemCount - 1 since endPosition parameter is inclusive
- endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
- final RecycleBin recycleBin = mRecycler;
- final boolean shouldRecycle = recycleOnMeasure();
- final boolean[] isScrap = mIsScrap;
-
- for (i = startPosition; i <= endPosition; ++i) {
- child = obtainView(i, isScrap);
-
- measureScrapChild(child, i, widthMeasureSpec);
-
- if (i > 0) {
- // Count the item margin for all but one child
- returnedHeight += itemMargin;
- }
-
- // Recycle the view before we possibly return from the method
- if (shouldRecycle) {
- recycleBin.addScrapView(child, -1);
- }
-
- returnedHeight += child.getMeasuredHeight();
-
- if (returnedHeight >= maxHeight) {
- // We went over, figure out which height to return. If returnedHeight > maxHeight,
- // then the i'th position did not fit completely.
- return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
- && (i > disallowPartialChildPosition) // We've past the min pos
- && (prevHeightWithoutPartialChild > 0) // We have a prev height
- && (returnedHeight != maxHeight) // i'th child did not fit completely
- ? prevHeightWithoutPartialChild
- : maxHeight;
- }
-
- if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
- prevHeightWithoutPartialChild = returnedHeight;
- }
- }
-
- // At this point, we went through the range of children, and they each
- // completely fit, so return the returnedHeight
- return returnedHeight;
- }
-
- /**
- * Measures the width of the given range of children (inclusive) and
- * returns the width with this TwoWayView's padding and item margin widths
- * included. If maxWidth is provided, the measuring will stop when the
- * current width reaches maxWidth.
- *
- * @param heightMeasureSpec The height measure spec to be given to a child's
- * {@link View#measure(int, int)}.
- * @param startPosition The position of the first child to be shown.
- * @param endPosition The (inclusive) position of the last child to be
- * shown. Specify {@link #NO_POSITION} if the last child should be
- * the last available child from the adapter.
- * @param maxWidth The maximum width that will be returned (if all the
- * children don't fit in this value, this value will be
- * returned).
- * @param disallowPartialChildPosition In general, whether the returned
- * width should only contain entire children. This is more
- * powerful--it is the first inclusive position at which partial
- * children will not be allowed. Example: it looks nice to have
- * at least 3 completely visible children, and in portrait this
- * will most likely fit; but in landscape there could be times
- * when even 2 children can not be completely shown, so a value
- * of 2 (remember, inclusive) would be good (assuming
- * startPosition is 0).
- * @return The width of this TwoWayView with the given children.
- */
- private int measureWidthOfChildren(int heightMeasureSpec, int startPosition, int endPosition,
- final int maxWidth, int disallowPartialChildPosition) {
-
- final int paddingLeft = getPaddingLeft();
- final int paddingRight = getPaddingRight();
-
- final ListAdapter adapter = mAdapter;
- if (adapter == null) {
- return paddingLeft + paddingRight;
- }
-
- // Include the padding of the list
- int returnedWidth = paddingLeft + paddingRight;
- final int itemMargin = mItemMargin;
-
- // The previous height value that was less than maxHeight and contained
- // no partial children
- int prevWidthWithoutPartialChild = 0;
- int i;
- View child;
-
- // mItemCount - 1 since endPosition parameter is inclusive
- endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
- final RecycleBin recycleBin = mRecycler;
- final boolean shouldRecycle = recycleOnMeasure();
- final boolean[] isScrap = mIsScrap;
-
- for (i = startPosition; i <= endPosition; ++i) {
- child = obtainView(i, isScrap);
-
- measureScrapChild(child, i, heightMeasureSpec);
-
- if (i > 0) {
- // Count the item margin for all but one child
- returnedWidth += itemMargin;
- }
-
- // Recycle the view before we possibly return from the method
- if (shouldRecycle) {
- recycleBin.addScrapView(child, -1);
- }
-
- returnedWidth += child.getMeasuredWidth();
-
- if (returnedWidth >= maxWidth) {
- // We went over, figure out which width to return. If returnedWidth > maxWidth,
- // then the i'th position did not fit completely.
- return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
- && (i > disallowPartialChildPosition) // We've past the min pos
- && (prevWidthWithoutPartialChild > 0) // We have a prev width
- && (returnedWidth != maxWidth) // i'th child did not fit completely
- ? prevWidthWithoutPartialChild
- : maxWidth;
- }
-
- if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
- prevWidthWithoutPartialChild = returnedWidth;
- }
- }
-
- // At this point, we went through the range of children, and they each
- // completely fit, so return the returnedWidth
- return returnedWidth;
- }
-
- private View makeAndAddView(int position, int offset, boolean flow, boolean selected) {
- final int top;
- final int left;
-
- // Compensate item margin on the first item of the list if the item margin
- // is negative to avoid incorrect offset for the very first child.
- if (mIsVertical) {
- top = offset;
- left = getPaddingLeft();
- } else {
- top = getPaddingTop();
- left = offset;
- }
-
- if (!mDataChanged) {
- // Try to use an existing view for this position
- final View activeChild = mRecycler.getActiveView(position);
- if (activeChild != null) {
- // Found it -- we're using an existing child
- // This just needs to be positioned
- setupChild(activeChild, position, top, left, flow, selected, true);
-
- return activeChild;
- }
- }
-
- // Make a new view for this position, or convert an unused view if possible
- final View child = obtainView(position, mIsScrap);
-
- // This needs to be positioned and measured
- setupChild(child, position, top, left, flow, selected, mIsScrap[0]);
-
- return child;
- }
-
- @TargetApi(11)
- private void setupChild(View child, int position, int top, int left,
- boolean flow, boolean selected, boolean recycled) {
- final boolean isSelected = selected && shouldShowSelector();
- final boolean updateChildSelected = isSelected != child.isSelected();
- final int touchMode = mTouchMode;
-
- final boolean isPressed = touchMode > TOUCH_MODE_DOWN && touchMode < TOUCH_MODE_DRAGGING &&
- mMotionPosition == position;
-
- final boolean updateChildPressed = isPressed != child.isPressed();
- final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
-
- // Respect layout params that are already in the view. Otherwise make some up...
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (lp == null) {
- lp = generateDefaultLayoutParams();
- }
-
- lp.viewType = mAdapter.getItemViewType(position);
-
- if (recycled && !lp.forceAdd) {
- attachViewToParent(child, (flow ? -1 : 0), lp);
- } else {
- lp.forceAdd = false;
- addViewInLayout(child, (flow ? -1 : 0), lp, true);
- }
-
- if (updateChildSelected) {
- child.setSelected(isSelected);
- }
-
- if (updateChildPressed) {
- child.setPressed(isPressed);
- }
-
- if (mChoiceMode != ChoiceMode.NONE && mCheckStates != null) {
- if (child instanceof Checkable) {
- ((Checkable) child).setChecked(mCheckStates.get(position));
- } else if (Build.VERSION.SDK_INT >= HONEYCOMB) {
- child.setActivated(mCheckStates.get(position));
- }
- }
-
- if (needToMeasure) {
- measureChild(child, lp);
- } else {
- cleanupLayoutState(child);
- }
-
- final int w = child.getMeasuredWidth();
- final int h = child.getMeasuredHeight();
-
- final int childTop = (mIsVertical && !flow ? top - h : top);
- final int childLeft = (!mIsVertical && !flow ? left - w : left);
-
- if (needToMeasure) {
- final int childRight = childLeft + w;
- final int childBottom = childTop + h;
-
- child.layout(childLeft, childTop, childRight, childBottom);
- } else {
- child.offsetLeftAndRight(childLeft - child.getLeft());
- child.offsetTopAndBottom(childTop - child.getTop());
- }
- }
-
- void fillGap(boolean down) {
- final int childCount = getChildCount();
-
- if (down) {
- final int start = getStartEdge();
- final int lastEnd = getChildEndEdge(getChildAt(childCount - 1));
- final int offset = (childCount > 0 ? lastEnd + mItemMargin : start);
- fillAfter(mFirstPosition + childCount, offset);
- correctTooHigh(getChildCount());
- } else {
- final int end = getEndEdge();
- final int firstStart = getChildStartEdge(getChildAt(0));
- final int offset = (childCount > 0 ? firstStart - mItemMargin : end);
- fillBefore(mFirstPosition - 1, offset);
- correctTooLow(getChildCount());
- }
- }
-
- private View fillBefore(int pos, int nextOffset) {
- View selectedView = null;
-
- final int start = getStartEdge();
-
- while (nextOffset > start && pos >= 0) {
- boolean isSelected = (pos == mSelectedPosition);
-
- View child = makeAndAddView(pos, nextOffset, false, isSelected);
- nextOffset = getChildStartEdge(child) - mItemMargin;
-
- if (isSelected) {
- selectedView = child;
- }
-
- pos--;
- }
-
- mFirstPosition = pos + 1;
-
- return selectedView;
- }
-
- private View fillAfter(int pos, int nextOffset) {
- View selectedView = null;
-
- final int end = getEndEdge();
-
- while (nextOffset < end && pos < mItemCount) {
- boolean selected = (pos == mSelectedPosition);
-
- View child = makeAndAddView(pos, nextOffset, true, selected);
- nextOffset = getChildEndEdge(child) + mItemMargin;
-
- if (selected) {
- selectedView = child;
- }
-
- pos++;
- }
-
- return selectedView;
- }
-
- private View fillSpecific(int position, int offset) {
- final boolean tempIsSelected = (position == mSelectedPosition);
- View temp = makeAndAddView(position, offset, true, tempIsSelected);
-
- // Possibly changed again in fillBefore if we add rows above this one.
- mFirstPosition = position;
-
- final int offsetBefore = getChildStartEdge(temp) - mItemMargin;
- final View before = fillBefore(position - 1, offsetBefore);
-
- // This will correct for the top of the first view not touching the top of the list
- adjustViewsStartOrEnd();
-
- final int offsetAfter = getChildEndEdge(temp) + mItemMargin;
- final View after = fillAfter(position + 1, offsetAfter);
-
- final int childCount = getChildCount();
- if (childCount > 0) {
- correctTooHigh(childCount);
- }
-
- if (tempIsSelected) {
- return temp;
- } else if (before != null) {
- return before;
- } else {
- return after;
- }
- }
-
- private View fillFromOffset(int nextOffset) {
- mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
- mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
-
- if (mFirstPosition < 0) {
- mFirstPosition = 0;
- }
-
- return fillAfter(mFirstPosition, nextOffset);
- }
-
- private View fillFromMiddle(int start, int end) {
- final int size = end - start;
- int position = reconcileSelectedPosition();
-
- View selected = makeAndAddView(position, start, true, true);
- mFirstPosition = position;
-
- if (mIsVertical) {
- int selectedHeight = selected.getMeasuredHeight();
- if (selectedHeight <= size) {
- selected.offsetTopAndBottom((size - selectedHeight) / 2);
- }
- } else {
- int selectedWidth = selected.getMeasuredWidth();
- if (selectedWidth <= size) {
- selected.offsetLeftAndRight((size - selectedWidth) / 2);
- }
- }
-
- fillBeforeAndAfter(selected, position);
- correctTooHigh(getChildCount());
-
- return selected;
- }
-
- private void fillBeforeAndAfter(View selected, int position) {
- final int offsetBefore = getChildStartEdge(selected) + mItemMargin;
- fillBefore(position - 1, offsetBefore);
-
- adjustViewsStartOrEnd();
-
- final int offsetAfter = getChildEndEdge(selected) + mItemMargin;
- fillAfter(position + 1, offsetAfter);
- }
-
- private View fillFromSelection(int selectedTop, int start, int end) {
- int fadingEdgeLength = getFadingEdgeLength();
- final int selectedPosition = mSelectedPosition;
-
- final int minStart = getMinSelectionPixel(start, fadingEdgeLength, selectedPosition);
- final int maxEnd = getMaxSelectionPixel(end, fadingEdgeLength, selectedPosition);
-
- View selected = makeAndAddView(selectedPosition, selectedTop, true, true);
-
- final int selectedStart = getChildStartEdge(selected);
- final int selectedEnd = getChildEndEdge(selected);
-
- // Some of the newly selected item extends below the bottom of the list
- if (selectedEnd > maxEnd) {
- // Find space available above the selection into which we can scroll
- // upwards
- final int spaceAbove = selectedStart - minStart;
-
- // Find space required to bring the bottom of the selected item
- // fully into view
- final int spaceBelow = selectedEnd - maxEnd;
-
- final int offset = Math.min(spaceAbove, spaceBelow);
-
- // Now offset the selected item to get it into view
- selected.offsetTopAndBottom(-offset);
- } else if (selectedStart < minStart) {
- // Find space required to bring the top of the selected item fully
- // into view
- final int spaceAbove = minStart - selectedStart;
-
- // Find space available below the selection into which we can scroll
- // downwards
- final int spaceBelow = maxEnd - selectedEnd;
-
- final int offset = Math.min(spaceAbove, spaceBelow);
-
- // Offset the selected item to get it into view
- selected.offsetTopAndBottom(offset);
- }
-
- // Fill in views above and below
- fillBeforeAndAfter(selected, selectedPosition);
- correctTooHigh(getChildCount());
-
- return selected;
- }
-
- private void correctTooHigh(int childCount) {
- // First see if the last item is visible. If it is not, it is OK for the
- // top of the list to be pushed up.
- final int lastPosition = mFirstPosition + childCount - 1;
- if (lastPosition != mItemCount - 1 || childCount == 0) {
- return;
- }
-
- // Get the last child end edge
- final int lastEnd = getChildEndEdge(getChildAt(childCount - 1));
-
- // This is bottom of our drawable area
- final int start = getStartEdge();
- final int end = getEndEdge();
-
- // This is how far the end edge of the last view is from the end of the
- // drawable area
- int endOffset = end - lastEnd;
-
- View firstChild = getChildAt(0);
- int firstStart = getChildStartEdge(firstChild);
-
- // Make sure we are 1) Too high, and 2) Either there are more rows above the
- // first row or the first row is scrolled off the top of the drawable area
- if (endOffset > 0 && (mFirstPosition > 0 || firstStart < start)) {
- if (mFirstPosition == 0) {
- // Don't pull the top too far down
- endOffset = Math.min(endOffset, start - firstStart);
- }
-
- // Move everything down
- offsetChildren(endOffset);
-
- if (mFirstPosition > 0) {
- firstStart = getChildStartEdge(firstChild);
-
- // Fill the gap that was opened above mFirstPosition with more rows, if
- // possible
- fillBefore(mFirstPosition - 1, firstStart - mItemMargin);
-
- // Close up the remaining gap
- adjustViewsStartOrEnd();
- }
- }
- }
-
- private void correctTooLow(int childCount) {
- // First see if the first item is visible. If it is not, it is OK for the
- // bottom of the list to be pushed down.
- if (mFirstPosition != 0 || childCount == 0) {
- return;
- }
-
- final int firstStart = getChildStartEdge(getChildAt(0));
-
- final int start = getStartEdge();
- final int end = getEndEdge();
-
- // This is how far the start edge of the first view is from the start of the
- // drawable area
- int startOffset = firstStart - start;
-
- View last = getChildAt(childCount - 1);
- int lastEnd = getChildEndEdge(last);
-
- int lastPosition = mFirstPosition + childCount - 1;
-
- // Make sure we are 1) Too low, and 2) Either there are more columns/rows below the
- // last column/row or the last column/row is scrolled off the end of the
- // drawable area
- if (startOffset > 0) {
- if (lastPosition < mItemCount - 1 || lastEnd > end) {
- if (lastPosition == mItemCount - 1) {
- // Don't pull the bottom too far up
- startOffset = Math.min(startOffset, lastEnd - end);
- }
-
- // Move everything up
- offsetChildren(-startOffset);
-
- if (lastPosition < mItemCount - 1) {
- lastEnd = getChildEndEdge(last);
-
- // Fill the gap that was opened below the last position with more rows, if
- // possible
- fillAfter(lastPosition + 1, lastEnd + mItemMargin);
-
- // Close up the remaining gap
- adjustViewsStartOrEnd();
- }
- } else if (lastPosition == mItemCount - 1) {
- adjustViewsStartOrEnd();
- }
- }
- }
-
- private void adjustViewsStartOrEnd() {
- if (getChildCount() == 0) {
- return;
- }
-
- int delta = getChildStartEdge(getChildAt(0)) - getStartEdge();
-
- // If item margin is negative we shouldn't apply it in the
- // first item of the list to avoid offsetting it incorrectly.
- if (mItemMargin >= 0 || mFirstPosition != 0) {
- delta -= mItemMargin;
- }
-
- if (delta < 0) {
- // We only are looking to see if we are too low, not too high
- delta = 0;
- }
-
- if (delta != 0) {
- offsetChildren(-delta);
- }
- }
-
- @TargetApi(14)
- private SparseBooleanArray cloneCheckStates() {
- if (mCheckStates == null) {
- return null;
- }
-
- SparseBooleanArray checkedStates;
-
- if (Build.VERSION.SDK_INT >= 14) {
- checkedStates = mCheckStates.clone();
- } else {
- checkedStates = new SparseBooleanArray();
-
- for (int i = 0; i < mCheckStates.size(); i++) {
- checkedStates.put(mCheckStates.keyAt(i), mCheckStates.valueAt(i));
- }
- }
-
- return checkedStates;
- }
-
- private int findSyncPosition() {
- int itemCount = mItemCount;
-
- if (itemCount == 0) {
- return INVALID_POSITION;
- }
-
- final long idToMatch = mSyncRowId;
-
- // If there isn't a selection don't hunt for it
- if (idToMatch == INVALID_ROW_ID) {
- return INVALID_POSITION;
- }
-
- // Pin seed to reasonable values
- int seed = mSyncPosition;
- seed = Math.max(0, seed);
- seed = Math.min(itemCount - 1, seed);
-
- long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
-
- long rowId;
-
- // first position scanned so far
- int first = seed;
-
- // last position scanned so far
- int last = seed;
-
- // True if we should move down on the next iteration
- boolean next = false;
-
- // True when we have looked at the first item in the data
- boolean hitFirst;
-
- // True when we have looked at the last item in the data
- boolean hitLast;
-
- // Get the item ID locally (instead of getItemIdAtPosition), so
- // we need the adapter
- final ListAdapter adapter = mAdapter;
- if (adapter == null) {
- return INVALID_POSITION;
- }
-
- while (SystemClock.uptimeMillis() <= endTime) {
- rowId = adapter.getItemId(seed);
- if (rowId == idToMatch) {
- // Found it!
- return seed;
- }
-
- hitLast = (last == itemCount - 1);
- hitFirst = (first == 0);
-
- if (hitLast && hitFirst) {
- // Looked at everything
- break;
- }
-
- if (hitFirst || (next && !hitLast)) {
- // Either we hit the top, or we are trying to move down
- last++;
- seed = last;
-
- // Try going up next time
- next = false;
- } else if (hitLast || (!next && !hitFirst)) {
- // Either we hit the bottom, or we are trying to move up
- first--;
- seed = first;
-
- // Try going down next time
- next = true;
- }
- }
-
- return INVALID_POSITION;
- }
-
- @TargetApi(16)
- private View obtainView(int position, boolean[] isScrap) {
- isScrap[0] = false;
-
- View scrapView = mRecycler.getTransientStateView(position);
- if (scrapView != null) {
- return scrapView;
- }
-
- scrapView = mRecycler.getScrapView(position);
-
- final View child;
- if (scrapView != null) {
- child = mAdapter.getView(position, scrapView, this);
-
- if (child != scrapView) {
- mRecycler.addScrapView(scrapView, position);
- } else {
- isScrap[0] = true;
- }
- } else {
- child = mAdapter.getView(position, null, this);
- }
-
- if (ViewCompat.getImportantForAccessibility(child) == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
- ViewCompat.setImportantForAccessibility(child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
- }
-
- if (mHasStableIds) {
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
- if (lp == null) {
- lp = generateDefaultLayoutParams();
- } else if (!checkLayoutParams(lp)) {
- lp = generateLayoutParams(lp);
- }
-
- lp.id = mAdapter.getItemId(position);
-
- child.setLayoutParams(lp);
- }
-
- if (mAccessibilityDelegate == null) {
- mAccessibilityDelegate = new ListItemAccessibilityDelegate();
- }
-
- ViewCompat.setAccessibilityDelegate(child, mAccessibilityDelegate);
-
- return child;
- }
-
- void resetState() {
- mScroller.forceFinished(true);
-
- removeAllViewsInLayout();
-
- mSelectedStart = 0;
- mFirstPosition = 0;
- mDataChanged = false;
- mNeedSync = false;
- mPendingSync = null;
- mOldSelectedPosition = INVALID_POSITION;
- mOldSelectedRowId = INVALID_ROW_ID;
-
- mOverScroll = 0;
-
- setSelectedPositionInt(INVALID_POSITION);
- setNextSelectedPositionInt(INVALID_POSITION);
-
- mSelectorPosition = INVALID_POSITION;
- mSelectorRect.setEmpty();
-
- invalidate();
- }
-
- private void rememberSyncState() {
- if (getChildCount() == 0) {
- return;
- }
-
- mNeedSync = true;
-
- if (mSelectedPosition >= 0) {
- View child = getChildAt(mSelectedPosition - mFirstPosition);
-
- mSyncRowId = mNextSelectedRowId;
- mSyncPosition = mNextSelectedPosition;
-
- if (child != null) {
- mSpecificStart = getChildStartEdge(child);
- }
-
- mSyncMode = SYNC_SELECTED_POSITION;
- } else {
- // Sync the based on the offset of the first view
- View child = getChildAt(0);
- ListAdapter adapter = getAdapter();
-
- if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
- mSyncRowId = adapter.getItemId(mFirstPosition);
- } else {
- mSyncRowId = NO_ID;
- }
-
- mSyncPosition = mFirstPosition;
-
- if (child != null) {
- mSpecificStart = getChildStartEdge(child);
- }
-
- mSyncMode = SYNC_FIRST_POSITION;
- }
- }
-
- private ContextMenuInfo createContextMenuInfo(View view, int position, long id) {
- return new AdapterContextMenuInfo(view, position, id);
- }
-
- @TargetApi(11)
- private void updateOnScreenCheckedViews() {
- final int firstPos = mFirstPosition;
- final int count = getChildCount();
-
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- final int position = firstPos + i;
-
- if (child instanceof Checkable) {
- ((Checkable) child).setChecked(mCheckStates.get(position));
- } else if (Build.VERSION.SDK_INT >= HONEYCOMB) {
- child.setActivated(mCheckStates.get(position));
- }
- }
- }
-
- @Override
- public boolean performItemClick(View view, int position, long id) {
- boolean checkedStateChanged = false;
-
- if (mChoiceMode == ChoiceMode.MULTIPLE) {
- boolean checked = !mCheckStates.get(position, false);
- mCheckStates.put(position, checked);
-
- if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
- if (checked) {
- mCheckedIdStates.put(mAdapter.getItemId(position), position);
- } else {
- mCheckedIdStates.delete(mAdapter.getItemId(position));
- }
- }
-
- if (checked) {
- mCheckedItemCount++;
- } else {
- mCheckedItemCount--;
- }
-
- checkedStateChanged = true;
- } else if (mChoiceMode == ChoiceMode.SINGLE) {
- boolean checked = !mCheckStates.get(position, false);
- if (checked) {
- mCheckStates.clear();
- mCheckStates.put(position, true);
-
- if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
- mCheckedIdStates.clear();
- mCheckedIdStates.put(mAdapter.getItemId(position), position);
- }
-
- mCheckedItemCount = 1;
- } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
- mCheckedItemCount = 0;
- }
-
- checkedStateChanged = true;
- }
-
- if (checkedStateChanged) {
- updateOnScreenCheckedViews();
- }
-
- return super.performItemClick(view, position, id);
- }
-
- private boolean performLongPress(final View child,
- final int longPressPosition, final long longPressId) {
- // CHOICE_MODE_MULTIPLE_MODAL takes over long press.
- boolean handled = false;
-
- OnItemLongClickListener listener = getOnItemLongClickListener();
- if (listener != null) {
- handled = listener.onItemLongClick(TwoWayView.this, child,
- longPressPosition, longPressId);
- }
-
- if (!handled) {
- mContextMenuInfo = createContextMenuInfo(child, longPressPosition, longPressId);
- handled = super.showContextMenuForChild(TwoWayView.this);
- }
-
- if (handled) {
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- }
-
- return handled;
- }
-
- @Override
- protected LayoutParams generateDefaultLayoutParams() {
- if (mIsVertical) {
- return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
- } else {
- return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
- }
- }
-
- @Override
- protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
- return new LayoutParams(lp);
- }
-
- @Override
- protected boolean checkLayoutParams(ViewGroup.LayoutParams lp) {
- return lp instanceof LayoutParams;
- }
-
- @Override
- public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
- return new LayoutParams(getContext(), attrs);
- }
-
- @Override
- protected ContextMenuInfo getContextMenuInfo() {
- return mContextMenuInfo;
- }
-
- @Override
- public Parcelable onSaveInstanceState() {
- Parcelable superState = super.onSaveInstanceState();
- SavedState ss = new SavedState(superState);
-
- if (mPendingSync != null) {
- ss.selectedId = mPendingSync.selectedId;
- ss.firstId = mPendingSync.firstId;
- ss.viewStart = mPendingSync.viewStart;
- ss.position = mPendingSync.position;
- ss.size = mPendingSync.size;
-
- return ss;
- }
-
- boolean haveChildren = (getChildCount() > 0 && mItemCount > 0);
- long selectedId = getSelectedItemId();
- ss.selectedId = selectedId;
- ss.size = getSize();
-
- if (selectedId >= 0) {
- ss.viewStart = mSelectedStart;
- ss.position = getSelectedItemPosition();
- ss.firstId = INVALID_POSITION;
- } else if (haveChildren && mFirstPosition > 0) {
- // Remember the position of the first child.
- // We only do this if we are not currently at the top of
- // the list, for two reasons:
- //
- // (1) The list may be in the process of becoming empty, in
- // which case mItemCount may not be 0, but if we try to
- // ask for any information about position 0 we will crash.
- //
- // (2) Being "at the top" seems like a special case, anyway,
- // and the user wouldn't expect to end up somewhere else when
- // they revisit the list even if its content has changed.
-
- ss.viewStart = getChildStartEdge(getChildAt(0));
-
- int firstPos = mFirstPosition;
- if (firstPos >= mItemCount) {
- firstPos = mItemCount - 1;
- }
-
- ss.position = firstPos;
- ss.firstId = mAdapter.getItemId(firstPos);
- } else {
- ss.viewStart = 0;
- ss.firstId = INVALID_POSITION;
- ss.position = 0;
- }
-
- if (mCheckStates != null) {
- ss.checkState = cloneCheckStates();
- }
-
- if (mCheckedIdStates != null) {
- final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
-
- final int count = mCheckedIdStates.size();
- for (int i = 0; i < count; i++) {
- idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
- }
-
- ss.checkIdState = idState;
- }
-
- ss.checkedItemCount = mCheckedItemCount;
-
- return ss;
- }
-
- @Override
- public void onRestoreInstanceState(Parcelable state) {
- SavedState ss = (SavedState) state;
- super.onRestoreInstanceState(ss.getSuperState());
-
- mDataChanged = true;
- mSyncSize = ss.size;
-
- if (ss.selectedId >= 0) {
- mNeedSync = true;
- mPendingSync = ss;
- mSyncRowId = ss.selectedId;
- mSyncPosition = ss.position;
- mSpecificStart = ss.viewStart;
- mSyncMode = SYNC_SELECTED_POSITION;
- } else if (ss.firstId >= 0) {
- setSelectedPositionInt(INVALID_POSITION);
-
- // Do this before setting mNeedSync since setNextSelectedPosition looks at mNeedSync
- setNextSelectedPositionInt(INVALID_POSITION);
-
- mSelectorPosition = INVALID_POSITION;
- mNeedSync = true;
- mPendingSync = ss;
- mSyncRowId = ss.firstId;
- mSyncPosition = ss.position;
- mSpecificStart = ss.viewStart;
- mSyncMode = SYNC_FIRST_POSITION;
- }
-
- if (ss.checkState != null) {
- mCheckStates = ss.checkState;
- }
-
- if (ss.checkIdState != null) {
- mCheckedIdStates = ss.checkIdState;
- }
-
- mCheckedItemCount = ss.checkedItemCount;
-
- requestLayout();
- }
-
- public static class LayoutParams extends ViewGroup.LayoutParams {
- /**
- * Type of this view as reported by the adapter
- */
- int viewType;
-
- /**
- * The stable ID of the item this view displays
- */
- long id = -1;
-
- /**
- * The position the view was removed from when pulled out of the
- * scrap heap.
- * @hide
- */
- int scrappedFromPosition;
-
- /**
- * When a TwoWayView is measured with an AT_MOST measure spec, it needs
- * to obtain children views to measure itself. When doing so, the children
- * are not attached to the window, but put in the recycler which assumes
- * they've been attached before. Setting this flag will force the reused
- * view to be attached to the window rather than just attached to the
- * parent.
- */
- boolean forceAdd;
-
- public LayoutParams(int width, int height) {
- super(width, height);
-
- if (this.width == MATCH_PARENT) {
- Log.w(LOGTAG, "Constructing LayoutParams with width FILL_PARENT " +
- "does not make much sense as the view might change orientation. " +
- "Falling back to WRAP_CONTENT");
- this.width = WRAP_CONTENT;
- }
-
- if (this.height == MATCH_PARENT) {
- Log.w(LOGTAG, "Constructing LayoutParams with height FILL_PARENT " +
- "does not make much sense as the view might change orientation. " +
- "Falling back to WRAP_CONTENT");
- this.height = WRAP_CONTENT;
- }
- }
-
- public LayoutParams(Context c, AttributeSet attrs) {
- super(c, attrs);
-
- if (this.width == MATCH_PARENT) {
- Log.w(LOGTAG, "Inflation setting LayoutParams width to MATCH_PARENT - " +
- "does not make much sense as the view might change orientation. " +
- "Falling back to WRAP_CONTENT");
- this.width = MATCH_PARENT;
- }
-
- if (this.height == MATCH_PARENT) {
- Log.w(LOGTAG, "Inflation setting LayoutParams height to MATCH_PARENT - " +
- "does not make much sense as the view might change orientation. " +
- "Falling back to WRAP_CONTENT");
- this.height = WRAP_CONTENT;
- }
- }
-
- public LayoutParams(ViewGroup.LayoutParams other) {
- super(other);
-
- if (this.width == MATCH_PARENT) {
- Log.w(LOGTAG, "Constructing LayoutParams with width MATCH_PARENT - " +
- "does not make much sense as the view might change orientation. " +
- "Falling back to WRAP_CONTENT");
- this.width = WRAP_CONTENT;
- }
-
- if (this.height == MATCH_PARENT) {
- Log.w(LOGTAG, "Constructing LayoutParams with height MATCH_PARENT - " +
- "does not make much sense as the view might change orientation. " +
- "Falling back to WRAP_CONTENT");
- this.height = WRAP_CONTENT;
- }
- }
- }
-
- class RecycleBin {
- private RecyclerListener mRecyclerListener;
- private int mFirstActivePosition;
- private View[] mActiveViews = new View[0];
- private ArrayList<View>[] mScrapViews;
- private int mViewTypeCount;
- private ArrayList<View> mCurrentScrap;
- private SparseArrayCompat<View> mTransientStateViews;
-
- public void setViewTypeCount(int viewTypeCount) {
- if (viewTypeCount < 1) {
- throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
- }
-
- @SuppressWarnings({"unchecked", "rawtypes"})
- ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
- for (int i = 0; i < viewTypeCount; i++) {
- scrapViews[i] = new ArrayList<View>();
- }
-
- mViewTypeCount = viewTypeCount;
- mCurrentScrap = scrapViews[0];
- mScrapViews = scrapViews;
- }
-
- public void markChildrenDirty() {
- if (mViewTypeCount == 1) {
- final ArrayList<View> scrap = mCurrentScrap;
- final int scrapCount = scrap.size();
-
- for (int i = 0; i < scrapCount; i++) {
- scrap.get(i).forceLayout();
- }
- } else {
- final int typeCount = mViewTypeCount;
- for (int i = 0; i < typeCount; i++) {
- for (View scrap : mScrapViews[i]) {
- scrap.forceLayout();
- }
- }
- }
-
- if (mTransientStateViews != null) {
- final int count = mTransientStateViews.size();
- for (int i = 0; i < count; i++) {
- mTransientStateViews.valueAt(i).forceLayout();
- }
- }
- }
-
- public boolean shouldRecycleViewType(int viewType) {
- return viewType >= 0;
- }
-
- void clear() {
- if (mViewTypeCount == 1) {
- final ArrayList<View> scrap = mCurrentScrap;
- final int scrapCount = scrap.size();
-
- for (int i = 0; i < scrapCount; i++) {
- removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
- }
- } else {
- final int typeCount = mViewTypeCount;
- for (int i = 0; i < typeCount; i++) {
- final ArrayList<View> scrap = mScrapViews[i];
- final int scrapCount = scrap.size();
-
- for (int j = 0; j < scrapCount; j++) {
- removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
- }
- }
- }
-
- if (mTransientStateViews != null) {
- mTransientStateViews.clear();
- }
- }
-
- void fillActiveViews(int childCount, int firstActivePosition) {
- if (mActiveViews.length < childCount) {
- mActiveViews = new View[childCount];
- }
-
- mFirstActivePosition = firstActivePosition;
-
- final View[] activeViews = mActiveViews;
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
-
- // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
- // However, we will NOT place them into scrap views.
- activeViews[i] = child;
- }
- }
-
- View getActiveView(int position) {
- final int index = position - mFirstActivePosition;
- final View[] activeViews = mActiveViews;
-
- if (index >= 0 && index < activeViews.length) {
- final View match = activeViews[index];
- activeViews[index] = null;
-
- return match;
- }
-
- return null;
- }
-
- View getTransientStateView(int position) {
- if (mTransientStateViews == null) {
- return null;
- }
-
- final int index = mTransientStateViews.indexOfKey(position);
- if (index < 0) {
- return null;
- }
-
- final View result = mTransientStateViews.valueAt(index);
- mTransientStateViews.removeAt(index);
-
- return result;
- }
-
- void clearTransientStateViews() {
- if (mTransientStateViews != null) {
- mTransientStateViews.clear();
- }
- }
-
- View getScrapView(int position) {
- if (mViewTypeCount == 1) {
- return retrieveFromScrap(mCurrentScrap, position);
- } else {
- int whichScrap = mAdapter.getItemViewType(position);
- if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
- return retrieveFromScrap(mScrapViews[whichScrap], position);
- }
- }
-
- return null;
- }
-
- @TargetApi(14)
- void addScrapView(View scrap, int position) {
- LayoutParams lp = (LayoutParams) scrap.getLayoutParams();
- if (lp == null) {
- return;
- }
-
- lp.scrappedFromPosition = position;
-
- final int viewType = lp.viewType;
- final boolean scrapHasTransientState = ViewCompat.hasTransientState(scrap);
-
- // Don't put views that should be ignored into the scrap heap
- if (!shouldRecycleViewType(viewType) || scrapHasTransientState) {
- if (scrapHasTransientState) {
- if (mTransientStateViews == null) {
- mTransientStateViews = new SparseArrayCompat<View>();
- }
-
- mTransientStateViews.put(position, scrap);
- }
-
- return;
- }
-
- if (mViewTypeCount == 1) {
- mCurrentScrap.add(scrap);
- } else {
- mScrapViews[viewType].add(scrap);
- }
-
- // FIXME: Unfortunately, ViewCompat.setAccessibilityDelegate() doesn't accept
- // null delegates.
- if (Build.VERSION.SDK_INT >= 14) {
- scrap.setAccessibilityDelegate(null);
- }
-
- if (mRecyclerListener != null) {
- mRecyclerListener.onMovedToScrapHeap(scrap);
- }
- }
-
- @TargetApi(14)
- void scrapActiveViews() {
- final View[] activeViews = mActiveViews;
- final boolean multipleScraps = (mViewTypeCount > 1);
-
- ArrayList<View> scrapViews = mCurrentScrap;
- final int count = activeViews.length;
-
- for (int i = count - 1; i >= 0; i--) {
- final View victim = activeViews[i];
- if (victim != null) {
- final LayoutParams lp = (LayoutParams) victim.getLayoutParams();
- int whichScrap = lp.viewType;
-
- activeViews[i] = null;
-
- final boolean scrapHasTransientState = ViewCompat.hasTransientState(victim);
- if (!shouldRecycleViewType(whichScrap) || scrapHasTransientState) {
- if (scrapHasTransientState) {
- removeDetachedView(victim, false);
-
- if (mTransientStateViews == null) {
- mTransientStateViews = new SparseArrayCompat<View>();
- }
-
- mTransientStateViews.put(mFirstActivePosition + i, victim);
- }
-
- continue;
- }
-
- if (multipleScraps) {
- scrapViews = mScrapViews[whichScrap];
- }
-
- lp.scrappedFromPosition = mFirstActivePosition + i;
- scrapViews.add(victim);
-
- // FIXME: Unfortunately, ViewCompat.setAccessibilityDelegate() doesn't accept
- // null delegates.
- if (Build.VERSION.SDK_INT >= 14) {
- victim.setAccessibilityDelegate(null);
- }
-
- if (mRecyclerListener != null) {
- mRecyclerListener.onMovedToScrapHeap(victim);
- }
- }
- }
-
- pruneScrapViews();
- }
-
- private void pruneScrapViews() {
- final int maxViews = mActiveViews.length;
- final int viewTypeCount = mViewTypeCount;
- final ArrayList<View>[] scrapViews = mScrapViews;
-
- for (int i = 0; i < viewTypeCount; ++i) {
- final ArrayList<View> scrapPile = scrapViews[i];
- int size = scrapPile.size();
- final int extras = size - maxViews;
-
- size--;
-
- for (int j = 0; j < extras; j++) {
- removeDetachedView(scrapPile.remove(size--), false);
- }
- }
-
- if (mTransientStateViews != null) {
- for (int i = 0; i < mTransientStateViews.size(); i++) {
- final View v = mTransientStateViews.valueAt(i);
- if (!ViewCompat.hasTransientState(v)) {
- mTransientStateViews.removeAt(i);
- i--;
- }
- }
- }
- }
-
- void reclaimScrapViews(List<View> views) {
- if (mViewTypeCount == 1) {
- views.addAll(mCurrentScrap);
- } else {
- final int viewTypeCount = mViewTypeCount;
- final ArrayList<View>[] scrapViews = mScrapViews;
-
- for (int i = 0; i < viewTypeCount; ++i) {
- final ArrayList<View> scrapPile = scrapViews[i];
- views.addAll(scrapPile);
- }
- }
- }
-
- View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
- int size = scrapViews.size();
- if (size <= 0) {
- return null;
- }
-
- for (int i = 0; i < size; i++) {
- final View scrapView = scrapViews.get(i);
- final LayoutParams lp = (LayoutParams) scrapView.getLayoutParams();
-
- if (lp.scrappedFromPosition == position) {
- scrapViews.remove(i);
- return scrapView;
- }
- }
-
- return scrapViews.remove(size - 1);
- }
- }
-
- @Override
- public void setEmptyView(View emptyView) {
- super.setEmptyView(emptyView);
- mEmptyView = emptyView;
- updateEmptyStatus();
- }
-
- @Override
- public void setFocusable(boolean focusable) {
- final ListAdapter adapter = getAdapter();
- final boolean empty = (adapter == null || adapter.getCount() == 0);
-
- mDesiredFocusableState = focusable;
- if (!focusable) {
- mDesiredFocusableInTouchModeState = false;
- }
-
- super.setFocusable(focusable && !empty);
- }
-
- @Override
- public void setFocusableInTouchMode(boolean focusable) {
- final ListAdapter adapter = getAdapter();
- final boolean empty = (adapter == null || adapter.getCount() == 0);
-
- mDesiredFocusableInTouchModeState = focusable;
- if (focusable) {
- mDesiredFocusableState = true;
- }
-
- super.setFocusableInTouchMode(focusable && !empty);
- }
-
- private void checkFocus() {
- final ListAdapter adapter = getAdapter();
- final boolean focusable = (adapter != null && adapter.getCount() > 0);
-
- // The order in which we set focusable in touch mode/focusable may matter
- // for the client, see View.setFocusableInTouchMode() comments for more
- // details
- super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
- super.setFocusable(focusable && mDesiredFocusableState);
-
- if (mEmptyView != null) {
- updateEmptyStatus();
- }
- }
-
- private void updateEmptyStatus() {
- final boolean isEmpty = (mAdapter == null || mAdapter.isEmpty());
-
- if (isEmpty) {
- if (mEmptyView != null) {
- mEmptyView.setVisibility(View.VISIBLE);
- setVisibility(View.GONE);
- } else {
- // If the caller just removed our empty view, make sure the list
- // view is visible
- setVisibility(View.VISIBLE);
- }
-
- // We are now GONE, so pending layouts will not be dispatched.
- // Force one here to make sure that the state of the list matches
- // the state of the adapter.
- if (mDataChanged) {
- layout(getLeft(), getTop(), getRight(), getBottom());
- }
- } else {
- if (mEmptyView != null) {
- mEmptyView.setVisibility(View.GONE);
- }
-
- setVisibility(View.VISIBLE);
- }
- }
-
- private class AdapterDataSetObserver extends DataSetObserver {
- private Parcelable mInstanceState = null;
-
- @Override
- public void onChanged() {
- mDataChanged = true;
- mOldItemCount = mItemCount;
- mItemCount = getAdapter().getCount();
-
- // Detect the case where a cursor that was previously invalidated has
- // been re-populated with new data.
- if (TwoWayView.this.mHasStableIds && mInstanceState != null
- && mOldItemCount == 0 && mItemCount > 0) {
- TwoWayView.this.onRestoreInstanceState(mInstanceState);
- mInstanceState = null;
- } else {
- rememberSyncState();
- }
-
- checkFocus();
- requestLayout();
- }
-
- @Override
- public void onInvalidated() {
- mDataChanged = true;
-
- if (TwoWayView.this.mHasStableIds) {
- // Remember the current state for the case where our hosting activity is being
- // stopped and later restarted
- mInstanceState = TwoWayView.this.onSaveInstanceState();
- }
-
- // Data is invalid so we should reset our state
- mOldItemCount = mItemCount;
- mItemCount = 0;
-
- mSelectedPosition = INVALID_POSITION;
- mSelectedRowId = INVALID_ROW_ID;
-
- mNextSelectedPosition = INVALID_POSITION;
- mNextSelectedRowId = INVALID_ROW_ID;
-
- mNeedSync = false;
-
- checkFocus();
- requestLayout();
- }
- }
-
- static class SavedState extends BaseSavedState {
- long selectedId;
- long firstId;
- int viewStart;
- int position;
- int size;
- int checkedItemCount;
- SparseBooleanArray checkState;
- LongSparseArray<Integer> checkIdState;
-
- /**
- * Constructor called from {@link TwoWayView#onSaveInstanceState()}
- */
- SavedState(Parcelable superState) {
- super(superState);
- }
-
- /**
- * Constructor called from {@link #CREATOR}
- */
- private SavedState(Parcel in) {
- super(in);
-
- selectedId = in.readLong();
- firstId = in.readLong();
- viewStart = in.readInt();
- position = in.readInt();
- size = in.readInt();
-
- checkedItemCount = in.readInt();
- checkState = in.readSparseBooleanArray();
-
- final int N = in.readInt();
- if (N > 0) {
- checkIdState = new LongSparseArray<Integer>();
- for (int i = 0; i < N; i++) {
- final long key = in.readLong();
- final int value = in.readInt();
- checkIdState.put(key, value);
- }
- }
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- super.writeToParcel(out, flags);
-
- out.writeLong(selectedId);
- out.writeLong(firstId);
- out.writeInt(viewStart);
- out.writeInt(position);
- out.writeInt(size);
-
- out.writeInt(checkedItemCount);
- out.writeSparseBooleanArray(checkState);
-
- final int N = checkIdState != null ? checkIdState.size() : 0;
- out.writeInt(N);
-
- for (int i = 0; i < N; i++) {
- out.writeLong(checkIdState.keyAt(i));
- out.writeInt(checkIdState.valueAt(i));
- }
- }
-
- @Override
- public String toString() {
- return "TwoWayView.SavedState{"
- + Integer.toHexString(System.identityHashCode(this))
- + " selectedId=" + selectedId
- + " firstId=" + firstId
- + " viewStart=" + viewStart
- + " size=" + size
- + " position=" + position
- + " checkState=" + checkState + "}";
- }
-
- public static final Parcelable.Creator<SavedState> CREATOR
- = new Parcelable.Creator<SavedState>() {
- @Override
- public SavedState createFromParcel(Parcel in) {
- return new SavedState(in);
- }
-
- @Override
- public SavedState[] newArray(int size) {
- return new SavedState[size];
- }
- };
- }
-
- private class SelectionNotifier implements Runnable {
- @Override
- public void run() {
- if (mDataChanged) {
- // Data has changed between when this SelectionNotifier
- // was posted and now. We need to wait until the AdapterView
- // has been synched to the new data.
- if (mAdapter != null) {
- post(this);
- }
- } else {
- fireOnSelected();
- performAccessibilityActionsOnSelected();
- }
- }
- }
-
- private class WindowRunnable {
- private int mOriginalAttachCount;
-
- public void rememberWindowAttachCount() {
- mOriginalAttachCount = getWindowAttachCount();
- }
-
- public boolean sameWindow() {
- return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
- }
- }
-
- private class PerformClick extends WindowRunnable implements Runnable {
- int mClickMotionPosition;
-
- @Override
- public void run() {
- if (mDataChanged) {
- return;
- }
-
- final ListAdapter adapter = mAdapter;
- final int motionPosition = mClickMotionPosition;
-
- if (adapter != null && mItemCount > 0 &&
- motionPosition != INVALID_POSITION &&
- motionPosition < adapter.getCount() && sameWindow()) {
-
- final View child = getChildAt(motionPosition - mFirstPosition);
- if (child != null) {
- performItemClick(child, motionPosition, adapter.getItemId(motionPosition));
- }
- }
- }
- }
-
- private final class CheckForTap implements Runnable {
- @Override
- public void run() {
- if (mTouchMode != TOUCH_MODE_DOWN) {
- return;
- }
-
- mTouchMode = TOUCH_MODE_TAP;
-
- final View child = getChildAt(mMotionPosition - mFirstPosition);
- if (child != null && !child.hasFocusable()) {
- mLayoutMode = LAYOUT_NORMAL;
-
- if (!mDataChanged) {
- setPressed(true);
- child.setPressed(true);
-
- layoutChildren();
- positionSelector(mMotionPosition, child);
- refreshDrawableState();
-
- positionSelector(mMotionPosition, child);
- refreshDrawableState();
-
- final boolean longClickable = isLongClickable();
-
- if (mSelector != null) {
- Drawable d = mSelector.getCurrent();
-
- if (d != null && d instanceof TransitionDrawable) {
- if (longClickable) {
- final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
- ((TransitionDrawable) d).startTransition(longPressTimeout);
- } else {
- ((TransitionDrawable) d).resetTransition();
- }
- }
- }
-
- if (longClickable) {
- triggerCheckForLongPress();
- } else {
- mTouchMode = TOUCH_MODE_DONE_WAITING;
- }
- } else {
- mTouchMode = TOUCH_MODE_DONE_WAITING;
- }
- }
- }
- }
-
- private class CheckForLongPress extends WindowRunnable implements Runnable {
- @Override
- public void run() {
- final int motionPosition = mMotionPosition;
- final View child = getChildAt(motionPosition - mFirstPosition);
-
- if (child != null) {
- final long longPressId = mAdapter.getItemId(mMotionPosition);
-
- boolean handled = false;
- if (sameWindow() && !mDataChanged) {
- handled = performLongPress(child, motionPosition, longPressId);
- }
-
- if (handled) {
- mTouchMode = TOUCH_MODE_REST;
- setPressed(false);
- child.setPressed(false);
- } else {
- mTouchMode = TOUCH_MODE_DONE_WAITING;
- }
- }
- }
- }
-
- private class CheckForKeyLongPress extends WindowRunnable implements Runnable {
- public void run() {
- if (!isPressed() || mSelectedPosition < 0) {
- return;
- }
-
- final int index = mSelectedPosition - mFirstPosition;
- final View v = getChildAt(index);
-
- if (!mDataChanged) {
- boolean handled = false;
-
- if (sameWindow()) {
- handled = performLongPress(v, mSelectedPosition, mSelectedRowId);
- }
-
- if (handled) {
- setPressed(false);
- v.setPressed(false);
- }
- } else {
- setPressed(false);
-
- if (v != null) {
- v.setPressed(false);
- }
- }
- }
- }
-
- private static class ArrowScrollFocusResult {
- private int mSelectedPosition;
- private int mAmountToScroll;
-
- /**
- * How {@link TwoWayView#arrowScrollFocused} returns its values.
- */
- void populate(int selectedPosition, int amountToScroll) {
- mSelectedPosition = selectedPosition;
- mAmountToScroll = amountToScroll;
- }
-
- public int getSelectedPosition() {
- return mSelectedPosition;
- }
-
- public int getAmountToScroll() {
- return mAmountToScroll;
- }
- }
-
- private class ListItemAccessibilityDelegate extends AccessibilityDelegateCompat {
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
-
- final int position = getPositionForView(host);
- final ListAdapter adapter = getAdapter();
-
- // Cannot perform actions on invalid items
- if (position == INVALID_POSITION || adapter == null) {
- return;
- }
-
- // Cannot perform actions on disabled items
- if (!isEnabled() || !adapter.isEnabled(position)) {
- return;
- }
-
- if (position == getSelectedItemPosition()) {
- info.setSelected(true);
- info.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION);
- } else {
- info.addAction(AccessibilityNodeInfoCompat.ACTION_SELECT);
- }
-
- if (isClickable()) {
- info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
- info.setClickable(true);
- }
-
- if (isLongClickable()) {
- info.addAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK);
- info.setLongClickable(true);
- }
- }
-
- @Override
- public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
- if (super.performAccessibilityAction(host, action, arguments)) {
- return true;
- }
-
- final int position = getPositionForView(host);
- final ListAdapter adapter = getAdapter();
-
- // Cannot perform actions on invalid items
- if (position == INVALID_POSITION || adapter == null) {
- return false;
- }
-
- // Cannot perform actions on disabled items
- if (!isEnabled() || !adapter.isEnabled(position)) {
- return false;
- }
-
- final long id = getItemIdAtPosition(position);
-
- switch (action) {
- case AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION:
- if (getSelectedItemPosition() == position) {
- setSelection(INVALID_POSITION);
- return true;
- }
- return false;
-
- case AccessibilityNodeInfoCompat.ACTION_SELECT:
- if (getSelectedItemPosition() != position) {
- setSelection(position);
- return true;
- }
- return false;
-
- case AccessibilityNodeInfoCompat.ACTION_CLICK:
- return isClickable() && performItemClick(host, position, id);
-
- case AccessibilityNodeInfoCompat.ACTION_LONG_CLICK:
- return isLongClickable() && performLongPress(host, position, id);
- }
-
- return false;
- }
- }
-
- private class PositionScroller implements Runnable {
- private static final int SCROLL_DURATION = 200;
-
- private static final int MOVE_AFTER_POS = 1;
- private static final int MOVE_BEFORE_POS = 2;
- private static final int MOVE_AFTER_BOUND = 3;
- private static final int MOVE_BEFORE_BOUND = 4;
- private static final int MOVE_OFFSET = 5;
-
- private int mMode;
- private int mTargetPosition;
- private int mBoundPosition;
- private int mLastSeenPosition;
- private int mScrollDuration;
- private final int mExtraScroll;
-
- private int mOffsetFromStart;
-
- PositionScroller() {
- mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
- }
-
- void start(final int position) {
- stop();
-
- if (mDataChanged) {
- // Wait until we're back in a stable state to try this.
- mPositionScrollAfterLayout = new Runnable() {
- @Override public void run() {
- start(position);
- }
- };
-
- return;
- }
-
- final int childCount = getChildCount();
- if (childCount == 0) {
- // Can't scroll without children.
- return;
- }
-
- final int firstPosition = mFirstPosition;
- final int lastPosition = firstPosition + childCount - 1;
-
- final int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
-
- final int viewTravelCount;
- if (clampedPosition < firstPosition) {
- viewTravelCount = firstPosition - clampedPosition + 1;
- mMode = MOVE_BEFORE_POS;
- } else if (clampedPosition > lastPosition) {
- viewTravelCount = clampedPosition - lastPosition + 1;
- mMode = MOVE_AFTER_POS;
- } else {
- scrollToVisible(clampedPosition, INVALID_POSITION, SCROLL_DURATION);
- return;
- }
-
- if (viewTravelCount > 0) {
- mScrollDuration = SCROLL_DURATION / viewTravelCount;
- } else {
- mScrollDuration = SCROLL_DURATION;
- }
-
- mTargetPosition = clampedPosition;
- mBoundPosition = INVALID_POSITION;
- mLastSeenPosition = INVALID_POSITION;
-
- ViewCompat.postOnAnimation(TwoWayView.this, this);
- }
-
- void start(final int position, final int boundPosition) {
- stop();
-
- if (boundPosition == INVALID_POSITION) {
- start(position);
- return;
- }
-
- if (mDataChanged) {
- // Wait until we're back in a stable state to try this.
- mPositionScrollAfterLayout = new Runnable() {
- @Override public void run() {
- start(position, boundPosition);
- }
- };
-
- return;
- }
-
- final int childCount = getChildCount();
- if (childCount == 0) {
- // Can't scroll without children.
- return;
- }
-
- final int firstPosition = mFirstPosition;
- final int lastPosition = firstPosition + childCount - 1;
-
- final int clampedPosition = Math.max(0, Math.min(getCount() - 1, position));
-
- final int viewTravelCount;
- if (clampedPosition < firstPosition) {
- final int boundPositionFromLast = lastPosition - boundPosition;
- if (boundPositionFromLast < 1) {
- // Moving would shift our bound position off the screen. Abort.
- return;
- }
-
- final int positionTravel = firstPosition - clampedPosition + 1;
- final int boundTravel = boundPositionFromLast - 1;
- if (boundTravel < positionTravel) {
- viewTravelCount = boundTravel;
- mMode = MOVE_BEFORE_BOUND;
- } else {
- viewTravelCount = positionTravel;
- mMode = MOVE_BEFORE_POS;
- }
- } else if (clampedPosition > lastPosition) {
- final int boundPositionFromFirst = boundPosition - firstPosition;
- if (boundPositionFromFirst < 1) {
- // Moving would shift our bound position off the screen. Abort.
- return;
- }
-
- final int positionTravel = clampedPosition - lastPosition + 1;
- final int boundTravel = boundPositionFromFirst - 1;
- if (boundTravel < positionTravel) {
- viewTravelCount = boundTravel;
- mMode = MOVE_AFTER_BOUND;
- } else {
- viewTravelCount = positionTravel;
- mMode = MOVE_AFTER_POS;
- }
- } else {
- scrollToVisible(clampedPosition, boundPosition, SCROLL_DURATION);
- return;
- }
-
- if (viewTravelCount > 0) {
- mScrollDuration = SCROLL_DURATION / viewTravelCount;
- } else {
- mScrollDuration = SCROLL_DURATION;
- }
-
- mTargetPosition = clampedPosition;
- mBoundPosition = boundPosition;
- mLastSeenPosition = INVALID_POSITION;
-
- ViewCompat.postOnAnimation(TwoWayView.this, this);
- }
-
- void startWithOffset(int position, int offset) {
- startWithOffset(position, offset, SCROLL_DURATION);
- }
-
- void startWithOffset(final int position, int offset, final int duration) {
- stop();
-
- if (mDataChanged) {
- // Wait until we're back in a stable state to try this.
- final int postOffset = offset;
- mPositionScrollAfterLayout = new Runnable() {
- @Override public void run() {
- startWithOffset(position, postOffset, duration);
- }
- };
-
- return;
- }
-
- final int childCount = getChildCount();
- if (childCount == 0) {
- // Can't scroll without children.
- return;
- }
-
- offset += getStartEdge();
-
- mTargetPosition = Math.max(0, Math.min(getCount() - 1, position));
- mOffsetFromStart = offset;
- mBoundPosition = INVALID_POSITION;
- mLastSeenPosition = INVALID_POSITION;
- mMode = MOVE_OFFSET;
-
- final int firstPosition = mFirstPosition;
- final int lastPosition = firstPosition + childCount - 1;
-
- final int viewTravelCount;
- if (mTargetPosition < firstPosition) {
- viewTravelCount = firstPosition - mTargetPosition;
- } else if (mTargetPosition > lastPosition) {
- viewTravelCount = mTargetPosition - lastPosition;
- } else {
- // On-screen, just scroll.
- final View targetView = getChildAt(mTargetPosition - firstPosition);
- final int targetStart = getChildStartEdge(targetView);
- smoothScrollBy(targetStart - offset, duration);
- return;
- }
-
- // Estimate how many screens we should travel
- final float screenTravelCount = (float) viewTravelCount / childCount;
- mScrollDuration = screenTravelCount < 1 ?
- duration : (int) (duration / screenTravelCount);
- mLastSeenPosition = INVALID_POSITION;
-
- ViewCompat.postOnAnimation(TwoWayView.this, this);
- }
-
- /**
- * Scroll such that targetPos is in the visible padded region without scrolling
- * boundPos out of view. Assumes targetPos is onscreen.
- */
- void scrollToVisible(int targetPosition, int boundPosition, int duration) {
- final int childCount = getChildCount();
- final int firstPosition = mFirstPosition;
- final int lastPosition = firstPosition + childCount - 1;
-
- final int start = getStartEdge();
- final int end = getEndEdge();
-
- if (targetPosition < firstPosition || targetPosition > lastPosition) {
- Log.w(LOGTAG, "scrollToVisible called with targetPosition " + targetPosition +
- " not visible [" + firstPosition + ", " + lastPosition + "]");
- }
-
- if (boundPosition < firstPosition || boundPosition > lastPosition) {
- // boundPos doesn't matter, it's already offscreen.
- boundPosition = INVALID_POSITION;
- }
-
- final View targetChild = getChildAt(targetPosition - firstPosition);
- final int targetStart = getChildStartEdge(targetChild);
- final int targetEnd = getChildEndEdge(targetChild);
-
- int scrollBy = 0;
- if (targetEnd > end) {
- scrollBy = targetEnd - end;
- }
- if (targetStart < start) {
- scrollBy = targetStart - start;
- }
-
- if (scrollBy == 0) {
- return;
- }
-
- if (boundPosition >= 0) {
- final View boundChild = getChildAt(boundPosition - firstPosition);
- final int boundStart = getChildStartEdge(boundChild);
- final int boundEnd = getChildEndEdge(boundChild);
- final int absScroll = Math.abs(scrollBy);
-
- if (scrollBy < 0 && boundEnd + absScroll > end) {
- // Don't scroll the bound view off the end of the screen.
- scrollBy = Math.max(0, boundEnd - end);
- } else if (scrollBy > 0 && boundStart - absScroll < start) {
- // Don't scroll the bound view off the top of the screen.
- scrollBy = Math.min(0, boundStart - start);
- }
- }
-
- smoothScrollBy(scrollBy, duration);
- }
-
- void stop() {
- removeCallbacks(this);
- }
-
- @Override
- public void run() {
- final int size = getAvailableSize();
- final int firstPosition = mFirstPosition;
-
- final int startPadding = (mIsVertical ? getPaddingTop() : getPaddingLeft());
- final int endPadding = (mIsVertical ? getPaddingBottom() : getPaddingRight());
-
- switch (mMode) {
- case MOVE_AFTER_POS: {
- final int lastViewIndex = getChildCount() - 1;
- if (lastViewIndex < 0) {
- return;
- }
-
- final int lastPosition = firstPosition + lastViewIndex;
- if (lastPosition == mLastSeenPosition) {
- // No new views, let things keep going.
- ViewCompat.postOnAnimation(TwoWayView.this, this);
- return;
- }
-
- final View lastView = getChildAt(lastViewIndex);
- final int lastViewSize = getChildSize(lastView);
- final int lastViewStart = getChildStartEdge(lastView);
- final int lastViewPixelsShowing = size - lastViewStart;
- final int extraScroll = lastPosition < mItemCount - 1 ?
- Math.max(endPadding, mExtraScroll) : endPadding;
-
- final int scrollBy = lastViewSize - lastViewPixelsShowing + extraScroll;
- smoothScrollBy(scrollBy, mScrollDuration);
-
- mLastSeenPosition = lastPosition;
- if (lastPosition < mTargetPosition) {
- ViewCompat.postOnAnimation(TwoWayView.this, this);
- }
-
- break;
- }
-
- case MOVE_AFTER_BOUND: {
- final int nextViewIndex = 1;
- final int childCount = getChildCount();
- if (firstPosition == mBoundPosition ||
- childCount <= nextViewIndex ||
- firstPosition + childCount >= mItemCount) {
- return;
- }
-
- final int nextPosition = firstPosition + nextViewIndex;
-
- if (nextPosition == mLastSeenPosition) {
- // No new views, let things keep going.
- ViewCompat.postOnAnimation(TwoWayView.this, this);
- return;
- }
-
- final View nextView = getChildAt(nextViewIndex);
- final int nextViewSize = getChildSize(nextView);
- final int nextViewStart = getChildStartEdge(nextView);
- final int extraScroll = Math.max(endPadding, mExtraScroll);
- if (nextPosition < mBoundPosition) {
- smoothScrollBy(Math.max(0, nextViewSize + nextViewStart - extraScroll),
- mScrollDuration);
- mLastSeenPosition = nextPosition;
- ViewCompat.postOnAnimation(TwoWayView.this, this);
- } else {
- if (nextViewSize > extraScroll) {
- smoothScrollBy(nextViewSize - extraScroll, mScrollDuration);
- }
- }
-
- break;
- }
-
- case MOVE_BEFORE_POS: {
- if (firstPosition == mLastSeenPosition) {
- // No new views, let things keep going.
- ViewCompat.postOnAnimation(TwoWayView.this, this);
- return;
- }
-
- final View firstView = getChildAt(0);
- if (firstView == null) {
- return;
- }
-
- final int firstViewTop = getChildStartEdge(firstView);
- final int extraScroll = firstPosition > 0 ?
- Math.max(mExtraScroll, startPadding) : startPadding;
-
- smoothScrollBy(firstViewTop - extraScroll, mScrollDuration);
- mLastSeenPosition = firstPosition;
-
- if (firstPosition > mTargetPosition) {
- ViewCompat.postOnAnimation(TwoWayView.this, this);
- }
-
- break;
- }
-
- case MOVE_BEFORE_BOUND: {
- final int lastViewIndex = getChildCount() - 2;
- if (lastViewIndex < 0) {
- return;
- }
-
- final int lastPosition = firstPosition + lastViewIndex;
-
- if (lastPosition == mLastSeenPosition) {
- // No new views, let things keep going.
- ViewCompat.postOnAnimation(TwoWayView.this, this);
- return;
- }
-
- final View lastView = getChildAt(lastViewIndex);
- final int lastViewSize = getChildSize(lastView);
- final int lastViewStart = getChildStartEdge(lastView);
- final int lastViewPixelsShowing = size - lastViewStart;
- final int extraScroll = Math.max(startPadding, mExtraScroll);
-
- mLastSeenPosition = lastPosition;
-
- if (lastPosition > mBoundPosition) {
- smoothScrollBy(-(lastViewPixelsShowing - extraScroll), mScrollDuration);
- ViewCompat.postOnAnimation(TwoWayView.this, this);
- } else {
- final int end = size - extraScroll;
- final int lastViewEnd = lastViewStart + lastViewSize;
- if (end > lastViewEnd) {
- smoothScrollBy(-(end - lastViewEnd), mScrollDuration);
- }
- }
-
- break;
- }
-
- case MOVE_OFFSET: {
- if (mLastSeenPosition == firstPosition) {
- // No new views, let things keep going.
- ViewCompat.postOnAnimation(TwoWayView.this, this);
- return;
- }
-
- mLastSeenPosition = firstPosition;
-
- final int childCount = getChildCount();
- final int position = mTargetPosition;
- final int lastPos = firstPosition + childCount - 1;
-
- int viewTravelCount = 0;
- if (position < firstPosition) {
- viewTravelCount = firstPosition - position + 1;
- } else if (position > lastPos) {
- viewTravelCount = position - lastPos;
- }
-
- // Estimate how many screens we should travel
- final float screenTravelCount = (float) viewTravelCount / childCount;
-
- final float modifier = Math.min(Math.abs(screenTravelCount), 1.f);
- if (position < firstPosition) {
- final int distance = (int) (-getSize() * modifier);
- final int duration = (int) (mScrollDuration * modifier);
- smoothScrollBy(distance, duration);
- ViewCompat.postOnAnimation(TwoWayView.this, this);
- } else if (position > lastPos) {
- final int distance = (int) (getSize() * modifier);
- final int duration = (int) (mScrollDuration * modifier);
- smoothScrollBy(distance, duration);
- ViewCompat.postOnAnimation(TwoWayView.this, this);
- } else {
- // On-screen, just scroll.
- final View targetView = getChildAt(position - firstPosition);
- final int targetStart = getChildStartEdge(targetView);
- final int distance = targetStart - mOffsetFromStart;
- final int duration = (int) (mScrollDuration *
- ((float) Math.abs(distance) / getSize()));
- smoothScrollBy(distance, duration);
- }
-
- break;
- }
-
- default:
- break;
- }
- }
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedEditText.java b/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedEditText.java
deleted file mode 100644
index c84686e90..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedEditText.java
+++ /dev/null
@@ -1,172 +0,0 @@
-// This file is generated by generate_themed_views.py; do not edit.
-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget.themed;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.lwt.LightweightTheme;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.DrawableUtil;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-
-public class ThemedEditText extends android.widget.EditText
- implements LightweightTheme.OnChangeListener {
- private LightweightTheme theme;
-
- private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
- private static final int[] STATE_LIGHT = { R.attr.state_light };
- private static final int[] STATE_DARK = { R.attr.state_dark };
-
- protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
- protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
- protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
-
- private boolean isPrivate;
- private boolean isLight;
- private boolean isDark;
- private boolean autoUpdateTheme; // always false if there's no theme.
-
- private ColorStateList drawableColors;
-
- public ThemedEditText(Context context, AttributeSet attrs) {
- super(context, attrs);
- initialize(context, attrs, 0);
- }
-
- public ThemedEditText(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- initialize(context, attrs, defStyle);
- }
-
- private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
- // The theme can be null, particularly if we might be instantiating this
- // View in an IDE, with no ambient GeckoApplication.
- final Context applicationContext = context.getApplicationContext();
- if (applicationContext instanceof GeckoApplication) {
- theme = ((GeckoApplication) applicationContext).getLightweightTheme();
- }
-
- final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
- autoUpdateTheme = theme != null && a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
- a.recycle();
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- if (autoUpdateTheme)
- theme.addListener(this);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- if (autoUpdateTheme)
- theme.removeListener(this);
- }
-
- @Override
- public int[] onCreateDrawableState(int extraSpace) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
-
- if (isPrivate)
- mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
- else if (isLight)
- mergeDrawableStates(drawableState, STATE_LIGHT);
- else if (isDark)
- mergeDrawableStates(drawableState, STATE_DARK);
-
- return drawableState;
- }
-
- @Override
- public void onLightweightThemeChanged() {
- if (autoUpdateTheme && theme.isEnabled())
- setTheme(theme.isLightTheme());
- }
-
- @Override
- public void onLightweightThemeReset() {
- if (autoUpdateTheme)
- resetTheme();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- onLightweightThemeChanged();
- }
-
- public boolean isPrivateMode() {
- return isPrivate;
- }
-
- public void setPrivateMode(boolean isPrivate) {
- if (this.isPrivate != isPrivate) {
- this.isPrivate = isPrivate;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setTheme(boolean isLight) {
- // Set the theme only if it is different from existing theme.
- if ((isLight && this.isLight != isLight) ||
- (!isLight && this.isDark == isLight)) {
- if (isLight) {
- this.isLight = true;
- this.isDark = false;
- } else {
- this.isLight = false;
- this.isDark = true;
- }
-
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void resetTheme() {
- if (isLight || isDark) {
- isLight = false;
- isDark = false;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setAutoUpdateTheme(boolean autoUpdateTheme) {
- if (theme == null) {
- return;
- }
-
- if (this.autoUpdateTheme != autoUpdateTheme) {
- this.autoUpdateTheme = autoUpdateTheme;
-
- if (autoUpdateTheme)
- theme.addListener(this);
- else
- theme.removeListener(this);
- }
- }
-
- public ColorDrawable getColorDrawable(int id) {
- return new ColorDrawable(ContextCompat.getColor(getContext(), id));
- }
-
- protected LightweightTheme getTheme() {
- return theme;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedFrameLayout.java b/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedFrameLayout.java
deleted file mode 100644
index a95fe2d9f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedFrameLayout.java
+++ /dev/null
@@ -1,172 +0,0 @@
-// This file is generated by generate_themed_views.py; do not edit.
-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget.themed;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.lwt.LightweightTheme;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.DrawableUtil;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-
-public class ThemedFrameLayout extends android.widget.FrameLayout
- implements LightweightTheme.OnChangeListener {
- private LightweightTheme theme;
-
- private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
- private static final int[] STATE_LIGHT = { R.attr.state_light };
- private static final int[] STATE_DARK = { R.attr.state_dark };
-
- protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
- protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
- protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
-
- private boolean isPrivate;
- private boolean isLight;
- private boolean isDark;
- private boolean autoUpdateTheme; // always false if there's no theme.
-
- private ColorStateList drawableColors;
-
- public ThemedFrameLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- initialize(context, attrs, 0);
- }
-
- public ThemedFrameLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- initialize(context, attrs, defStyle);
- }
-
- private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
- // The theme can be null, particularly if we might be instantiating this
- // View in an IDE, with no ambient GeckoApplication.
- final Context applicationContext = context.getApplicationContext();
- if (applicationContext instanceof GeckoApplication) {
- theme = ((GeckoApplication) applicationContext).getLightweightTheme();
- }
-
- final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
- autoUpdateTheme = theme != null && a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
- a.recycle();
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- if (autoUpdateTheme)
- theme.addListener(this);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- if (autoUpdateTheme)
- theme.removeListener(this);
- }
-
- @Override
- public int[] onCreateDrawableState(int extraSpace) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
-
- if (isPrivate)
- mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
- else if (isLight)
- mergeDrawableStates(drawableState, STATE_LIGHT);
- else if (isDark)
- mergeDrawableStates(drawableState, STATE_DARK);
-
- return drawableState;
- }
-
- @Override
- public void onLightweightThemeChanged() {
- if (autoUpdateTheme && theme.isEnabled())
- setTheme(theme.isLightTheme());
- }
-
- @Override
- public void onLightweightThemeReset() {
- if (autoUpdateTheme)
- resetTheme();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- onLightweightThemeChanged();
- }
-
- public boolean isPrivateMode() {
- return isPrivate;
- }
-
- public void setPrivateMode(boolean isPrivate) {
- if (this.isPrivate != isPrivate) {
- this.isPrivate = isPrivate;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setTheme(boolean isLight) {
- // Set the theme only if it is different from existing theme.
- if ((isLight && this.isLight != isLight) ||
- (!isLight && this.isDark == isLight)) {
- if (isLight) {
- this.isLight = true;
- this.isDark = false;
- } else {
- this.isLight = false;
- this.isDark = true;
- }
-
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void resetTheme() {
- if (isLight || isDark) {
- isLight = false;
- isDark = false;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setAutoUpdateTheme(boolean autoUpdateTheme) {
- if (theme == null) {
- return;
- }
-
- if (this.autoUpdateTheme != autoUpdateTheme) {
- this.autoUpdateTheme = autoUpdateTheme;
-
- if (autoUpdateTheme)
- theme.addListener(this);
- else
- theme.removeListener(this);
- }
- }
-
- public ColorDrawable getColorDrawable(int id) {
- return new ColorDrawable(ContextCompat.getColor(getContext(), id));
- }
-
- protected LightweightTheme getTheme() {
- return theme;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedImageButton.java b/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedImageButton.java
deleted file mode 100644
index 88e94c6c7..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedImageButton.java
+++ /dev/null
@@ -1,200 +0,0 @@
-// This file is generated by generate_themed_views.py; do not edit.
-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget.themed;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.lwt.LightweightTheme;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.DrawableUtil;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-
-public class ThemedImageButton extends android.widget.ImageButton
- implements LightweightTheme.OnChangeListener {
- private LightweightTheme theme;
-
- private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
- private static final int[] STATE_LIGHT = { R.attr.state_light };
- private static final int[] STATE_DARK = { R.attr.state_dark };
-
- protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
- protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
- protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
-
- private boolean isPrivate;
- private boolean isLight;
- private boolean isDark;
- private boolean autoUpdateTheme; // always false if there's no theme.
-
- private ColorStateList drawableColors;
-
- public ThemedImageButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- initialize(context, attrs, 0);
- }
-
- public ThemedImageButton(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- initialize(context, attrs, defStyle);
- }
-
- private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
- // The theme can be null, particularly if we might be instantiating this
- // View in an IDE, with no ambient GeckoApplication.
- final Context applicationContext = context.getApplicationContext();
- if (applicationContext instanceof GeckoApplication) {
- theme = ((GeckoApplication) applicationContext).getLightweightTheme();
- }
-
- final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
- autoUpdateTheme = theme != null && a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
- a.recycle();
-
- final TypedArray themedA = context.obtainStyledAttributes(attrs, R.styleable.ThemedView, defStyle, 0);
- drawableColors = themedA.getColorStateList(R.styleable.ThemedView_drawableTintList);
- themedA.recycle();
-
- // Apply the tint initially - the Drawable is
- // initially set by XML via super's constructor.
- setTintedImageDrawable(getDrawable());
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- if (autoUpdateTheme)
- theme.addListener(this);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- if (autoUpdateTheme)
- theme.removeListener(this);
- }
-
- @Override
- public int[] onCreateDrawableState(int extraSpace) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
-
- if (isPrivate)
- mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
- else if (isLight)
- mergeDrawableStates(drawableState, STATE_LIGHT);
- else if (isDark)
- mergeDrawableStates(drawableState, STATE_DARK);
-
- return drawableState;
- }
-
- @Override
- public void onLightweightThemeChanged() {
- if (autoUpdateTheme && theme.isEnabled())
- setTheme(theme.isLightTheme());
- }
-
- @Override
- public void onLightweightThemeReset() {
- if (autoUpdateTheme)
- resetTheme();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- onLightweightThemeChanged();
- }
-
- public boolean isPrivateMode() {
- return isPrivate;
- }
-
- public void setPrivateMode(boolean isPrivate) {
- if (this.isPrivate != isPrivate) {
- this.isPrivate = isPrivate;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setTheme(boolean isLight) {
- // Set the theme only if it is different from existing theme.
- if ((isLight && this.isLight != isLight) ||
- (!isLight && this.isDark == isLight)) {
- if (isLight) {
- this.isLight = true;
- this.isDark = false;
- } else {
- this.isLight = false;
- this.isDark = true;
- }
-
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void resetTheme() {
- if (isLight || isDark) {
- isLight = false;
- isDark = false;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setAutoUpdateTheme(boolean autoUpdateTheme) {
- if (theme == null) {
- return;
- }
-
- if (this.autoUpdateTheme != autoUpdateTheme) {
- this.autoUpdateTheme = autoUpdateTheme;
-
- if (autoUpdateTheme)
- theme.addListener(this);
- else
- theme.removeListener(this);
- }
- }
-
- @Override
- public void setImageDrawable(final Drawable drawable) {
- setTintedImageDrawable(drawable);
- }
-
- private void setTintedImageDrawable(final Drawable drawable) {
- final Drawable tintedDrawable;
- if (drawableColors == null || R.id.bookmark == getId()) {
- // NB: The bookmarked state uses a blue star, so this is a hack to keep it untinted.
- // NB: If we tint a drawable with a null ColorStateList, it will override
- // any existing colorFilters and tint... so don't!
- tintedDrawable = drawable;
- } else if (drawable == null) {
- tintedDrawable = null;
- } else {
- tintedDrawable = DrawableUtil.tintDrawableWithStateList(drawable, drawableColors);
- }
- super.setImageDrawable(tintedDrawable);
- }
-
- public ColorDrawable getColorDrawable(int id) {
- return new ColorDrawable(ContextCompat.getColor(getContext(), id));
- }
-
- protected LightweightTheme getTheme() {
- return theme;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedImageView.java b/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedImageView.java
deleted file mode 100644
index befbe6fb5..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedImageView.java
+++ /dev/null
@@ -1,199 +0,0 @@
-// This file is generated by generate_themed_views.py; do not edit.
-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget.themed;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.lwt.LightweightTheme;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.DrawableUtil;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-
-public class ThemedImageView extends android.widget.ImageView
- implements LightweightTheme.OnChangeListener {
- private LightweightTheme theme;
-
- private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
- private static final int[] STATE_LIGHT = { R.attr.state_light };
- private static final int[] STATE_DARK = { R.attr.state_dark };
-
- protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
- protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
- protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
-
- private boolean isPrivate;
- private boolean isLight;
- private boolean isDark;
- private boolean autoUpdateTheme; // always false if there's no theme.
-
- private ColorStateList drawableColors;
-
- public ThemedImageView(Context context, AttributeSet attrs) {
- super(context, attrs);
- initialize(context, attrs, 0);
- }
-
- public ThemedImageView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- initialize(context, attrs, defStyle);
- }
-
- private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
- // The theme can be null, particularly if we might be instantiating this
- // View in an IDE, with no ambient GeckoApplication.
- final Context applicationContext = context.getApplicationContext();
- if (applicationContext instanceof GeckoApplication) {
- theme = ((GeckoApplication) applicationContext).getLightweightTheme();
- }
-
- final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
- autoUpdateTheme = theme != null && a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
- a.recycle();
-
- final TypedArray themedA = context.obtainStyledAttributes(attrs, R.styleable.ThemedView, defStyle, 0);
- drawableColors = themedA.getColorStateList(R.styleable.ThemedView_drawableTintList);
- themedA.recycle();
-
- // Apply the tint initially - the Drawable is
- // initially set by XML via super's constructor.
- setTintedImageDrawable(getDrawable());
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- if (autoUpdateTheme)
- theme.addListener(this);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- if (autoUpdateTheme)
- theme.removeListener(this);
- }
-
- @Override
- public int[] onCreateDrawableState(int extraSpace) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
-
- if (isPrivate)
- mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
- else if (isLight)
- mergeDrawableStates(drawableState, STATE_LIGHT);
- else if (isDark)
- mergeDrawableStates(drawableState, STATE_DARK);
-
- return drawableState;
- }
-
- @Override
- public void onLightweightThemeChanged() {
- if (autoUpdateTheme && theme.isEnabled())
- setTheme(theme.isLightTheme());
- }
-
- @Override
- public void onLightweightThemeReset() {
- if (autoUpdateTheme)
- resetTheme();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- onLightweightThemeChanged();
- }
-
- public boolean isPrivateMode() {
- return isPrivate;
- }
-
- public void setPrivateMode(boolean isPrivate) {
- if (this.isPrivate != isPrivate) {
- this.isPrivate = isPrivate;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setTheme(boolean isLight) {
- // Set the theme only if it is different from existing theme.
- if ((isLight && this.isLight != isLight) ||
- (!isLight && this.isDark == isLight)) {
- if (isLight) {
- this.isLight = true;
- this.isDark = false;
- } else {
- this.isLight = false;
- this.isDark = true;
- }
-
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void resetTheme() {
- if (isLight || isDark) {
- isLight = false;
- isDark = false;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setAutoUpdateTheme(boolean autoUpdateTheme) {
- if (theme == null) {
- return;
- }
-
- if (this.autoUpdateTheme != autoUpdateTheme) {
- this.autoUpdateTheme = autoUpdateTheme;
-
- if (autoUpdateTheme)
- theme.addListener(this);
- else
- theme.removeListener(this);
- }
- }
-
- @Override
- public void setImageDrawable(final Drawable drawable) {
- setTintedImageDrawable(drawable);
- }
-
- private void setTintedImageDrawable(final Drawable drawable) {
- final Drawable tintedDrawable;
- if (drawableColors == null) {
- // NB: If we tint a drawable with a null ColorStateList, it will override
- // any existing colorFilters and tint... so don't!
- tintedDrawable = drawable;
- } else if (drawable == null) {
- tintedDrawable = null;
- } else {
- tintedDrawable = DrawableUtil.tintDrawableWithStateList(drawable, drawableColors);
- }
- super.setImageDrawable(tintedDrawable);
- }
-
- public ColorDrawable getColorDrawable(int id) {
- return new ColorDrawable(ContextCompat.getColor(getContext(), id));
- }
-
- protected LightweightTheme getTheme() {
- return theme;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedLinearLayout.java b/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedLinearLayout.java
deleted file mode 100644
index 87ec58ce0..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedLinearLayout.java
+++ /dev/null
@@ -1,167 +0,0 @@
-// This file is generated by generate_themed_views.py; do not edit.
-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget.themed;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.lwt.LightweightTheme;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.DrawableUtil;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-
-public class ThemedLinearLayout extends android.widget.LinearLayout
- implements LightweightTheme.OnChangeListener {
- private LightweightTheme theme;
-
- private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
- private static final int[] STATE_LIGHT = { R.attr.state_light };
- private static final int[] STATE_DARK = { R.attr.state_dark };
-
- protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
- protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
- protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
-
- private boolean isPrivate;
- private boolean isLight;
- private boolean isDark;
- private boolean autoUpdateTheme; // always false if there's no theme.
-
- private ColorStateList drawableColors;
-
- public ThemedLinearLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- initialize(context, attrs, 0);
- }
-
- private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
- // The theme can be null, particularly if we might be instantiating this
- // View in an IDE, with no ambient GeckoApplication.
- final Context applicationContext = context.getApplicationContext();
- if (applicationContext instanceof GeckoApplication) {
- theme = ((GeckoApplication) applicationContext).getLightweightTheme();
- }
-
- final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
- autoUpdateTheme = theme != null && a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
- a.recycle();
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- if (autoUpdateTheme)
- theme.addListener(this);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- if (autoUpdateTheme)
- theme.removeListener(this);
- }
-
- @Override
- public int[] onCreateDrawableState(int extraSpace) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
-
- if (isPrivate)
- mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
- else if (isLight)
- mergeDrawableStates(drawableState, STATE_LIGHT);
- else if (isDark)
- mergeDrawableStates(drawableState, STATE_DARK);
-
- return drawableState;
- }
-
- @Override
- public void onLightweightThemeChanged() {
- if (autoUpdateTheme && theme.isEnabled())
- setTheme(theme.isLightTheme());
- }
-
- @Override
- public void onLightweightThemeReset() {
- if (autoUpdateTheme)
- resetTheme();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- onLightweightThemeChanged();
- }
-
- public boolean isPrivateMode() {
- return isPrivate;
- }
-
- public void setPrivateMode(boolean isPrivate) {
- if (this.isPrivate != isPrivate) {
- this.isPrivate = isPrivate;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setTheme(boolean isLight) {
- // Set the theme only if it is different from existing theme.
- if ((isLight && this.isLight != isLight) ||
- (!isLight && this.isDark == isLight)) {
- if (isLight) {
- this.isLight = true;
- this.isDark = false;
- } else {
- this.isLight = false;
- this.isDark = true;
- }
-
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void resetTheme() {
- if (isLight || isDark) {
- isLight = false;
- isDark = false;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setAutoUpdateTheme(boolean autoUpdateTheme) {
- if (theme == null) {
- return;
- }
-
- if (this.autoUpdateTheme != autoUpdateTheme) {
- this.autoUpdateTheme = autoUpdateTheme;
-
- if (autoUpdateTheme)
- theme.addListener(this);
- else
- theme.removeListener(this);
- }
- }
-
- public ColorDrawable getColorDrawable(int id) {
- return new ColorDrawable(ContextCompat.getColor(getContext(), id));
- }
-
- protected LightweightTheme getTheme() {
- return theme;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedRelativeLayout.java b/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedRelativeLayout.java
deleted file mode 100644
index 14ef25c62..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedRelativeLayout.java
+++ /dev/null
@@ -1,172 +0,0 @@
-// This file is generated by generate_themed_views.py; do not edit.
-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget.themed;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.lwt.LightweightTheme;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.DrawableUtil;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-
-public class ThemedRelativeLayout extends android.widget.RelativeLayout
- implements LightweightTheme.OnChangeListener {
- private LightweightTheme theme;
-
- private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
- private static final int[] STATE_LIGHT = { R.attr.state_light };
- private static final int[] STATE_DARK = { R.attr.state_dark };
-
- protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
- protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
- protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
-
- private boolean isPrivate;
- private boolean isLight;
- private boolean isDark;
- private boolean autoUpdateTheme; // always false if there's no theme.
-
- private ColorStateList drawableColors;
-
- public ThemedRelativeLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- initialize(context, attrs, 0);
- }
-
- public ThemedRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- initialize(context, attrs, defStyle);
- }
-
- private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
- // The theme can be null, particularly if we might be instantiating this
- // View in an IDE, with no ambient GeckoApplication.
- final Context applicationContext = context.getApplicationContext();
- if (applicationContext instanceof GeckoApplication) {
- theme = ((GeckoApplication) applicationContext).getLightweightTheme();
- }
-
- final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
- autoUpdateTheme = theme != null && a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
- a.recycle();
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- if (autoUpdateTheme)
- theme.addListener(this);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- if (autoUpdateTheme)
- theme.removeListener(this);
- }
-
- @Override
- public int[] onCreateDrawableState(int extraSpace) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
-
- if (isPrivate)
- mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
- else if (isLight)
- mergeDrawableStates(drawableState, STATE_LIGHT);
- else if (isDark)
- mergeDrawableStates(drawableState, STATE_DARK);
-
- return drawableState;
- }
-
- @Override
- public void onLightweightThemeChanged() {
- if (autoUpdateTheme && theme.isEnabled())
- setTheme(theme.isLightTheme());
- }
-
- @Override
- public void onLightweightThemeReset() {
- if (autoUpdateTheme)
- resetTheme();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- onLightweightThemeChanged();
- }
-
- public boolean isPrivateMode() {
- return isPrivate;
- }
-
- public void setPrivateMode(boolean isPrivate) {
- if (this.isPrivate != isPrivate) {
- this.isPrivate = isPrivate;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setTheme(boolean isLight) {
- // Set the theme only if it is different from existing theme.
- if ((isLight && this.isLight != isLight) ||
- (!isLight && this.isDark == isLight)) {
- if (isLight) {
- this.isLight = true;
- this.isDark = false;
- } else {
- this.isLight = false;
- this.isDark = true;
- }
-
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void resetTheme() {
- if (isLight || isDark) {
- isLight = false;
- isDark = false;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setAutoUpdateTheme(boolean autoUpdateTheme) {
- if (theme == null) {
- return;
- }
-
- if (this.autoUpdateTheme != autoUpdateTheme) {
- this.autoUpdateTheme = autoUpdateTheme;
-
- if (autoUpdateTheme)
- theme.addListener(this);
- else
- theme.removeListener(this);
- }
- }
-
- public ColorDrawable getColorDrawable(int id) {
- return new ColorDrawable(ContextCompat.getColor(getContext(), id));
- }
-
- protected LightweightTheme getTheme() {
- return theme;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedTextSwitcher.java b/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedTextSwitcher.java
deleted file mode 100644
index 294abd9ba..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedTextSwitcher.java
+++ /dev/null
@@ -1,167 +0,0 @@
-// This file is generated by generate_themed_views.py; do not edit.
-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget.themed;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.lwt.LightweightTheme;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.DrawableUtil;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-
-public class ThemedTextSwitcher extends android.widget.TextSwitcher
- implements LightweightTheme.OnChangeListener {
- private LightweightTheme theme;
-
- private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
- private static final int[] STATE_LIGHT = { R.attr.state_light };
- private static final int[] STATE_DARK = { R.attr.state_dark };
-
- protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
- protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
- protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
-
- private boolean isPrivate;
- private boolean isLight;
- private boolean isDark;
- private boolean autoUpdateTheme; // always false if there's no theme.
-
- private ColorStateList drawableColors;
-
- public ThemedTextSwitcher(Context context, AttributeSet attrs) {
- super(context, attrs);
- initialize(context, attrs, 0);
- }
-
- private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
- // The theme can be null, particularly if we might be instantiating this
- // View in an IDE, with no ambient GeckoApplication.
- final Context applicationContext = context.getApplicationContext();
- if (applicationContext instanceof GeckoApplication) {
- theme = ((GeckoApplication) applicationContext).getLightweightTheme();
- }
-
- final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
- autoUpdateTheme = theme != null && a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
- a.recycle();
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- if (autoUpdateTheme)
- theme.addListener(this);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- if (autoUpdateTheme)
- theme.removeListener(this);
- }
-
- @Override
- public int[] onCreateDrawableState(int extraSpace) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
-
- if (isPrivate)
- mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
- else if (isLight)
- mergeDrawableStates(drawableState, STATE_LIGHT);
- else if (isDark)
- mergeDrawableStates(drawableState, STATE_DARK);
-
- return drawableState;
- }
-
- @Override
- public void onLightweightThemeChanged() {
- if (autoUpdateTheme && theme.isEnabled())
- setTheme(theme.isLightTheme());
- }
-
- @Override
- public void onLightweightThemeReset() {
- if (autoUpdateTheme)
- resetTheme();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- onLightweightThemeChanged();
- }
-
- public boolean isPrivateMode() {
- return isPrivate;
- }
-
- public void setPrivateMode(boolean isPrivate) {
- if (this.isPrivate != isPrivate) {
- this.isPrivate = isPrivate;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setTheme(boolean isLight) {
- // Set the theme only if it is different from existing theme.
- if ((isLight && this.isLight != isLight) ||
- (!isLight && this.isDark == isLight)) {
- if (isLight) {
- this.isLight = true;
- this.isDark = false;
- } else {
- this.isLight = false;
- this.isDark = true;
- }
-
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void resetTheme() {
- if (isLight || isDark) {
- isLight = false;
- isDark = false;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setAutoUpdateTheme(boolean autoUpdateTheme) {
- if (theme == null) {
- return;
- }
-
- if (this.autoUpdateTheme != autoUpdateTheme) {
- this.autoUpdateTheme = autoUpdateTheme;
-
- if (autoUpdateTheme)
- theme.addListener(this);
- else
- theme.removeListener(this);
- }
- }
-
- public ColorDrawable getColorDrawable(int id) {
- return new ColorDrawable(ContextCompat.getColor(getContext(), id));
- }
-
- protected LightweightTheme getTheme() {
- return theme;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedTextView.java b/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedTextView.java
deleted file mode 100644
index 51a23a406..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedTextView.java
+++ /dev/null
@@ -1,172 +0,0 @@
-// This file is generated by generate_themed_views.py; do not edit.
-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget.themed;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.lwt.LightweightTheme;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.DrawableUtil;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-
-public class ThemedTextView extends android.widget.TextView
- implements LightweightTheme.OnChangeListener {
- private LightweightTheme theme;
-
- private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
- private static final int[] STATE_LIGHT = { R.attr.state_light };
- private static final int[] STATE_DARK = { R.attr.state_dark };
-
- protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
- protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
- protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
-
- private boolean isPrivate;
- private boolean isLight;
- private boolean isDark;
- private boolean autoUpdateTheme; // always false if there's no theme.
-
- private ColorStateList drawableColors;
-
- public ThemedTextView(Context context, AttributeSet attrs) {
- super(context, attrs);
- initialize(context, attrs, 0);
- }
-
- public ThemedTextView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- initialize(context, attrs, defStyle);
- }
-
- private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
- // The theme can be null, particularly if we might be instantiating this
- // View in an IDE, with no ambient GeckoApplication.
- final Context applicationContext = context.getApplicationContext();
- if (applicationContext instanceof GeckoApplication) {
- theme = ((GeckoApplication) applicationContext).getLightweightTheme();
- }
-
- final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
- autoUpdateTheme = theme != null && a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
- a.recycle();
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- if (autoUpdateTheme)
- theme.addListener(this);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- if (autoUpdateTheme)
- theme.removeListener(this);
- }
-
- @Override
- public int[] onCreateDrawableState(int extraSpace) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
-
- if (isPrivate)
- mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
- else if (isLight)
- mergeDrawableStates(drawableState, STATE_LIGHT);
- else if (isDark)
- mergeDrawableStates(drawableState, STATE_DARK);
-
- return drawableState;
- }
-
- @Override
- public void onLightweightThemeChanged() {
- if (autoUpdateTheme && theme.isEnabled())
- setTheme(theme.isLightTheme());
- }
-
- @Override
- public void onLightweightThemeReset() {
- if (autoUpdateTheme)
- resetTheme();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- onLightweightThemeChanged();
- }
-
- public boolean isPrivateMode() {
- return isPrivate;
- }
-
- public void setPrivateMode(boolean isPrivate) {
- if (this.isPrivate != isPrivate) {
- this.isPrivate = isPrivate;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setTheme(boolean isLight) {
- // Set the theme only if it is different from existing theme.
- if ((isLight && this.isLight != isLight) ||
- (!isLight && this.isDark == isLight)) {
- if (isLight) {
- this.isLight = true;
- this.isDark = false;
- } else {
- this.isLight = false;
- this.isDark = true;
- }
-
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void resetTheme() {
- if (isLight || isDark) {
- isLight = false;
- isDark = false;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setAutoUpdateTheme(boolean autoUpdateTheme) {
- if (theme == null) {
- return;
- }
-
- if (this.autoUpdateTheme != autoUpdateTheme) {
- this.autoUpdateTheme = autoUpdateTheme;
-
- if (autoUpdateTheme)
- theme.addListener(this);
- else
- theme.removeListener(this);
- }
- }
-
- public ColorDrawable getColorDrawable(int id) {
- return new ColorDrawable(ContextCompat.getColor(getContext(), id));
- }
-
- protected LightweightTheme getTheme() {
- return theme;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedView.java b/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedView.java
deleted file mode 100644
index 77ecfd271..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedView.java
+++ /dev/null
@@ -1,172 +0,0 @@
-// This file is generated by generate_themed_views.py; do not edit.
-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget.themed;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.lwt.LightweightTheme;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.DrawableUtil;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-
-public class ThemedView extends android.view.View
- implements LightweightTheme.OnChangeListener {
- private LightweightTheme theme;
-
- private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
- private static final int[] STATE_LIGHT = { R.attr.state_light };
- private static final int[] STATE_DARK = { R.attr.state_dark };
-
- protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
- protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
- protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
-
- private boolean isPrivate;
- private boolean isLight;
- private boolean isDark;
- private boolean autoUpdateTheme; // always false if there's no theme.
-
- private ColorStateList drawableColors;
-
- public ThemedView(Context context, AttributeSet attrs) {
- super(context, attrs);
- initialize(context, attrs, 0);
- }
-
- public ThemedView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- initialize(context, attrs, defStyle);
- }
-
- private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
- // The theme can be null, particularly if we might be instantiating this
- // View in an IDE, with no ambient GeckoApplication.
- final Context applicationContext = context.getApplicationContext();
- if (applicationContext instanceof GeckoApplication) {
- theme = ((GeckoApplication) applicationContext).getLightweightTheme();
- }
-
- final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
- autoUpdateTheme = theme != null && a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
- a.recycle();
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- if (autoUpdateTheme)
- theme.addListener(this);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- if (autoUpdateTheme)
- theme.removeListener(this);
- }
-
- @Override
- public int[] onCreateDrawableState(int extraSpace) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
-
- if (isPrivate)
- mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
- else if (isLight)
- mergeDrawableStates(drawableState, STATE_LIGHT);
- else if (isDark)
- mergeDrawableStates(drawableState, STATE_DARK);
-
- return drawableState;
- }
-
- @Override
- public void onLightweightThemeChanged() {
- if (autoUpdateTheme && theme.isEnabled())
- setTheme(theme.isLightTheme());
- }
-
- @Override
- public void onLightweightThemeReset() {
- if (autoUpdateTheme)
- resetTheme();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- onLightweightThemeChanged();
- }
-
- public boolean isPrivateMode() {
- return isPrivate;
- }
-
- public void setPrivateMode(boolean isPrivate) {
- if (this.isPrivate != isPrivate) {
- this.isPrivate = isPrivate;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setTheme(boolean isLight) {
- // Set the theme only if it is different from existing theme.
- if ((isLight && this.isLight != isLight) ||
- (!isLight && this.isDark == isLight)) {
- if (isLight) {
- this.isLight = true;
- this.isDark = false;
- } else {
- this.isLight = false;
- this.isDark = true;
- }
-
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void resetTheme() {
- if (isLight || isDark) {
- isLight = false;
- isDark = false;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setAutoUpdateTheme(boolean autoUpdateTheme) {
- if (theme == null) {
- return;
- }
-
- if (this.autoUpdateTheme != autoUpdateTheme) {
- this.autoUpdateTheme = autoUpdateTheme;
-
- if (autoUpdateTheme)
- theme.addListener(this);
- else
- theme.removeListener(this);
- }
- }
-
- public ColorDrawable getColorDrawable(int id) {
- return new ColorDrawable(ContextCompat.getColor(getContext(), id));
- }
-
- protected LightweightTheme getTheme() {
- return theme;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedView.java.frag b/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedView.java.frag
deleted file mode 100644
index e731a0ebe..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/themed/ThemedView.java.frag
+++ /dev/null
@@ -1,211 +0,0 @@
-//#filter substitution
-// This file is generated by generate_themed_views.py; do not edit.
-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.widget.themed;
-
-import android.support.v4.content.ContextCompat;
-import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.lwt.LightweightTheme;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.util.DrawableUtil;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-
-public class Themed@VIEW_NAME_SUFFIX@ extends @BASE_TYPE@
- implements LightweightTheme.OnChangeListener {
- private LightweightTheme theme;
-
- private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
- private static final int[] STATE_LIGHT = { R.attr.state_light };
- private static final int[] STATE_DARK = { R.attr.state_dark };
-
- protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
- protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
- protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
-
- private boolean isPrivate;
- private boolean isLight;
- private boolean isDark;
- private boolean autoUpdateTheme; // always false if there's no theme.
-
- private ColorStateList drawableColors;
-
- public Themed@VIEW_NAME_SUFFIX@(Context context, AttributeSet attrs) {
- super(context, attrs);
- initialize(context, attrs, 0);
- }
-
-//#ifdef STYLE_CONSTRUCTOR
- public Themed@VIEW_NAME_SUFFIX@(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- initialize(context, attrs, defStyle);
- }
-
-//#endif
- private void initialize(final Context context, final AttributeSet attrs, final int defStyle) {
- // The theme can be null, particularly if we might be instantiating this
- // View in an IDE, with no ambient GeckoApplication.
- final Context applicationContext = context.getApplicationContext();
- if (applicationContext instanceof GeckoApplication) {
- theme = ((GeckoApplication) applicationContext).getLightweightTheme();
- }
-
- final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
- autoUpdateTheme = theme != null && a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
- a.recycle();
-//#if TINT_FOREGROUND_DRAWABLE
-
- final TypedArray themedA = context.obtainStyledAttributes(attrs, R.styleable.ThemedView, defStyle, 0);
- drawableColors = themedA.getColorStateList(R.styleable.ThemedView_drawableTintList);
- themedA.recycle();
-
- // Apply the tint initially - the Drawable is
- // initially set by XML via super's constructor.
- setTintedImageDrawable(getDrawable());
-//#endif
- }
-
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- if (autoUpdateTheme)
- theme.addListener(this);
- }
-
- @Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- if (autoUpdateTheme)
- theme.removeListener(this);
- }
-
- @Override
- public int[] onCreateDrawableState(int extraSpace) {
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
-
- if (isPrivate)
- mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
- else if (isLight)
- mergeDrawableStates(drawableState, STATE_LIGHT);
- else if (isDark)
- mergeDrawableStates(drawableState, STATE_DARK);
-
- return drawableState;
- }
-
- @Override
- public void onLightweightThemeChanged() {
- if (autoUpdateTheme && theme.isEnabled())
- setTheme(theme.isLightTheme());
- }
-
- @Override
- public void onLightweightThemeReset() {
- if (autoUpdateTheme)
- resetTheme();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- onLightweightThemeChanged();
- }
-
- public boolean isPrivateMode() {
- return isPrivate;
- }
-
- public void setPrivateMode(boolean isPrivate) {
- if (this.isPrivate != isPrivate) {
- this.isPrivate = isPrivate;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setTheme(boolean isLight) {
- // Set the theme only if it is different from existing theme.
- if ((isLight && this.isLight != isLight) ||
- (!isLight && this.isDark == isLight)) {
- if (isLight) {
- this.isLight = true;
- this.isDark = false;
- } else {
- this.isLight = false;
- this.isDark = true;
- }
-
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void resetTheme() {
- if (isLight || isDark) {
- isLight = false;
- isDark = false;
- refreshDrawableState();
- invalidate();
- }
- }
-
- public void setAutoUpdateTheme(boolean autoUpdateTheme) {
- if (theme == null) {
- return;
- }
-
- if (this.autoUpdateTheme != autoUpdateTheme) {
- this.autoUpdateTheme = autoUpdateTheme;
-
- if (autoUpdateTheme)
- theme.addListener(this);
- else
- theme.removeListener(this);
- }
- }
-
-//#ifdef TINT_FOREGROUND_DRAWABLE
- @Override
- public void setImageDrawable(final Drawable drawable) {
- setTintedImageDrawable(drawable);
- }
-
- private void setTintedImageDrawable(final Drawable drawable) {
- final Drawable tintedDrawable;
-//#ifdef BOOKMARK_NO_TINT
- if (drawableColors == null || R.id.bookmark == getId()) {
- // NB: The bookmarked state uses a blue star, so this is a hack to keep it untinted.
-//#else
- if (drawableColors == null) {
-//#endif
- // NB: If we tint a drawable with a null ColorStateList, it will override
- // any existing colorFilters and tint... so don't!
- tintedDrawable = drawable;
- } else if (drawable == null) {
- tintedDrawable = null;
- } else {
- tintedDrawable = DrawableUtil.tintDrawableWithStateList(drawable, drawableColors);
- }
- super.setImageDrawable(tintedDrawable);
- }
-
-//#endif
- public ColorDrawable getColorDrawable(int id) {
- return new ColorDrawable(ContextCompat.getColor(getContext(), id));
- }
-
- protected LightweightTheme getTheme() {
- return theme;
- }
-}
diff --git a/mobile/android/base/java/org/mozilla/gecko/widget/themed/generate_themed_views.py b/mobile/android/base/java/org/mozilla/gecko/widget/themed/generate_themed_views.py
deleted file mode 100644
index 3b5a00b40..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/widget/themed/generate_themed_views.py
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/bin/python
-
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-'''
-Script to generate Themed*.java source files for Fennec.
-
-This script runs the preprocessor on a input template and writes
-updated files into the source directory.
-
-To update the themed views, update the input template
-(ThemedView.java.frag) and run the script using 'mach python <script.py>'. Use version control to
-examine the differences, and don't forget to commit the changes to the
-template and the outputs.
-'''
-
-from __future__ import (
- print_function,
- unicode_literals,
-)
-
-import os
-
-from mozbuild.preprocessor import Preprocessor
-
-__DIR__ = os.path.dirname(os.path.abspath(__file__))
-
-template = os.path.join(__DIR__, 'ThemedView.java.frag')
-dest_format_string = 'Themed%(VIEW_NAME_SUFFIX)s.java'
-
-views = [
- dict(VIEW_NAME_SUFFIX='EditText',
- BASE_TYPE='android.widget.EditText',
- STYLE_CONSTRUCTOR=1),
- dict(VIEW_NAME_SUFFIX='FrameLayout',
- BASE_TYPE='android.widget.FrameLayout',
- STYLE_CONSTRUCTOR=1),
- dict(VIEW_NAME_SUFFIX='ImageButton',
- BASE_TYPE='android.widget.ImageButton',
- STYLE_CONSTRUCTOR=1,
- TINT_FOREGROUND_DRAWABLE=1,
- BOOKMARK_NO_TINT=1),
- dict(VIEW_NAME_SUFFIX='ImageView',
- BASE_TYPE='android.widget.ImageView',
- STYLE_CONSTRUCTOR=1,
- TINT_FOREGROUND_DRAWABLE=1),
- dict(VIEW_NAME_SUFFIX='LinearLayout',
- BASE_TYPE='android.widget.LinearLayout'),
- dict(VIEW_NAME_SUFFIX='RelativeLayout',
- BASE_TYPE='android.widget.RelativeLayout',
- STYLE_CONSTRUCTOR=1),
- dict(VIEW_NAME_SUFFIX='TextSwitcher',
- BASE_TYPE='android.widget.TextSwitcher'),
- dict(VIEW_NAME_SUFFIX='TextView',
- BASE_TYPE='android.widget.TextView',
- STYLE_CONSTRUCTOR=1),
- dict(VIEW_NAME_SUFFIX='View',
- BASE_TYPE='android.view.View',
- STYLE_CONSTRUCTOR=1),
-]
-
-for view in views:
- pp = Preprocessor(defines=view, marker='//#')
-
- dest = os.path.join(__DIR__, dest_format_string % view)
- with open(template, 'rU') as input:
- with open(dest, 'wt') as output:
- pp.processFile(input=input, output=output)
- print('%s' % dest)
diff --git a/mobile/android/base/locales/Makefile.in b/mobile/android/base/locales/Makefile.in
deleted file mode 100644
index ce0636c12..000000000
--- a/mobile/android/base/locales/Makefile.in
+++ /dev/null
@@ -1,114 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-include $(topsrcdir)/config/config.mk
-
-# special case some locale codes, he and id
-# http://code.google.com/p/android/issues/detail?id=3639
-AB_rCD = $(if $(filter he, $(AB_CD)),iw,$(if $(filter id, $(AB_CD)),in,$(subst -,-r,$(AB_CD))))
-
-# The search strings path is always passed to strings.xml.in; the
-# decision to include is made based on the feature flag at the
-# inclusion site.
-SEARCHSTRINGSPATH = $(abspath $(call MERGE_FILE,search_strings.dtd))
-
-SYNCSTRINGSPATH = $(abspath $(call MERGE_FILE,sync_strings.dtd))
-STRINGSPATH = $(abspath $(call MERGE_FILE,android_strings.dtd))
-ifeq (,$(XPI_NAME))
-BRANDPATH = $(topobjdir)/dist/bin/chrome/$(AB_CD)/locale/branding/brand.dtd
-else
-BRANDPATH = $(ABS_DIST)/xpi-stage/$(XPI_NAME)/chrome/$(AB_CD)/locale/branding/brand.dtd
-endif
-$(warnIfEmpty,AB_CD) # todo: $(errorIfEmpty )
-
-dir-res-values := ../res/values
-strings-xml := $(dir-res-values)/strings.xml
-strings-xml-in := $(srcdir)/../strings.xml.in
-
-GARBAGE += $(strings-xml)
-
-dir-res-raw := ../res/raw
-suggestedsites := $(dir-res-raw)/suggestedsites.json
-browsersearch := $(dir-res-raw)/browsersearch.json
-
-libs realchrome:: \
- $(strings-xml) \
- $(NULL)
-
-chrome-%:: AB_CD=$*
-chrome-%::
- @$(MAKE) \
- $(dir-res-values)-$(AB_rCD)/strings.xml \
- $(dir-res-raw)-$(AB_rCD)/suggestedsites.json \
- $(dir-res-raw)-$(AB_rCD)/browsersearch.json \
- AB_CD=$*
-
-# Determine the ../res/values[-*]/ path
-strings-xml-bypath = $(filter %/strings.xml,$(MAKECMDGOALS))
-ifeq (,$(strip $(strings-xml-bypath)))
- strings-xml-bypath = $(strings-xml)
-endif
-dir-strings-xml = $(patsubst %/,%,$(dir $(strings-xml-bypath)))
-
-strings-xml-preqs =\
- $(strings-xml-in) \
- $(BRANDPATH) \
- $(STRINGSPATH) \
- $(SEARCHSTRINGSPATH) \
- $(SYNCSTRINGSPATH) \
- $(if $(IS_LANGUAGE_REPACK),FORCE) \
- $(NULL)
-
-$(dir-strings-xml)/strings.xml: $(strings-xml-preqs)
- $(call py_action,preprocessor, \
- $(DEFINES) \
- $(ACDEFINES) \
- -DANDROID_PACKAGE_NAME=$(ANDROID_PACKAGE_NAME) \
- -DBRANDPATH='$(BRANDPATH)' \
- -DMOZ_APP_DISPLAYNAME='@MOZ_APP_DISPLAYNAME@' \
- -DSTRINGSPATH='$(STRINGSPATH)' \
- -DSYNCSTRINGSPATH='$(SYNCSTRINGSPATH)' \
- -DSEARCHSTRINGSPATH='$(SEARCHSTRINGSPATH)' \
- $< \
- -o $@)
-
-# Arg 1: Valid Make identifier, like suggestedsites.
-# Arg 2: File name, like suggestedsites.json.
-define generated_file_template
-
-# Determine the ../res/raw[-*] path. This can be ../res/raw when no
-# locale is explicitly specified.
-$(1)-bypath = $(filter %/$(2),$(MAKECMDGOALS))
-ifeq (,$$(strip $$($(1)-bypath)))
- $(1)-bypath = $($(1))
-endif
-$(1)-dstdir-raw = $$(patsubst %/,%,$$(dir $$($(1)-bypath)))
-
-GARBAGE += $($(1))
-
-libs realchrome:: $($(1))
-endef
-
-# L10NBASEDIR is not defined for en-US.
-l10n-srcdir := $(if $(filter en-US,$(AB_CD)),,$(or $(realpath $(L10NBASEDIR)),$(abspath $(L10NBASEDIR)))/$(AB_CD)/mobile/chrome)
-
-$(eval $(call generated_file_template,suggestedsites,suggestedsites.json))
-
-$(suggestedsites-dstdir-raw)/suggestedsites.json: FORCE
- $(call py_action,generate_suggestedsites, \
- --verbose \
- --android-package-name=$(ANDROID_PACKAGE_NAME) \
- --resources=$(srcdir)/../resources \
- $(if $(filter en-US,$(AB_CD)),,--srcdir=$(l10n-srcdir)) \
- --srcdir=$(topsrcdir)/mobile/locales/en-US/chrome \
- $@)
-
-$(eval $(call generated_file_template,browsersearch,browsersearch.json))
-
-$(browsersearch-dstdir-raw)/browsersearch.json: FORCE
- $(call py_action,generate_browsersearch, \
- --verbose \
- $(if $(filter en-US,$(AB_CD)),,--srcdir=$(l10n-srcdir)) \
- --srcdir=$(topsrcdir)/mobile/locales/en-US/chrome \
- $@)
diff --git a/mobile/android/base/locales/en-US/android_strings.dtd b/mobile/android/base/locales/en-US/android_strings.dtd
deleted file mode 100644
index 140e2a216..000000000
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ /dev/null
@@ -1,848 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!ENTITY firstrun_panel_title_welcome "Welcome">
-
-<!ENTITY firstrun_urlbar_message "Welcome to &brandShortName;">
-<!ENTITY firstrun_urlbar_subtext "Find things faster with helpful search suggestion shortcuts.">
-<!ENTITY firstrun_bookmarks_title "History">
-<!ENTITY firstrun_bookmarks_message "Your faves, front and center">
-<!ENTITY firstrun_bookmarks_subtext "Get results from your bookmarks and history when you search.">
-<!ENTITY firstrun_data_title "Data">
-<!ENTITY firstrun_data_message "Less data, more savings">
-<!ENTITY firstrun_data_subtext2 "Turn off images to spend less data on every site you visit.">
-<!ENTITY firstrun_sync_title "Sync">
-<!ENTITY firstrun_sync_message "&brandShortName;, always by your side">
-<!ENTITY firstrun_sync_subtext "Sync your tabs, passwords, and more everywhere you use it.">
-<!ENTITY firstrun_signin_message "Get connected, get started">
-<!ENTITY firstrun_signin_button "Sign in to Sync">
-<!ENTITY onboard_start_button_browser "Start Browsing">
-<!ENTITY firstrun_button_notnow "Not right now">
-<!ENTITY firstrun_button_next "Next">
-
-<!ENTITY firstrun_tabqueue_title "Links">
-<!-- Localization note (firstrun_tabqueue_message): 'Tab queue' is a feature that allows users to queue up or save links from outside of Firefox (without switching apps) - these links will be loaded in Firefox the next time Firefox is opened. -->
-<!ENTITY firstrun_tabqueue_message_off "Turn on Tab queue">
-<!ENTITY firstrun_tabqueue_subtext_off "Save links for later in &brandShortName; when tapping them in other apps.">
-
-<!ENTITY firstrun_tabqueue_message_on "Success!">
-<!ENTITY firstrun_tabqueue_subtext_on "You can always turn this off in &settings; under &pref_category_general;.">
-
-<!ENTITY firstrun_readerview_title "Articles">
-<!-- Localization note (firstrun_readerview_message): This is a casual way of describing getting rid of unnecessary things, and is referring to simplifying websites so only the article text and images are visible, removing unnecessary headers or ads. -->
-<!ENTITY firstrun_readerview_message "Lose the clutter">
-<!ENTITY firstrun_readerview_subtext "Use Reader View to make articles nicer to read \u2014 even offline.">
-
-<!-- Localization note (firstrun_devices_title): This is a casual way of addressing the user, somewhat referring to their online identity (which would include other devices, Firefox usage, accounts, etc). -->
-<!ENTITY firstrun_account_title "You">
-<!ENTITY firstrun_account_message "Have &brandShortName; on another device?">
-
-<!ENTITY onboard_start_restricted1 "Stay safe and in control with this simplified version of &brandShortName;.">
-
-<!-- Localization note: These are used as the titles of different pages on the home screen.
- They are automatically converted to all caps by the Android platform. -->
-<!ENTITY bookmarks_title "Bookmarks">
-<!ENTITY history_title "History">
-
-<!ENTITY switch_to_tab "Switch to tab">
-
-<!-- Localization note: Shown in a snackbar when tab is loaded from cache while device was offline. -->
-<!ENTITY tab_offline_version "Showing offline version">
-
-<!ENTITY crash_reporter_title "&brandShortName; Crash Reporter">
-<!ENTITY crash_message2 "&brandShortName; had a problem and crashed. Your tabs should be listed on the &brandShortName; Start page when you restart.">
-<!ENTITY crash_send_report_message3 "Tell &vendorShortName; about this crash so they can fix it">
-<!ENTITY crash_include_url2 "Include the address of the page I was on">
-<!ENTITY crash_sorry "We\'re sorry">
-<!ENTITY crash_comment "Add a comment (comments are publicly visible)">
-<!ENTITY crash_allow_contact2 "Allow &vendorShortName; to contact me about this report">
-<!ENTITY crash_email "Your email">
-<!ENTITY crash_closing_alert "Exit without sending a crash report?">
-<!ENTITY sending_crash_report "Sending crash report\u2026">
-<!ENTITY crash_close_label "Close">
-<!ENTITY crash_restart_label "Restart &brandShortName;">
-
-<!ENTITY url_bar_default_text2 "Search or enter address">
-
-<!ENTITY bookmark "Bookmark">
-<!ENTITY bookmark_remove "Remove bookmark">
-<!ENTITY bookmark_added "Bookmark added">
-<!-- Localization note (bookmark_already_added) : This string is
- used as a label in a toast. It is the verb "to bookmark", not
- the noun "a bookmark". -->
-<!ENTITY bookmark_already_added "Already bookmarked">
-<!ENTITY bookmark_removed "Bookmark removed">
-<!ENTITY bookmark_updated "Bookmark updated">
-<!ENTITY bookmark_options "Options">
-<!ENTITY screenshot_added_to_bookmarks "Screenshot added to bookmarks">
-<!-- Localization note (screenshot_folder_label_in_bookmarks): We save links to screenshots
- the user takes. The folder we store these links in is located in the bookmarks list
- and is labeled by this String. -->
-<!ENTITY screenshot_folder_label_in_bookmarks "Screenshots">
-<!ENTITY readinglist_smartfolder_label_in_bookmarks "Reading List">
-
-<!-- Localization note (bookmark_folder_items): The variable is replaced by the number of items
- in the folder. -->
-<!ENTITY bookmark_folder_items "&formatD; items">
-<!ENTITY bookmark_folder_one_item "1 item">
-
-<!ENTITY reader_saved_offline "Saved offline">
-<!-- Localization note (reader_switch_to_bookmarks) : This
- string is used as an action in a snackbar - it lets you
- "switch" to the bookmarks (saved items) panel. -->
-<!ENTITY reader_switch_to_bookmarks "Switch">
-
-<!ENTITY history_today_section "Today">
-<!ENTITY history_yesterday_section "Yesterday">
-<!ENTITY history_week_section3 "Last 7 days">
-<!ENTITY history_older_section3 "Older than 6 months">
-
-<!ENTITY search "Search">
-<!ENTITY reload "Reload">
-<!ENTITY forward "Forward">
-<!ENTITY menu "Menu">
-<!ENTITY back "Back">
-<!ENTITY stop "Stop">
-<!ENTITY site_security "Site Security">
-<!ENTITY edit_mode_cancel "Cancel">
-
-<!ENTITY close_tab "Close Tab">
-<!ENTITY one_tab "1 tab">
-<!-- Localization note (num_tabs2) : Number of tabs is always more than one.
- We can't use android plural forms, sadly. See bug #753859. -->
-<!ENTITY num_tabs2 "&formatD; tabs">
-<!ENTITY new_tab_opened "New tab opened">
-<!ENTITY new_private_tab_opened "New private tab opened">
-<!-- Localization note (switch_button_message): This string should be as short
- as possible because it's shown as a label in a toast. Ideally, this string
- is upper-case, to match Google and Android's convention. -->
-<!ENTITY switch_button_message "SWITCH">
-<!-- Localization note (tab_title_prefix_is_playing_audio): This string is not
- visible in the UI, but rather used as a text-to-speech content description
- for sight-impaired a11y users. The content description is set on a tab
- title in a list of open tabs when content in that tab is playing audio.
- &formatS; will be replaced with the title of the tab, as received from the
- web page. When audio is not playing in a tab, &formatS; will be used as
- the content description. -->
-<!ENTITY tab_title_prefix_is_playing_audio "Playing audio – &formatS;">
-
-<!ENTITY settings "Settings">
-<!ENTITY settings_title "Settings">
-<!ENTITY pref_category_general "General">
-<!ENTITY pref_category_general_summary3 "Home, language, tab queue">
-
-<!-- Localization note (pref_category_language) : This is the preferences
- section in which the user picks the locale in which to display Firefox
- UI. The locale includes both language and region concepts. -->
-<!ENTITY pref_category_language "Language">
-<!ENTITY pref_category_language_summary "Change the language of your browser">
-<!ENTITY pref_browser_locale "Browser language">
-
-<!-- Localization note (locale_system_default) : This string indicates that
- Firefox will use the locale currently selected in Android's settings
- to display browser chrome. -->
-<!ENTITY locale_system_default "System default">
-
-<!-- Localization note (overlay_share_label) : This is the label that appears
- in Android's intent chooser when sending a link to Firefox to bookmark,
- send to another device, or add to Reading List. -->
-<!ENTITY overlay_share_label "Add to &brandShortName;">
-
-<!-- Localization note (overlay_share_bookmark_btn_label) : This string is
- used in the share overlay menu to select an action. It is the verb
- "to bookmark", not the noun "a bookmark". -->
-<!ENTITY overlay_share_bookmark_btn_label "Bookmark">
-<!ENTITY overlay_share_bookmark_btn_label_already "Already bookmarked">
-<!ENTITY overlay_share_send_other "Send to other devices">
-
-<!-- Localization note (overlay_share_send_tab_btn_label) : Used on the
- share overlay menu to represent the "Send Tab" action when the user
- either has not set up Sync, or has no other devices to send a tab
- to. -->
-<!ENTITY overlay_share_send_tab_btn_label "Send to another device">
-<!ENTITY overlay_share_no_url "No link found in this share">
-<!ENTITY overlay_share_select_device "Select device">
-<!-- Localization note (overlay_no_synced_devices) : Used when the menu option
- to send a tab to a synced device is pressed and no other synced devices
- are found. -->
-<!ENTITY overlay_no_synced_devices "No Firefox Account connected devices found">
-
-<!ENTITY pref_category_search3 "Search">
-<!ENTITY pref_category_search_summary2 "Add, set default, show suggestions">
-<!ENTITY pref_category_accessibility "Accessibility">
-<!ENTITY pref_category_accessibility_summary2 "Text size, zoom, voice input">
-<!ENTITY pref_category_privacy_short "Privacy">
-<!ENTITY pref_category_privacy_summary4 "Tracking, logins, data choices">
-<!ENTITY pref_category_vendor2 "&vendorShortName; &brandShortName;">
-<!ENTITY pref_category_vendor_summary2 "About &brandShortName;, FAQs, feedback">
-<!ENTITY pref_category_datareporting "Data choices">
-<!ENTITY pref_category_logins "Logins">
-<!ENTITY pref_learn_more "Learn more">
-<!ENTITY pref_category_installed_search_engines "Installed search engines">
-<!ENTITY pref_category_add_search_providers "Add more search providers">
-<!ENTITY pref_category_search_restore_defaults "Restore search engines">
-<!ENTITY pref_search_restore_defaults "Restore defaults">
-<!ENTITY pref_search_restore_defaults_summary "Restore defaults">
-<!-- Localization note (pref_search_hint) : "TIP" as in "hint", "clue" etc. Displayed as an
- advisory message on the customise search providers settings page explaining how to add new
- search providers.
- The &formatI; in the string will be replaced by a small image of the icon described, and can be moved to wherever
- it is applicable. -->
-<!ENTITY pref_search_hint2 "TIP: Add any website to your list of search providers by long-pressing on its search field and then touching the &formatI; icon.">
-<!ENTITY pref_category_advanced "Advanced">
-<!-- Localization note (pref_category_advanced_summary3): “data saver” in this
- context means consuming less data, e.g. by not loading images, not
- “storing data”. -->
-<!ENTITY pref_category_advanced_summary3 "Restore tabs, data saver, developer tools">
-<!ENTITY pref_category_notifications "Notifications">
-<!ENTITY pref_category_notifications_summary "New features, website updates">
-<!ENTITY pref_content_notifications "Website updates">
-<!ENTITY pref_content_notifications_summary2 "Discover new content from supported sites">
-<!ENTITY pref_developer_remotedebugging_usb "Remote debugging via USB">
-<!ENTITY pref_developer_remotedebugging_wifi "Remote debugging via Wi-Fi">
-<!ENTITY pref_developer_remotedebugging_wifi_disabled_summary "Wi-Fi debugging requires your device to have a QR code reader app installed.">
-<!ENTITY pref_remember_signons2 "Remember logins">
-<!ENTITY pref_manage_logins "Manage logins">
-
-<!ENTITY pref_category_home "Home">
-<!ENTITY pref_category_home_summary "Customize your homepage">
-<!ENTITY pref_category_home_panels "Panels">
-<!ENTITY pref_category_home_add_ons "Add-ons">
-<!ENTITY pref_home_updates2 "Content updates">
-<!ENTITY pref_home_updates_enabled "Enabled">
-<!ENTITY pref_home_updates_wifi "Only over Wi-Fi">
-<!ENTITY pref_category_home_homepage "Homepage">
-<!ENTITY home_homepage_title "Set a Homepage">
-<!-- Localization note (home_homepage_radio_user_address): The user will see a series of radio
- buttons to choose the homepage they'd like to start on. When they click the radio
- button for this string, they will use the built-in default Firefox homepage (about:home). -->
-<!ENTITY home_homepage_radio_default "&brandShortName; Home">
-<!-- Localization note (home_homepage_radio_user_address): The user will see a series of radio
- buttons to choose the homepage they'd like to start on. When they click the radio
- button for this string, a text field will appear below the radio button and allow the
- user to insert an address of their choice. -->
-<!ENTITY home_homepage_radio_user_address "Custom">
-<!-- Localization note (home_homepage_hint_user_address): The user will see a series of
- radio buttons to choose the homepage they'd like to start on. When they click a
- particular radio button, a text field will appear below the radio button and allow the
- user to insert an address of their choice. This string is the hint text to that
- text field. -->
-<!ENTITY home_homepage_hint_user_address "Enter address or search term">
-
-<!-- Localization note: These are shown in the left sidebar on tablets -->
-<!ENTITY pref_header_general "General">
-<!ENTITY pref_header_search "Search">
-<!ENTITY pref_header_privacy_short "Privacy">
-<!ENTITY pref_header_accessibility "Accessibility">
-<!ENTITY pref_header_notifications "Notifications">
-<!ENTITY pref_header_advanced "Advanced">
-<!ENTITY pref_header_help "Help">
-<!ENTITY pref_header_vendor "&vendorShortName;">
-
-<!ENTITY pref_cookies_menu "Cookies">
-<!ENTITY pref_cookies_accept_all "Enabled">
-<!ENTITY pref_cookies_not_accept_foreign "Enabled, excluding 3rd party">
-<!ENTITY pref_cookies_disabled "Disabled">
-
-<!-- Localization note (pref_category_data_saver): “data saver” in this
- context means consuming less data, e.g. by not loading images, not
- “storing data”. -->
-<!ENTITY pref_category_data_saver "Data saver">
-<!ENTITY pref_category_media "Media">
-<!ENTITY pref_category_developer_tools "Developer tools">
-
-<!ENTITY pref_tap_to_load_images_title2 "Show images">
-<!ENTITY pref_tap_to_load_images_enabled "Always">
-<!ENTITY pref_tap_to_load_images_data "Only over Wi-Fi">
-<!ENTITY pref_tap_to_load_images_disabled2 "Blocked">
-
-<!ENTITY pref_show_web_fonts "Show web fonts">
-<!ENTITY pref_show_web_fonts_summary2 "Download remote fonts when loading a page">
-
-<!ENTITY pref_tracking_protection_title2 "Tracking Protection">
-<!ENTITY pref_tracking_protection_summary3 "Enabled in Private Browsing">
-<!ENTITY pref_donottrack_title "Do not track">
-<!ENTITY pref_donottrack_summary "&brandShortName; will tell sites that you do not want to be tracked">
-
-<!ENTITY pref_tracking_protection_enabled "Enabled">
-<!ENTITY pref_tracking_protection_enabled_pb "Enabled in Private Browsing">
-<!ENTITY pref_tracking_protection_disabled "Disabled">
-
-<!ENTITY pref_whats_new_notification "What\'s new in &brandShortName;">
-<!ENTITY pref_whats_new_notification_summary "Learn about new features after an update">
-
-<!-- Localization note (pref_category_experimental): Title of a sub category in the 'advanced' category
- for experimental features. -->
-<!ENTITY pref_category_experimental "Experimental features">
-
-<!-- Custom Tabs is an Android API for allowing third-party apps to open URLs in a customized UI.
- Instead of switching to the browser it appears as if the user stays in the third-party app.
- For more see: https://developer.chrome.com/multidevice/android/customtabs -->
-<!ENTITY pref_custom_tabs "Custom Tabs">
-<!ENTITY pref_custom_tabs_summary3 "Allow apps to open websites using a customized version of &brandShortName;">
-
-<!-- Localization note (pref_activity_stream): Experimental feature, see https://testpilot.firefox.com/experiments/activity-stream -->
-<!ENTITY pref_activity_stream "Activity Stream">
-<!ENTITY pref_activity_stream_summary "A rich visual history feed and a reimagined home page make it easier than ever to find exactly what you\'re looking for in &brandShortName;.">
-
-<!ENTITY tracking_protection_prompt_title "Now with Tracking Protection">
-<!ENTITY tracking_protection_prompt_text "Actively block tracking elements so you don\'t have to worry.">
-<!ENTITY tracking_protection_prompt_tip_text "Visit Privacy settings to learn more">
-<!ENTITY tracking_protection_prompt_action_button "Got it!">
-
-<!ENTITY tab_queue_toast_message3 "Tab saved in &brandShortName;">
-<!ENTITY tab_queue_toast_action "Open now">
-<!ENTITY tab_queue_prompt_title "Opening multiple links?">
-<!ENTITY tab_queue_prompt_text4 "Save them until the next time you open &brandShortName;.">
-<!ENTITY tab_queue_prompt_tip_text2 "You can change this later in Settings">
-<!-- Localization note (tab_queue_prompt_permit_drawing_over_apps): This additional text is shown if the
- user needs to enable an Android setting in order to enable tab queues. -->
-<!ENTITY tab_queue_prompt_permit_drawing_over_apps "Turn on Permit drawing over other apps">
-<!ENTITY tab_queue_prompt_positive_action_button "Enable">
-<!ENTITY tab_queue_prompt_negative_action_button "Not now">
-<!-- Localization note (tab_queue_prompt_settings_button): This button is shown if the user needs to
- enable a permission in Android's setting in order to enable tab queues. -->
-<!ENTITY tab_queue_prompt_settings_button "Go to Settings">
-<!ENTITY tab_queue_notification_title "&brandShortName;">
-<!-- Localization note (tab_queue_notification_text_plural2) : The
- formatD is replaced with the number of tabs queued. The
- number of tabs queued is always more than one. We can't use
- Android plural forms, sadly. See Bug #753859. -->
-<!ENTITY tab_queue_notification_text_plural2 "&formatD; tabs waiting">
-<!-- Localization note (tab_queue_notification_text_singular2) : This is the
- text of a notification; we expect only one tab queued. -->
-<!ENTITY tab_queue_notification_text_singular2 "1 tab waiting">
-
-<!-- Localization note (tab_queue_notification_settings): This notification text is shown if a tab
- has been queued but we are missing the system permission to show an overlay. -->
-<!ENTITY tab_queue_notification_settings "To \&quot;Open multiple links\&quot;, please enable the \'Draw over other apps\' permission for &brandShortName;">
-
-<!ENTITY content_notification_summary "&brandShortName;">
-<!-- Localization note (content_notification_title_plural): &formatD; will be replaced with the number of websites that
- have been updated (new content is available). The number of websites is always more than one (>= 2). For a single
- update the website title is used instead of this string.
- We can't use Android plural forms, sadly. See Bug #753859. -->
-<!ENTITY content_notification_title_plural "&formatD; websites updated">
-<!-- Localization note (content_notification_action_settings2): This label will be shown as an action in a content notification.
- Clicking the action will jump to the notification settings of the app. -->
-<!ENTITY content_notification_action_settings2 "Settings">
-<!-- Localization note(content_notification_action_read_now): This label will be shown as an action in a content notification.
- Clicking the action will open all new content in the browser. -->
-<!ENTITY content_notification_action_read_now "Read now">
-<!-- Localization note (content_notification_updated_on): &formatS; will be replaced with a medium sized version of the
- date, depending on locale. For en_US this is for example: Feb 24, 2016. For more details see the Android developer
- documentation for DateFormat.getMediumDateFormat(). -->
-<!ENTITY content_notification_updated_on "Updated on &formatS;">
-
-<!ENTITY pref_char_encoding "Character encoding">
-<!ENTITY pref_char_encoding_on "Show menu">
-<!ENTITY pref_char_encoding_off "Don\'t show menu">
-<!ENTITY pref_clear_private_data2 "Clear private data">
-<!-- Localization note (pref_clear_private_data_now_tablet): This action to clear private data is only shown on tablets.
- The action is shown below a header saying "Clear private data"; See pref_clear_private_data -->
-<!ENTITY pref_clear_private_data_now_tablet "Clear now">
-<!ENTITY pref_clear_on_exit_title3 "Clear private data on exit">
-<!ENTITY pref_clear_on_exit_summary2 "&brandShortName; will automatically clear your data whenever you select \u0022Quit\u0022 from the main menu">
-<!ENTITY pref_clear_on_exit_dialog_title "Select which data to clear">
-<!ENTITY pref_plugins "Plugins">
-<!ENTITY pref_plugins_enabled "Enabled">
-<!ENTITY pref_plugins_tap_to_play2 "Touch to play">
-<!ENTITY pref_plugins_disabled "Disabled">
-<!ENTITY pref_text_size "Text size">
-<!ENTITY pref_restore_tabs "Restore tabs">
-<!ENTITY pref_restore_always "Always restore">
-<!ENTITY pref_restore_quit "Don\'t restore after quitting &brandShortName;">
-<!ENTITY pref_font_size_tiny "Tiny">
-<!ENTITY pref_font_size_small "Small">
-<!ENTITY pref_font_size_medium "Medium">
-<!ENTITY pref_font_size_large "Large">
-<!ENTITY pref_font_size_xlarge "Extra Large">
-<!ENTITY pref_font_size_set "Set">
-<!-- Localization note (pref_font_size_adjust_char): A button with a small version of this character
-(or combination of characters) is used to decrease the preview font size; a larger version of the
-same character/combination is used to increase the preview font size. It should be a concise
-representation of the language it is used in that will help show the text in the preview will change
-size. -->
-<!ENTITY pref_font_size_adjust_char "A">
-
-<!-- Localization note (pref_font_size_preview_text): This paragraph is used as an example to
- demonstrate the font size setting. It is meant to be whimsical and fun. -->
-<!ENTITY pref_font_size_preview_text "The quick orange fox jumps over your expectations with more speed, more flexibility and more security. As a non-profit, we\'re free to innovate on your behalf without any pressure to compromise. That means a better experience for you and a brighter future for the Web.">
-
-<!ENTITY pref_media_autoplay_enabled "Allow autoplay">
-<!ENTITY pref_media_autoplay_enabled_summary "Control if websites can autoplay videos and other media content">
-<!ENTITY pref_zoom_force_enabled "Always enable zoom">
-<!ENTITY pref_zoom_force_enabled_summary "Force override so you can zoom any page">
-<!ENTITY pref_voice_input "Voice input">
-<!ENTITY pref_voice_input_summary2 "Allow voice dictation in the URL bar">
-<!ENTITY pref_qrcode_enabled "QR code reader">
-<!ENTITY pref_qrcode_enabled_summary2 "Allow QR scanner in the URL bar">
-
-<!ENTITY pref_use_master_password "Use master password">
-<!ENTITY pref_sync2 "Sign in">
-<!ENTITY pref_sync_summary2 "Sync your tabs, bookmarks, logins, history">
-<!ENTITY pref_search_suggestions "Show search suggestions">
-<!ENTITY pref_history_search_suggestions "Show search history">
-<!ENTITY pref_import_options "Import options">
-<!ENTITY pref_import_android_summary "Import bookmarks and history from the native browser">
-<!ENTITY pref_private_data_history2 "Browsing history">
-<!ENTITY pref_private_data_searchHistory "Search history">
-<!ENTITY pref_private_data_formdata2 "Form history">
-<!ENTITY pref_private_data_cookies2 "Cookies &amp; active logins">
-<!ENTITY pref_private_data_passwords2 "Saved logins">
-<!ENTITY pref_private_data_cache "Cache">
-<!ENTITY pref_private_data_offlineApps "Offline website data">
-<!ENTITY pref_private_data_siteSettings2 "Site settings">
-<!ENTITY pref_private_data_downloadFiles2 "Downloads">
-<!ENTITY pref_private_data_syncedTabs "Synced tabs">
-
-<!ENTITY pref_default_browser "Make default browser">
-<!ENTITY pref_default_browser_mozilla_support_tablet "Visit Mozilla Support">
-<!ENTITY pref_about_firefox "About &brandShortName;">
-<!ENTITY pref_vendor_faqs "FAQs">
-<!ENTITY pref_vendor_feedback "Give feedback">
-
-<!ENTITY pref_dialog_set_default "Set as default">
-<!ENTITY pref_dialog_default "Default">
-<!ENTITY pref_dialog_remove "Remove">
-
-<!ENTITY pref_search_last_toast "You can\'t remove or disable your last search engine.">
-
-<!ENTITY pref_panels_show "Show">
-<!ENTITY pref_panels_hide "Hide">
-<!ENTITY pref_panels_reorder "Change order">
-<!ENTITY pref_panels_move_up "Move up">
-<!ENTITY pref_panels_move_down "Move down">
-
-<!ENTITY datareporting_notification_title "&brandShortName; stats &amp; data">
-<!ENTITY datareporting_notification_action "Choose what to share">
-<!-- Used in datareporting_notification_ticket_text, but unused in strings.xml. -->
-<!ENTITY datareporting_notification_action_long "Choose what information to share">
-<!ENTITY datareporting_notification_summary "To improve your experience, &brandShortName; automatically sends some information to &vendorShortName;.">
-<!-- When this item is removed, also remove datareporting_notification_action_long:
- it is unused in strings.xml. -->
-<!ENTITY datareporting_notification_ticker_text "&datareporting_notification_title;: &datareporting_notification_action_long;">
-
-<!-- Localization note (datareporting_fhr_title, datareporting_fhr_summary2,
- reporting_telemetry_title, datareporting_telemetry_summary,
- datareporting_crashreporter_summary) : These match the strings in
- en-US/chrome/browser/preferences/advanced.dtd (healthReportSection.label,
- healthReportDesc.label, telemetrySection.label, telemetryDesc.label,
- crashReporterDesc.label). -->
-<!ENTITY datareporting_fhr_title "&brandShortName; Health Report">
-<!ENTITY datareporting_fhr_summary2 "Shares data with &vendorShortName; about your browser health and helps you understand your browser performance">
-<!ENTITY datareporting_abouthr_title "View my Health Report">
-<!ENTITY datareporting_telemetry_title "Telemetry">
-<!ENTITY datareporting_telemetry_summary "Shares performance, usage, hardware and customization data about your browser with &vendorShortName; to help us make &brandShortName; better">
-<!ENTITY datareporting_crashreporter_summary "&brandShortName; submits crash reports to help &vendorShortName; make your browser more stable and secure">
-<!-- Localization note (datareporting_crashreporter_title_short) : This string matches
- (crashReporterSection.label) in en-US/chrome/browser/preferences/advanced.dtd.-->
-<!ENTITY datareporting_crashreporter_title_short "Crash Reporter">
-<!ENTITY datareporting_wifi_title2 "&vendorShortName; Location Service">
-<!ENTITY datareporting_wifi_geolocation_summary4 "Help &vendorShortName; map the world! Share the approximate Wi-Fi and cellular location of your device to improve our geolocation service.">
-<!-- Localization note (pref_update_autodownload2) : This should mention downloading
- specifically, since the pref only prevents automatic downloads and not the
- actual notification that an update is available. -->
-<!ENTITY pref_update_autodownload3 "Automatic updates">
-<!ENTITY pref_update_autodownload_wifi "Only over Wi-Fi">
-<!ENTITY pref_update_autodownload_never "Never">
-<!ENTITY pref_update_autodownload_always "Always">
-
-<!-- Localization note (help_menu) : This string is used in the main menu-->
-<!ENTITY help_menu "Help">
-
-<!ENTITY quit "Quit">
-
-<!ENTITY addons "Add-ons">
-<!ENTITY logins "Logins">
-<!ENTITY downloads "Downloads">
-<!ENTITY char_encoding "Character Encoding">
-
-<!ENTITY share "Share">
-<!ENTITY share_title "Share via">
-<!ENTITY share_image_failed "Unable to share this image">
-<!ENTITY save_as_pdf "Save as PDF">
-<!ENTITY print "Print">
-<!ENTITY find_in_page "Find in page">
-<!ENTITY desktop_mode "Request desktop site">
-<!ENTITY page "Page">
-<!ENTITY tools "Tools">
-<!ENTITY new_tab "New tab">
-<!ENTITY new_private_tab "New private tab">
-<!ENTITY close_all_tabs "Close All Tabs">
-<!ENTITY close_private_tabs "Close Private Tabs">
-<!ENTITY tabs_normal "Tabs">
-<!ENTITY tabs_private "Private">
-<!ENTITY set_image_fail "Unable to set image">
-<!ENTITY set_image_path_fail "Unable to save image">
-<!ENTITY set_image_chooser_title "Set Image As">
-
-<!-- Localization note (find_text, find_prev, find_next, find_close) : These strings are used
- as alternate text for accessibility. They are not visible in the UI. -->
-<!ENTITY find_text "Find in Page">
-<!ENTITY find_prev "Previous">
-<!ENTITY find_next "Next">
-<!ENTITY find_close "Close">
-
-<!-- Localization note (media_sending_to, media_play, media_pause, media_stop) : These strings are used
- as alternate text for accessibility. They are not visible in the UI. -->
-<!ENTITY media_sending_to "Sending to Device">
-<!ENTITY media_play "Play">
-<!ENTITY media_pause "Pause">
-<!ENTITY media_stop "Stop">
-
-<!ENTITY contextmenu_open_new_tab "Open in New Tab">
-<!ENTITY contextmenu_open_private_tab "Open in Private Tab">
-<!ENTITY contextmenu_remove "Remove">
-<!ENTITY contextmenu_add_to_launcher "Add to Home Screen">
-<!ENTITY contextmenu_share "Share">
-<!ENTITY contextmenu_pasteandgo "Paste &amp; Go">
-<!ENTITY contextmenu_paste "Paste">
-<!ENTITY contextmenu_copyurl "Copy Address">
-<!ENTITY contextmenu_edit_bookmark "Edit">
-<!ENTITY contextmenu_subscribe "Subscribe to Page">
-<!ENTITY contextmenu_site_settings "Edit Site Settings">
-<!ENTITY contextmenu_top_sites_edit "Edit">
-<!ENTITY contextmenu_top_sites_pin "Pin Site">
-<!ENTITY contextmenu_top_sites_unpin "Unpin Site">
-<!ENTITY contextmenu_add_search_engine "Add a Search Engine">
-
-<!-- Localization note (doorhanger_login_no_username): This string is used in the save-login doorhanger
- where normally a username would be displayed. In this case, no username was found, and this placeholder
- contains brackets to indicate this is not actually a username, but rather a placeholder -->
-<!ENTITY doorhanger_login_no_username "[No username]">
-<!ENTITY doorhanger_login_edit_title "Edit login">
-<!ENTITY doorhanger_login_edit_username_hint "Username">
-<!ENTITY doorhanger_login_edit_password_hint "Password">
-<!ENTITY doorhanger_login_edit_toggle "Show password">
-<!ENTITY doorhanger_login_edit_toast_error "Failed to save login">
-<!ENTITY doorhanger_login_select_message "Copy password from &formatS;?">
-<!ENTITY doorhanger_login_select_toast_copy "Password copied to clipboard">
-<!ENTITY doorhanger_login_select_toast_copy_error "Couldn\'t copy password">
-<!ENTITY doorhanger_login_select_action_text "Select another login">
-<!ENTITY doorhanger_login_select_title "Copy password from">
-
-<!-- Localization note (pref_prevent_magnifying_glass): Label for setting that controls
- whether or not the magnifying glass is disabled. -->
-<!ENTITY pref_magnifying_glass_enabled "Magnify small areas">
-<!ENTITY pref_magnifying_glass_enabled_summary2 "Enlarge links and form fields when touching near them">
-
-<!-- Localization note (pref_scroll_title_bar2): Label for setting that controls
- whether or not the dynamic toolbar is enabled. -->
-<!ENTITY pref_scroll_title_bar2 "Full-screen browsing">
-<!ENTITY pref_scroll_title_bar_summary2 "Hide the &brandShortName; toolbar when scrolling down a page">
-
-<!ENTITY pref_tab_queue_title3 "Tab queue">
-<!ENTITY pref_tab_queue_summary4 "Save links until the next time you open &brandShortName;">
-
-<!-- Localization note (page_removed): This string appears in a toast message when
- any page is removed frome about:home. This includes pages that are in history,
- bookmarks, or reading list. -->
-<!ENTITY page_removed "Page removed">
-
-<!ENTITY bookmark_edit_title "Edit Bookmark">
-<!ENTITY bookmark_edit_name "Name">
-<!ENTITY bookmark_edit_location "Location">
-<!ENTITY bookmark_edit_keyword "Keyword">
-
-<!-- Localization note (site_settings_*) : These strings are used in the "Site Settings"
- dialog that appears after selecting the "Edit Site Settings" context menu item. -->
-<!ENTITY site_settings_title3 "Site Settings">
-<!ENTITY site_settings_cancel "Cancel">
-<!ENTITY site_settings_clear "Clear">
-
-<!-- Localization note : These strings are used as alternate text for accessibility.
- They are not visible in the UI. -->
-<!ENTITY page_action_dropmarker_description "Additional Actions">
-
-<!ENTITY masterpassword_create_title "Create Master Password">
-<!ENTITY masterpassword_remove_title "Remove Master Password">
-<!ENTITY masterpassword_password "Password">
-<!ENTITY masterpassword_confirm "Confirm password">
-
-<!ENTITY button_ok "OK">
-<!ENTITY button_cancel "Cancel">
-<!ENTITY button_yes "Yes">
-<!ENTITY button_no "No">
-<!ENTITY button_clear_data "Clear data">
-<!ENTITY button_set "Set">
-<!ENTITY button_clear "Clear">
-<!ENTITY button_copy "Copy">
-
-<!ENTITY home_top_sites_title "Top Sites">
-<!-- Localization note (home_top_sites_add): This string is used as placeholder
- text underneath empty thumbnails in the Top Sites page on about:home. -->
-<!ENTITY home_top_sites_add "Add a site">
-
-<!-- Localization note (home_title): This string should be kept in sync
- with the page title defined in aboutHome.dtd -->
-<!ENTITY home_title "&brandShortName; Home">
-<!ENTITY home_history_title "History">
-<!ENTITY home_synced_devices_smartfolder "Synced devices">
-<!ENTITY home_synced_devices_number "&formatD; devices">
-<!-- Localization note (home_synced_devices_one_device): This is the singular version of home_synced_devices_number, referring to the number of devices a user has synced. -->
-<!ENTITY home_synced_devices_one "1 device">
-<!ENTITY home_history_back_to2 "Back to full History">
-<!ENTITY home_clear_history_button "Clear browsing history">
-<!ENTITY home_clear_history_confirm "Are you sure you want to clear your history?">
-<!ENTITY home_bookmarks_empty "Bookmarks you save show up here.">
-<!ENTITY home_closed_tabs_title2 "Recently closed">
-<!ENTITY home_last_tabs_empty "Your recent tabs show up here.">
-<!ENTITY home_restore_all "Restore all">
-<!ENTITY home_closed_tabs_number "&formatD; tabs">
-<!-- Localization note (home_closed_tabs_one): This is the singular version of home_closed_tabs_number, referring to the number of recently closed tabs available. -->
-<!ENTITY home_closed_tabs_one "1 tab">
-<!ENTITY home_most_recent_empty "Websites you visited most recently show up here.">
-<!-- Localization note (home_most_recent_emptyhint2): "Psst" is a sound that might be used to attract someone's attention unobtrusively, and intended to hint at Private Browsing to the user.
- The placeholders &formatS1; and &formatS2; are used to mark the location of text underlining. -->
-<!ENTITY home_most_recent_emptyhint2 "Psst: using a &formatS1;New Private Tab&formatS2; won\'t save your history.">
-
-<!-- Localization note (home_default_empty): This string is used as the default text when there
- is no data to show in an about:home panel that was created by an add-on. -->
-<!ENTITY home_default_empty "No content could be found for this panel.">
-
-<!-- Localization note (home_back_up_to_filter): The variable is replaced by the name of the
- previous location in the navigation, such as the previous folder -->
-<!ENTITY home_move_back_to_filter "Back to &formatS;">
-
-<!-- Localization note (home_remote_tabs_many_hidden_devices) : The
- formatD is replaced with the number of hidden devices. The
- number of hidden devices is always more than one. We can't use
- Android plural forms, sadly. See Bug #753859. -->
-<!ENTITY home_remote_tabs_many_hidden_devices "&formatD; devices hidden">
-<!-- Localization note (home_remote_tabs_hidden_devices_title) : This is the
- title of a dialog; we expect more than one device. -->
-<!ENTITY home_remote_tabs_hidden_devices_title "Hidden devices">
-<!-- Localization note (home_remote_tabs_unhide_selected_devices) : This is
- the text of a button; we expect more than one device. -->
-<!ENTITY home_remote_tabs_unhide_selected_devices "Unhide selected devices">
-
-<!ENTITY remote_tabs_panel_moved_title "Where did my tabs go?">
-<!ENTITY remote_tabs_panel_moved_desc "We\'ve moved your tabs from other devices into a panel on your home page that can be easily accessed every time you open a new tab.">
-<!ENTITY remote_tabs_panel_moved_link "Take me to my new panel.">
-
-<!ENTITY pin_site_dialog_hint "Enter a search keyword">
-
-<!ENTITY filepicker_title "Choose File">
-<!ENTITY filepicker_audio_title "Choose or record a sound">
-<!ENTITY filepicker_image_title "Choose or take a picture">
-<!ENTITY filepicker_video_title "Choose or record a video">
-
-<!-- Site identity popup -->
-<!ENTITY identity_connected_to "You are connected to">
-<!-- Localization note (identity_run_by) : This string appears between a
-domain name (above) and an organization name (below). E.g.
-
-example.com
-which is run by
-Example Enterprises, Inc.
-
-The layout of the identity dialog prevents combining this into a single string with
-substitution variables. If it is difficult to translate the sense of the string
-with that structure, consider a translation which ignores the preceding domain and
-just addresses the organization to follow, e.g. "This site is run by " -->
-<!ENTITY identity_connection_secure "Secure Connection">
-<!ENTITY identity_connection_insecure "Insecure connection">
-<!ENTITY identity_connection_chromeui "This is a secure &brandShortName; page">
-
-<!-- Mixed content notifications in site identity popup -->
-<!ENTITY mixed_content_blocked_all1 "&brandShortName; has blocked insecure content on this page.">
-<!ENTITY mixed_content_blocked_some1 "&brandShortName; has blocked some of the insecure content on this page.">
-<!ENTITY mixed_content_display_loaded1 "Parts of this page are not secure (such as images).">
-<!ENTITY mixed_content_protection_disabled1 "You have disabled protection from insecure content.">
-
-<!-- Tracking content notifications in site identity popup -->
-<!ENTITY doorhanger_tracking_title2 "Tracking Protection">
-<!ENTITY doorhanger_tracking_state_enabled "Enabled">
-<!ENTITY doorhanger_tracking_state_disabled "Disabled">
-<!ENTITY doorhanger_tracking_message_enabled1 "Attempts to track your online behavior have been blocked.">
-<!ENTITY doorhanger_tracking_message_disabled2 "This page includes elements that may track your browsing.">
-
-<!-- Common mixed and tracking content strings in site identity popup -->
-<!ENTITY learn_more "Learn More">
-<!ENTITY enable_protection "Enable protection">
-<!ENTITY disable_protection "Disable protection">
-
-<!ENTITY private_data_success "Private data cleared">
-<!ENTITY private_data_fail "Some private data could not be cleared">
-
-<!ENTITY bookmarkhistory_button_import "Import">
-<!ENTITY bookmarkhistory_import_both "Importing bookmarks and history
- from Android">
-<!ENTITY bookmarkhistory_import_bookmarks "Importing bookmarks
- from Android">
-<!ENTITY bookmarkhistory_import_history "Importing history
- from Android">
-<!ENTITY bookmarkhistory_import_wait "Please wait...">
-
-<!ENTITY suggestions_prompt3 "Would you like to turn on search suggestions?">
-<!-- Localization note (search_bar_item_desc): When the user clicks the url bar
- and starts typing, a list of icons of search engines appears at the bottom
- of the screen. When a user clicks an icon, the entered text will be searched
- via the search engine that uses the icon they clicked. This text is used
- for screen reader users when they hover each icon - &formatS; will be
- replaced with the name of the currently highlighted icon. -->
-<!ENTITY search_bar_item_desc "Search with &formatS;">
-
-<!-- Localization note (suggestion_for_engine): The placeholder &formatS1; will be
- replaced with the name of the search engine. The placeholder &formatS2; will be
- replaced with the search query. -->
-<!ENTITY suggestion_for_engine "Search &formatS1; for &formatS2;">
-
-<!ENTITY searchable_description "Bookmarks and history">
-
- <!-- Updater notifications -->
-<!ENTITY updater_start_title2 "Update available for &brandShortName;">
-<!ENTITY updater_start_select2 "Touch to download">
-
-<!ENTITY updater_downloading_title2 "Downloading &brandShortName;">
-<!ENTITY updater_downloading_title_failed2 "Download failed">
-<!ENTITY updater_downloading_select2 "Touch to apply update once downloaded">
-<!ENTITY updater_downloading_retry2 "Touch to retry">
-
-<!ENTITY updater_apply_title2 "Update available for &brandShortName;">
-<!ENTITY updater_apply_select2 "Touch to update">
-
-<!-- Localization note (updater_permission_text): This text is shown in a notification and as a snackbar
- if the app requires a runtime permission to download updates. Currently, the updater only sees
- remotely advertised updates in the Nightly and Aurora channels. -->
-<!ENTITY updater_permission_text "To download files and updates, allow &brandShortName; permission to access storage.">
-<!-- LOCALIZATION NOTE (updater_permission_allow): This action is shown in a snackbar along with updater_permission_text. -->
-<!ENTITY updater_permission_allow "Allow">
-
- <!-- Guest mode -->
-<!ENTITY new_guest_session "New Guest Session">
-<!ENTITY exit_guest_session "Exit Guest Session">
-<!ENTITY guest_session_dialog_continue "Continue">
-<!ENTITY guest_session_dialog_cancel "Cancel">
-<!ENTITY new_guest_session_title "&brandShortName; will now restart">
-<!ENTITY new_guest_session_text2 "The person using it will not be able to see any of your personal browsing data (like saved logins, history or bookmarks).\n\nWhen your guest is done, their browsing data will be deleted and your session will be restored.">
-<!ENTITY guest_browsing_notification_title "Guest browsing is enabled">
-<!ENTITY guest_browsing_notification_text "Tap to exit">
-
-<!ENTITY exit_guest_session_title "&brandShortName; will now restart">
-<!ENTITY exit_guest_session_text "The browsing data from this session will be deleted.">
-
-<!-- Miscellaneous -->
-<!-- LOCALIZATION NOTE (ellipsis): This text is appended to a piece of text that does not fit in the
- designated space. Use the unicode ellipsis char, \u2026, or use "..." if \u2026 doesn't suit
- traditions in your locale. -->
-<!ENTITY ellipsis "…">
-
-<!ENTITY colon ":">
-
-<!-- LOCALIZATION NOTE (percent): The percent sign is appended after a number to
- display a percentage value. formatS is the number, #37 is the code to display a percent sign.
- This format string is typically used by getString method, in such method the percent sign
- is a reserved caracter. In order to display one percent sign in the result of getString,
- double percent signs must be inserted in the format string.
- This entity is used in the zoomed view to display the zoom factor-->
-<!ENTITY percent "&formatS;&#37;&#37;">
-
-<!-- These are only used for accessibility for the done and overflow-menu buttons in the actionbar.
- They are never shown to users -->
-<!ENTITY actionbar_menu "Menu">
-<!ENTITY actionbar_done "Done">
-
-<!-- Voice search in the awesome bar -->
-<!ENTITY voicesearch_prompt "Speak now">
-
-<!-- Localization note (remote_tabs_last_synced): the variable is replaced by a
- "relative time span string" produced by Android. This string describes the
- time the tabs were last synced relative to the current time; examples
- include "42 minutes ago", "4 days ago", "last week", etc. The subject of
- "Last synced" is one of the user's other Sync clients, typically Firefox on
- their desktop or laptop.-->
-<!ENTITY remote_tabs_last_synced "Last synced: &formatS;">
-<!-- Localization note: Used when the sync has not happend yet, showed in place of a date -->
-<!ENTITY remote_tabs_never_synced "Last synced: never">
-
-<!-- LOCALIZATION NOTE (intent_uri_private_browsing_prompt): This string will
- appear in an alert when a user, who is currently in private browsing,
- clicks a link that will open an external Android application. "&formatS;"
- will be replaced with the name of the application that will be opened. -->
-<!ENTITY intent_uri_private_browsing_prompt "This link will open in &formatS;. Are you sure you want to exit Private Browsing?">
-<!-- LOCALIZATION NOTE (intent_uri_private_browsing_multiple_match_title): This
- string will appear as the title of an alert when a user, who is currently
- in private browsing, clicks a link that will open an external Android
- application and more than one application is available to open that link.
- We don't have control over the style of this dialog and it looks
- unpolished when this string is longer than one line so ideally keep it
- short! -->
-<!ENTITY intent_uri_private_browsing_multiple_match_title "Exit Private Browsing?">
-
-<!-- DevTools Authentication -->
-<!-- LOCALIZATION NOTE (devtools_auth_scan_header): This header text appears
- above a QR reader that is actively scanning for QR codes. The expected QR
- code has already been displayed by the client trying to connect (such as
- desktop Firefox via WebIDE), so you just need to aim this device at the QR
- code. -->
-<!ENTITY devtools_auth_scan_header "Scanning for the QR code displayed on your other device">
-
-<!-- Restrictable features -->
-<!-- Localization note: These are features the device owner (e.g. parent) can enable or disable for
- a restricted profile (e.g. child). Used inside the Android settings UI. -->
-<!ENTITY restrictable_feature_addons_installation "Add-ons">
-<!ENTITY restrictable_feature_addons_installation_description "Add features or functionality to Firefox. Note: Add-ons can disable certain restrictions.">
-<!ENTITY restrictable_feature_private_browsing "Private Browsing">
-<!ENTITY restrictable_feature_private_browsing_description "Allows family members to browse without saving information about the sites and pages they\'ve visited.">
-<!ENTITY restrictable_feature_clear_history "Clear History">
-<!ENTITY restrictable_feature_clear_history_description "Allows family members to delete information about the sites and pages they\'ve visited.">
-<!ENTITY restrictable_feature_advanced_settings "Advanced Settings">
-<!ENTITY restrictable_feature_advanced_settings_description "This includes importing bookmarks, restoring tabs and automated updates. Turn off for simplified settings suitable for any family member.">
-<!ENTITY restrictable_feature_camera_microphone "Camera &amp; Microphone">
-<!ENTITY restrictable_feature_camera_microphone_description "Allows family members to engage in real time communication on websites.">
-<!ENTITY restrictable_feature_block_list "Block List">
-<!ENTITY restrictable_feature_block_list_description "Block websites that include sensitive content.">
-
-<!-- Default Bookmarks titles-->
-<!-- LOCALIZATION NOTE (bookmarks_about_browser): link title for about:fennec -->
-<!ENTITY bookmarks_about_browser "Firefox: About your browser">
-<!-- LOCALIZATION NOTE (bookmarks_addons): link title for https://addons.mozilla.org/en-US/mobile -->
-<!ENTITY bookmarks_addons "Firefox: Customize with add-ons">
-<!-- LOCALIZATION NOTE (bookmarks_support): link title for https://support.mozilla.org/ -->
-<!ENTITY bookmarks_support "Firefox: Support">
-<!-- LOCALIZATION NOTE (bookmarks_restricted_support): link title for https://support.mozilla.org/kb/controlledaccess -->
-<!ENTITY bookmarks_restricted_support2 "Firefox Help and Support for restricted profiles on Android tablets">
-<!-- LOCALIZATION NOTE (bookmarks_restricted_webmaker):link title for https://webmaker.org -->
-<!ENTITY bookmarks_restricted_webmaker "Learn the Web: Mozilla Webmaker">
-
-<!-- LOCALIZATION NOTE (unsupported_sdk_version): The user installed a build of this app that does not support
- the Android version of this device. the formatS1 is replaced by the CPU ABI (e.g., ARMv7); the formatS2 is
- replaced by the Android OS version (e.g., 14)-->
-<!ENTITY unsupported_sdk_version "Sorry! This &brandShortName; won\'t work on this device (&formatS1;, &formatS2;). Please download the correct version.">
-
-<!ENTITY eol_notification_title2 "&brandShortName; will no longer update">
-<!ENTITY eol_notification_summary "Tap to learn more">
-
-<!-- LOCALIZATION NOTE (whatsnew_notification_title, whatsnew_notification_summary): These strings
- are used for a system notification that's shown to users after the app updates. -->
-<!ENTITY whatsnew_notification_title "&brandShortName; is up to date">
-<!ENTITY whatsnew_notification_summary "Find out what\'s new in this version">
-
-<!ENTITY promotion_add_to_homescreen "Add to home screen">
-
-<!ENTITY helper_first_offline_bookmark_title "Read offline">
-<!ENTITY helper_first_offline_bookmark_message "Find your Reader View items in Bookmarks, even offline.">
-<!ENTITY helper_first_offline_bookmark_button "Go to Bookmarks">
-
-<!ENTITY helper_triple_readerview_open_title "Available offline">
-<!ENTITY helper_triple_readerview_open_message "Bookmark Reader View items to read them offline.">
-<!ENTITY helper_triple_readerview_open_button "Add to Bookmarks">
-
-<!ENTITY activity_stream_topsites "Top Sites">
-<!ENTITY activity_stream_highlights "Highlights">
-
-<!-- LOCALIZATION NOTE (activity_stream_highlight_label_bookmarked): This label is shown in the Activity
-Stream list for highlights sourced from th user's bookmarks. -->
-<!ENTITY activity_stream_highlight_label_bookmarked "Bookmarked">
-<!-- LOCALIZATION NOTE (activity_stream_highlight_label_visited): This label is shown in the Activity
-Stream list for highlights sourced from th user's bookmarks. -->
-<!ENTITY activity_stream_highlight_label_visited "Visited">
-
-<!-- LOCALIZATION NOTE (activity_stream_dismiss): This label is shown in the Activity Stream context menu,
-and allows hiding a URL/page from highlights or topsites. The page remains in history/bookmarks, but
-is simply hidden from the Activity Stream panel. -->
-<!ENTITY activity_stream_dismiss "Dismiss">
-<!ENTITY activity_stream_delete_history "Delete from History">
diff --git a/mobile/android/base/locales/en-US/search_strings.dtd b/mobile/android/base/locales/en-US/search_strings.dtd
deleted file mode 100644
index fe8180cff..000000000
--- a/mobile/android/base/locales/en-US/search_strings.dtd
+++ /dev/null
@@ -1,28 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!ENTITY search_app_name '&brandShortName; Search'>
-
-<!-- Localization note (search_bar_hint): The &formatS; will be replaced with the name of
- the currently selected search engine. -->
-<!ENTITY search_bar_hint 'Search with &formatS;'>
-
-<!ENTITY search_empty_title2 'Start searching'>
-<!ENTITY search_empty_message 'Quickly search for anything you want'>
-
-<!-- Localization note (search_plus_content_description): This is the content description
- for the "+" icon that appears at the end of search suggestions. -->
-<!ENTITY search_plus_content_description 'Add to search bar'>
-
-<!ENTITY search_pref_title 'Settings'>
-<!ENTITY search_pref_button_content_description 'Settings'>
-
-<!ENTITY pref_clearHistory_confirmation 'History cleared'>
-<!ENTITY pref_clearHistory_dialogMessage 'Delete all search history from this device?'>
-<!ENTITY pref_clearHistory_title 'Clear search history'>
-
-<!ENTITY search_widget_button_label 'Search'>
-
-<!ENTITY network_error_title 'No internet connection'>
-<!ENTITY network_error_message 'Tap here to check your network settings'>
diff --git a/mobile/android/base/locales/en-US/sync_strings.dtd b/mobile/android/base/locales/en-US/sync_strings.dtd
deleted file mode 100644
index 117600cac..000000000
--- a/mobile/android/base/locales/en-US/sync_strings.dtd
+++ /dev/null
@@ -1,126 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- Don't localize these. They're here until they have
- a better place to live. -->
-<!ENTITY syncBrand.fullName.label "Firefox Sync">
-<!ENTITY syncBrand.shortName.label "Sync">
-
-<!-- Main titles. -->
-<!ENTITY sync.title.connect.label 'Connect to &syncBrand.shortName.label;'>
-
-<!-- J-PAKE Key Screen -->
-<!ENTITY sync.subtitle.connect.label 'To activate your new device, select “Set up &syncBrand.shortName.label;” on the device.'>
-<!ENTITY sync.subtitle.pair.label 'To activate, select “Pair a device” on your other device.'>
-<!ENTITY sync.pin.default.label '...\n...\n...\n'>
-<!ENTITY sync.link.nodevice.label 'I don\&apos;t have the device with me…'>
-
-<!-- Configure Engines -->
-<!ENTITY sync.configure.engines.title.passwords2 'Logins'>
-<!ENTITY sync.configure.engines.title.history 'History'>
-<!ENTITY sync.configure.engines.title.tabs 'Tabs'>
-
-<!-- Localization note (sync.default.client.name): Default string of the "Device
- name" menu item upon setting up Firefox Sync. The placeholder &formatS1
- will be replaced by the name of the Firefox release channel and &formatS2
- by the model name of the Android device. Examples look like "Aurora on
- GT-I1950" and "Fennec on MI 2S". -->
-<!ENTITY sync.default.client.name '&formatS1; on &formatS2;'>
-
-<!-- Bookmark folder strings -->
-<!ENTITY bookmarks.folder.menu.label 'Bookmarks Menu'>
-<!ENTITY bookmarks.folder.places.label ''>
-<!ENTITY bookmarks.folder.tags.label 'Tags'>
-<!ENTITY bookmarks.folder.toolbar.label 'Bookmarks Toolbar'>
-<!ENTITY bookmarks.folder.other.label 'Other Bookmarks'>
-<!ENTITY bookmarks.folder.desktop.label 'Desktop Bookmarks'>
-<!ENTITY bookmarks.folder.mobile.label 'Mobile Bookmarks'>
-<!-- Pinned sites on about:home. This folder should never be shown to the user, but we have to give it a string name -->
-<!ENTITY bookmarks.folder.pinned.label 'Pinned'>
-
-<!-- Firefox Account strings. -->
-
-<!-- Localization note: these are shown in screens after the user has
- created or signed in to an account, and take the user back to
- Firefox. -->
-<!ENTITY fxaccount_back_to_browsing 'Back to browsing'>
-
-<!ENTITY fxaccount_getting_started_welcome_to_sync 'Welcome to &syncBrand.shortName.label;'>
-<!ENTITY fxaccount_getting_started_description2 'Sign in to sync your tabs, bookmarks, logins &amp; more.'>
-<!ENTITY fxaccount_getting_started_get_started 'Get started'>
-<!ENTITY fxaccount_getting_started_old_firefox 'Using an older version of &syncBrand.shortName.label;?'>
-
-<!ENTITY fxaccount_status_signed_in_as 'Signed in as'>
-<!ENTITY fxaccount_status_manage_account 'Manage account'>
-<!ENTITY fxaccount_status_auth_server 'Account server'>
-<!ENTITY fxaccount_status_sync_now 'Sync now'>
-<!ENTITY fxaccount_status_syncing2 'Syncing…'>
-<!ENTITY fxaccount_status_device_name 'Device name'>
-<!ENTITY fxaccount_status_sync_server 'Sync server'>
-<!ENTITY fxaccount_status_sync '&syncBrand.shortName.label;'>
-<!ENTITY fxaccount_status_sync_enabled '&syncBrand.shortName.label;: enabled'>
-<!ENTITY fxaccount_status_needs_verification2 'Your account needs to be verified. Tap to resend verification email.'>
-<!ENTITY fxaccount_status_needs_credentials 'Cannot connect. Tap to sign in.'>
-<!ENTITY fxaccount_status_needs_upgrade 'You need to upgrade &brandShortName; to sign in.'>
-<!ENTITY fxaccount_status_needs_master_sync_automatically_enabled '&syncBrand.shortName.label; is set up, but not syncing automatically. Toggle “Auto-sync data” in Android Settings &gt; Data Usage.'>
-<!ENTITY fxaccount_status_needs_master_sync_automatically_enabled_v21 '&syncBrand.shortName.label; is set up, but not syncing automatically. Toggle “Auto-sync data” in the menu of Android Settings &gt; Accounts.'>
-<!ENTITY fxaccount_status_needs_finish_migrating 'Tap to sign in to your new Firefox Account.'>
-<!ENTITY fxaccount_status_bookmarks 'Bookmarks'>
-<!ENTITY fxaccount_status_history 'History'>
-<!ENTITY fxaccount_status_passwords2 'Logins'>
-<!ENTITY fxaccount_status_tabs 'Open tabs'>
-<!ENTITY fxaccount_status_legal 'Legal' >
-<!-- Localization note: when tapped, the following two strings link to
- external web pages. Compare fxaccount_policy_{linktos,linkprivacy}:
- these strings are separated to accommodate languages that decline
- the two uses differently. -->
-<!ENTITY fxaccount_status_linktos2 'Terms of service'>
-<!ENTITY fxaccount_status_linkprivacy2 'Privacy notice'>
-<!ENTITY fxaccount_status_more 'More&ellipsis;'>
-<!ENTITY fxaccount_remove_account 'Disconnect&ellipsis;'>
-
-<!ENTITY fxaccount_remove_account_dialog_title 'Remove Firefox Account?'>
-<!ENTITY fxaccount_remove_account_dialog_message '&brandShortName; will stop syncing with your account, but won’t delete any of your browsing data on this device.'>
-<!-- Localization note: format string below will be replaced
- with the Firefox Account's email address. -->
-<!ENTITY fxaccount_remove_account_toast 'Firefox Account &formatS; removed.'>
-
-<!ENTITY fxaccount_enable_debug_mode 'Enable Debug Mode'>
-
-<!-- Localization note: this is the name shown by the Android system
- itself for a Firefox Account. Don't localize this. -->
-<!ENTITY fxaccount_account_type_label 'Firefox'>
-
-<!-- Localization note: these are shown by the Android system itself,
- when the user navigates to the Android > Accounts > {Firefox
- Account} Screen. The link takes the user to the Firefox Account
- status activity, which lets them manage their Firefox
- Account. -->
-<!ENTITY fxaccount_options_title '&syncBrand.shortName.label; Options'>
-<!ENTITY fxaccount_options_configure_title 'Configure &syncBrand.shortName.label;'>
-
-<!-- Localization note: these error messages are shown after a request
- has been made to the remote server, and an error of some type has
- been returned. -->
-<!ENTITY fxaccount_remote_error_UPGRADE_REQUIRED 'You need to upgrade Firefox'>
-
-<!-- Localization note: the format string will be fxaccount_sign_in_button_label, linkified. -->
-<!ENTITY fxaccount_remote_error_ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS_2 'Account already exists. &formatS1;'>
-<!ENTITY fxaccount_remote_error_ATTEMPT_TO_ACCESS_AN_ACCOUNT_THAT_DOES_NOT_EXIST 'Invalid email or password'>
-<!ENTITY fxaccount_remote_error_INCORRECT_PASSWORD 'Invalid email or password'>
-<!ENTITY fxaccount_remote_error_ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT 'Account is not verified'>
-<!ENTITY fxaccount_remote_error_CLIENT_HAS_SENT_TOO_MANY_REQUESTS 'Server busy, try again soon'>
-<!ENTITY fxaccount_remote_error_SERVICE_TEMPORARILY_UNAVAILABLE_TO_DUE_HIGH_LOAD 'Server busy, try again soon'>
-<!ENTITY fxaccount_remote_error_UNKNOWN_ERROR 'There was a problem'>
-<!ENTITY fxaccount_remote_error_ACCOUNT_LOCKED 'Account is locked. &formatS1;'>
-
-<!ENTITY fxaccount_sync_sign_in_error_notification_title2 '&syncBrand.shortName.label; is not connected'>
-<!-- Localization note: the format string below will be replaced
- with the Firefox Account's email address. -->
-<!ENTITY fxaccount_sync_sign_in_error_notification_text2 'Tap to sign in as &formatS;'>
-
-<!ENTITY fxaccount_sync_finish_migrating_notification_title 'Finish upgrading &syncBrand.shortName.label;?'>
-<!-- Localization note: the format string below will be replaced
- with the Firefox Account's email address. -->
-<!ENTITY fxaccount_sync_finish_migrating_notification_text 'Tap to sign in as &formatS;'>
diff --git a/mobile/android/base/locales/moz.build b/mobile/android/base/locales/moz.build
deleted file mode 100644
index 079d4d640..000000000
--- a/mobile/android/base/locales/moz.build
+++ /dev/null
@@ -1,8 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-if CONFIG['MOZ_ANDROID_SEARCH_ACTIVITY']:
- DEFINES['MOZ_ANDROID_SEARCH_ACTIVITY'] = 1
diff --git a/mobile/android/base/moz.build b/mobile/android/base/moz.build
deleted file mode 100644
index eac831421..000000000
--- a/mobile/android/base/moz.build
+++ /dev/null
@@ -1,1143 +0,0 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-DIRS += ['locales']
-
-CONFIGURE_SUBST_FILES += ['adjust_sdk_app_token']
-
-include('android-services.mozbuild')
-
-geckoview_source_dir = TOPSRCDIR + '/mobile/android/geckoview/src/main/'
-geckoview_thirdparty_source_dir = TOPSRCDIR + '/mobile/android/geckoview/src/thirdparty/'
-thirdparty_source_dir = TOPSRCDIR + '/mobile/android/thirdparty/'
-
-constants_jar = add_java_jar('constants')
-constants_jar.sources += [geckoview_source_dir + 'java/org/mozilla/gecko/' + x for x in [
- 'annotation/JNITarget.java',
- 'annotation/ReflectionTarget.java',
- 'annotation/RobocopTarget.java',
- 'annotation/WebRTCJNITarget.java',
- 'annotation/WrapForJNI.java',
- 'SysInfo.java',
-]]
-constants_jar.sources += ['java/org/mozilla/gecko/' + x for x in [
- 'adjust/AdjustHelperInterface.java',
- 'adjust/AttributionHelperListener.java',
- 'db/BrowserContract.java',
- 'LocaleManager.java',
- 'Locales.java',
-]]
-constants_jar.generated_sources = [
- 'preprocessed/org/mozilla/gecko/AdjustConstants.java',
- 'preprocessed/org/mozilla/gecko/AppConstants.java',
-]
-constants_jar.extra_jars = [
- CONFIG['ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB'],
- CONFIG['ANDROID_SUPPORT_V4_AAR_LIB'],
- CONFIG['ANDROID_SUPPORT_V4_AAR_INTERNAL_LIB'],
- CONFIG['ANDROID_APPCOMPAT_V7_AAR_LIB'],
-]
-
-if CONFIG['MOZ_INSTALL_TRACKING']:
- constants_jar.sources += ['java/org/mozilla/gecko/' + x for x in [
- 'adjust/AdjustHelper.java',
- ]]
- constants_jar.extra_jars += [
- 'gecko-thirdparty-adjust_sdk.jar',
- ]
-else:
- constants_jar.sources += ['java/org/mozilla/gecko/' + x for x in [
- 'adjust/StubAdjustHelper.java',
- ]]
-
-resjar = add_java_jar('gecko-R')
-resjar.sources = []
-resjar.generated_sources += [
- 'org/mozilla/gecko/R.java',
-]
-
-if CONFIG['ANDROID_SUPPORT_V4_AAR']:
- ANDROID_EXTRA_PACKAGES += ['android.support.v4']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_SUPPORT_V4_AAR_RES']]
-# (no resources) resjar.generated_sources += ['android/support/v4/R.java']
-if CONFIG['ANDROID_APPCOMPAT_V7_AAR']:
- ANDROID_EXTRA_PACKAGES += ['android.support.v7.appcompat']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_APPCOMPAT_V7_AAR_RES']]
- resjar.generated_sources += ['android/support/v7/appcompat/R.java']
-if CONFIG['ANDROID_SUPPORT_VECTOR_DRAWABLE_AAR']:
- ANDROID_EXTRA_PACKAGES += ['android.support.graphics.drawable']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_SUPPORT_VECTOR_DRAWABLE_AAR_RES']]
-# (no reosurces) resjar.generated_sources += ['android/support/graphics/drawable/R.java']
-if CONFIG['ANDROID_ANIMATED_VECTOR_DRAWABLE_AAR']:
- ANDROID_EXTRA_PACKAGES += ['android.support.graphics.drawable.animated']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_ANIMATED_VECTOR_DRAWABLE_AAR_RES']]
-# (no resources) resjar.generated_sources += ['android/support/graphics/drawable/animated/R.java']
-if CONFIG['ANDROID_CARDVIEW_V7_AAR']:
- ANDROID_EXTRA_PACKAGES += ['android.support.v7.cardview']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_CARDVIEW_V7_AAR_RES']]
- resjar.generated_sources += ['android/support/v7/cardview/R.java']
-if CONFIG['ANDROID_DESIGN_AAR']:
- ANDROID_EXTRA_PACKAGES += ['android.support.design']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_DESIGN_AAR_RES']]
- resjar.generated_sources += ['android/support/design/R.java']
-if CONFIG['ANDROID_RECYCLERVIEW_V7_AAR']:
- ANDROID_EXTRA_PACKAGES += ['android.support.v7.recyclerview']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_RECYCLERVIEW_V7_AAR_RES']]
- resjar.generated_sources += ['android/support/v7/recyclerview/R.java']
-if CONFIG['ANDROID_CUSTOMTABS_AAR']:
- ANDROID_EXTRA_PACKAGES += ['android.support.customtabs']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_CUSTOMTABS_AAR_RES']]
-# (no resources) resjar.generated_sources += ['android/support/customtabs/R.java']
-if CONFIG['ANDROID_PALETTE_V7_AAR']:
- ANDROID_EXTRA_PACKAGES += ['android.support.v7.palette']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_PALETTE_V7_AAR_RES']]
-# (no resources) resjar.generated_sources += ['android/support/v7/palette/R.java']
-
-resjar.javac_flags += ['-Xlint:all']
-
-mgjar = add_java_jar('gecko-mozglue')
-mgjar.sources += [geckoview_source_dir + 'java/org/mozilla/gecko/' + x for x in [
- 'mozglue/ByteBufferInputStream.java',
- 'mozglue/DirectBufferAllocator.java',
- 'mozglue/GeckoLoader.java',
- 'mozglue/JNIObject.java',
- 'mozglue/NativeReference.java',
- 'mozglue/NativeZip.java',
- 'mozglue/SafeIntent.java',
-]]
-mgjar.generated_sources = [] # Keep it this way.
-mgjar.extra_jars += [
- CONFIG['ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB'],
- 'constants.jar',
-]
-mgjar.javac_flags += ['-Xlint:all']
-
-gujar = add_java_jar('gecko-util')
-gujar.sources += [geckoview_source_dir + 'java/org/mozilla/gecko/' + x for x in [
- 'util/ActivityResultHandler.java',
- 'util/ActivityResultHandlerMap.java',
- 'util/ActivityUtils.java',
- 'util/BundleEventListener.java',
- 'util/Clipboard.java',
- 'util/ContextUtils.java',
- 'util/DateUtil.java',
- 'util/EventCallback.java',
- 'util/FileUtils.java',
- 'util/FloatUtils.java',
- 'util/GamepadUtils.java',
- 'util/GeckoBackgroundThread.java',
- 'util/GeckoEventListener.java',
- 'util/GeckoJarReader.java',
- 'util/GeckoRequest.java',
- 'util/HardwareCodecCapabilityUtils.java',
- 'util/HardwareUtils.java',
- 'util/INIParser.java',
- 'util/INISection.java',
- 'util/InputOptionsUtils.java',
- 'util/IntentUtils.java',
- 'util/IOUtils.java',
- 'util/JSONUtils.java',
- 'util/MenuUtils.java',
- 'util/NativeEventListener.java',
- 'util/NativeJSContainer.java',
- 'util/NativeJSObject.java',
- 'util/NetworkUtils.java',
- 'util/NonEvictingLruCache.java',
- 'util/PrefUtils.java',
- 'util/ProxySelector.java',
- 'util/publicsuffix/PublicSuffix.java',
- 'util/publicsuffix/PublicSuffixPatterns.java',
- 'util/RawResource.java',
- 'util/StringUtils.java',
- 'util/ThreadUtils.java',
- 'util/UIAsyncTask.java',
- 'util/UUIDUtil.java',
- 'util/WeakReferenceHandler.java',
- 'util/WindowUtils.java',
-]]
-gujar.extra_jars = [
- CONFIG['ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB'],
- CONFIG['ANDROID_SUPPORT_V4_AAR_LIB'],
- CONFIG['ANDROID_SUPPORT_V4_AAR_INTERNAL_LIB'],
- 'constants.jar',
- 'gecko-mozglue.jar',
-]
-gujar.javac_flags += ['-Xlint:all,-deprecation']
-
-stjar = add_java_jar('sync-thirdparty')
-stjar.sources += [ thirdparty_source_dir + f for f in sync_thirdparty_java_files ]
-stjar.javac_flags = ['-Xlint:none']
-
-services_jar = add_java_jar('services')
-services_jar.sources += sync_java_files
-services_jar.extra_jars = [
- CONFIG['ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB'],
- CONFIG['ANDROID_SUPPORT_V4_AAR_LIB'],
- CONFIG['ANDROID_SUPPORT_V4_AAR_INTERNAL_LIB'],
- CONFIG['ANDROID_APPCOMPAT_V7_AAR_LIB'],
- 'constants.jar',
- 'gecko-R.jar',
- 'gecko-mozglue.jar',
- 'gecko-thirdparty.jar',
- 'gecko-util.jar',
- 'sync-thirdparty.jar',
-]
-services_jar.javac_flags += ['-Xlint:all,-deprecation']
-
-if CONFIG['MOZ_WEBRTC']:
- video_root = TOPSRCDIR + '/media/webrtc/trunk/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/'
- video_render_root = TOPSRCDIR + '/media/webrtc/trunk/webrtc/modules/video_render/android/java/src/org/webrtc/videoengine/'
- audio_root = TOPSRCDIR + '/media/webrtc/trunk/webrtc/modules/audio_device/android/java/src/org/webrtc/voiceengine/'
- wrjar = add_java_jar('webrtc')
- wrjar.sources += [
- video_root + 'CaptureCapabilityAndroid.java',
- video_root + 'VideoCaptureAndroid.java',
- video_root + 'VideoCaptureDeviceInfoAndroid.java',
- video_render_root + 'ViEAndroidGLES20.java',
- video_render_root + 'ViERenderer.java',
- ]
- wrjar.sources += [
- audio_root + 'AudioManagerAndroid.java',
- audio_root + 'WebRtcAudioManager.java',
- audio_root + 'WebRtcAudioRecord.java',
- audio_root + 'WebRtcAudioTrack.java',
- audio_root + 'WebRtcAudioUtils.java',
- ]
- wrjar.extra_jars = [
- 'constants.jar',
- 'gecko-R.jar',
- 'gecko-browser.jar',
- 'gecko-mozglue.jar',
- 'gecko-util.jar',
- 'gecko-view.jar',
- ]
- wrjar.javac_flags += ['-Xlint:all,-deprecation,-cast']
-
-gvjar = add_java_jar('gecko-view')
-
-gvjar.sources += [geckoview_source_dir + 'java/org/mozilla/gecko/' + x
- for x in [
- 'AlarmReceiver.java',
- 'AndroidGamepadManager.java',
- 'BaseGeckoInterface.java',
- 'ContextGetter.java',
- 'CrashHandler.java',
- 'EventDispatcher.java',
- 'GeckoAccessibility.java',
- 'GeckoAppShell.java',
- 'GeckoBatteryManager.java',
- 'GeckoEditable.java',
- 'GeckoEditableClient.java',
- 'GeckoEditableListener.java',
- 'GeckoHalDefines.java',
- 'GeckoInputConnection.java',
- 'GeckoNetworkManager.java',
- 'GeckoProfile.java',
- 'GeckoProfileDirectories.java',
- 'GeckoScreenOrientation.java',
- 'GeckoSharedPrefs.java',
- 'GeckoThread.java',
- 'GeckoView.java',
- 'GeckoViewChrome.java',
- 'GeckoViewContent.java',
- 'GeckoViewFragment.java',
- 'gfx/BitmapUtils.java',
- 'gfx/BufferedImage.java',
- 'gfx/BufferedImageGLInfo.java',
- 'gfx/DynamicToolbarAnimator.java',
- 'gfx/FloatSize.java',
- 'gfx/FullScreenState.java',
- 'gfx/GeckoLayerClient.java',
- 'gfx/ImmutableViewportMetrics.java',
- 'gfx/IntSize.java',
- 'gfx/LayerRenderer.java',
- 'gfx/LayerView.java',
- 'gfx/NativePanZoomController.java',
- 'gfx/Overscroll.java',
- 'gfx/OverscrollEdgeEffect.java',
- 'gfx/PanningPerfAPI.java',
- 'gfx/PanZoomController.java',
- 'gfx/PanZoomTarget.java',
- 'gfx/PointUtils.java',
- 'gfx/ProgressiveUpdateData.java',
- 'gfx/RectUtils.java',
- 'gfx/RenderTask.java',
- 'gfx/StackScroller.java',
- 'gfx/SurfaceTextureListener.java',
- 'gfx/ViewTransform.java',
- 'InputConnectionListener.java',
- 'InputMethods.java',
- 'NotificationListener.java',
- 'NSSBridge.java',
- 'permissions/PermissionBlock.java',
- 'permissions/Permissions.java',
- 'permissions/PermissionsHelper.java',
- 'PrefsHelper.java',
- 'sqlite/ByteBufferInputStream.java',
- 'sqlite/MatrixBlobCursor.java',
- 'sqlite/SQLiteBridge.java',
- 'sqlite/SQLiteBridgeException.java',
- 'TouchEventInterceptor.java',
-]]
-
-gvjar.sources += [geckoview_thirdparty_source_dir + f for f in [
- 'java/com/googlecode/eyesfree/braille/selfbraille/ISelfBrailleService.java',
- 'java/com/googlecode/eyesfree/braille/selfbraille/SelfBrailleClient.java',
- 'java/com/googlecode/eyesfree/braille/selfbraille/WriteData.java',
-]]
-
-gvjar.extra_jars += [
- CONFIG['ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB'],
- CONFIG['ANDROID_SUPPORT_V4_AAR_LIB'],
- CONFIG['ANDROID_SUPPORT_V4_AAR_INTERNAL_LIB'],
- 'constants.jar',
- 'gecko-mozglue.jar',
- 'gecko-util.jar',
-]
-
-gvjar.javac_flags += [
- '-Xlint:all,-deprecation,-fallthrough',
- '-J-Xmx512m',
- '-J-Xms128m'
-]
-
-
-gbjar = add_java_jar('gecko-browser')
-
-gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
- 'AboutPages.java',
- 'AccountsHelper.java',
- 'ActionBarTextSelection.java',
- 'ActionModeCompat.java',
- 'ActionModeCompatView.java',
- 'ActivityHandlerHelper.java',
- 'activitystream/ActivityStream.java',
- 'adjust/AdjustBrowserAppDelegate.java',
- 'animation/AnimationUtils.java',
- 'animation/HeightChangeAnimation.java',
- 'animation/PropertyAnimator.java',
- 'animation/Rotate3DAnimation.java',
- 'animation/ViewHelper.java',
- 'ANRReporter.java',
- 'BootReceiver.java',
- 'BrowserApp.java',
- 'BrowserLocaleManager.java',
- 'cleanup/FileCleanupController.java',
- 'cleanup/FileCleanupService.java',
- 'CustomEditText.java',
- 'customtabs/CustomTabsActivity.java',
- 'customtabs/GeckoCustomTabsService.java',
- 'DataReportingNotification.java',
- 'db/AbstractPerProfileDatabaseProvider.java',
- 'db/AbstractTransactionalProvider.java',
- 'db/BaseTable.java',
- 'db/BrowserDatabaseHelper.java',
- 'db/BrowserDB.java',
- 'db/BrowserProvider.java',
- 'db/DBUtils.java',
- 'db/FormHistoryProvider.java',
- 'db/HomeProvider.java',
- 'db/LocalBrowserDB.java',
- 'db/LocalSearches.java',
- 'db/LocalTabsAccessor.java',
- 'db/LocalUrlAnnotations.java',
- 'db/LocalURLMetadata.java',
- 'db/LoginsProvider.java',
- 'db/PasswordsProvider.java',
- 'db/PerProfileDatabaseProvider.java',
- 'db/PerProfileDatabases.java',
- 'db/RemoteClient.java',
- 'db/RemoteTab.java',
- 'db/Searches.java',
- 'db/SearchHistoryProvider.java',
- 'db/SharedBrowserDatabaseProvider.java',
- 'db/SQLiteBridgeContentProvider.java',
- 'db/SuggestedSites.java',
- 'db/Table.java',
- 'db/TabsAccessor.java',
- 'db/TabsProvider.java',
- 'db/UrlAnnotations.java',
- 'db/URLMetadata.java',
- 'db/URLMetadataTable.java',
- 'delegates/BookmarkStateChangeDelegate.java',
- 'delegates/BrowserAppDelegate.java',
- 'delegates/BrowserAppDelegateWithReference.java',
- 'delegates/OfflineTabStatusDelegate.java',
- 'delegates/ScreenshotDelegate.java',
- 'delegates/TabsTrayVisibilityAwareDelegate.java',
- 'DevToolsAuthHelper.java',
- 'distribution/Distribution.java',
- 'distribution/DistributionStoreCallback.java',
- 'distribution/PartnerBookmarksProviderProxy.java',
- 'distribution/PartnerBrowserCustomizationsClient.java',
- 'distribution/ReferrerDescriptor.java',
- 'distribution/ReferrerReceiver.java',
- 'dlc/BaseAction.java',
- 'dlc/catalog/DownloadContent.java',
- 'dlc/catalog/DownloadContentBootstrap.java',
- 'dlc/catalog/DownloadContentBuilder.java',
- 'dlc/catalog/DownloadContentCatalog.java',
- 'dlc/DownloadAction.java',
- 'dlc/DownloadContentService.java',
- 'dlc/StudyAction.java',
- 'dlc/SyncAction.java',
- 'dlc/VerifyAction.java',
- 'DoorHangerPopup.java',
- 'DownloadsIntegration.java',
- 'DynamicToolbar.java',
- 'EditBookmarkDialog.java',
- 'Experiments.java',
- 'feeds/action/CheckForUpdatesAction.java',
- 'feeds/action/EnrollSubscriptionsAction.java',
- 'feeds/action/FeedAction.java',
- 'feeds/action/SetupAlarmsAction.java',
- 'feeds/action/SubscribeToFeedAction.java',
- 'feeds/action/WithdrawSubscriptionsAction.java',
- 'feeds/ContentNotificationsDelegate.java',
- 'feeds/FeedAlarmReceiver.java',
- 'feeds/FeedFetcher.java',
- 'feeds/FeedService.java',
- 'feeds/knownsites/KnownSite.java',
- 'feeds/knownsites/KnownSiteBlogger.java',
- 'feeds/knownsites/KnownSiteMedium.java',
- 'feeds/knownsites/KnownSiteTumblr.java',
- 'feeds/knownsites/KnownSiteWordpress.java',
- 'feeds/parser/Feed.java',
- 'feeds/parser/Item.java',
- 'feeds/parser/SimpleFeedParser.java',
- 'feeds/subscriptions/FeedSubscription.java',
- 'FilePicker.java',
- 'FilePickerResultHandler.java',
- 'FindInPageBar.java',
- 'firstrun/DataPanel.java',
- 'firstrun/FirstrunAnimationContainer.java',
- 'firstrun/FirstrunPager.java',
- 'firstrun/FirstrunPagerConfig.java',
- 'firstrun/FirstrunPanel.java',
- 'firstrun/RestrictedWelcomePanel.java',
- 'firstrun/SyncPanel.java',
- 'firstrun/TabQueuePanel.java',
- 'FormAssistPopup.java',
- 'GeckoActivity.java',
- 'GeckoActivityStatus.java',
- 'GeckoApp.java',
- 'GeckoApplication.java',
- 'GeckoJavaSampler.java',
- 'GeckoMessageReceiver.java',
- 'GeckoProfilesProvider.java',
- 'GeckoService.java',
- 'GeckoUpdateReceiver.java',
- 'GlobalHistory.java',
- 'GlobalPageMetadata.java',
- 'GuestSession.java',
- 'health/HealthRecorder.java',
- 'health/SessionInformation.java',
- 'health/StubbedHealthRecorder.java',
- 'home/activitystream/ActivityStream.java',
- 'home/activitystream/ActivityStreamHomeFragment.java',
- 'home/activitystream/ActivityStreamHomeScreen.java',
- 'home/activitystream/menu/ActivityStreamContextMenu.java',
- 'home/activitystream/menu/BottomSheetContextMenu.java',
- 'home/activitystream/menu/PopupContextMenu.java',
- 'home/activitystream/StreamItem.java',
- 'home/activitystream/StreamRecyclerAdapter.java',
- 'home/activitystream/topsites/CirclePageIndicator.java',
- 'home/activitystream/topsites/TopSitesCard.java',
- 'home/activitystream/topsites/TopSitesPage.java',
- 'home/activitystream/topsites/TopSitesPageAdapter.java',
- 'home/activitystream/topsites/TopSitesPagerAdapter.java',
- 'home/BookmarkFolderView.java',
- 'home/BookmarkScreenshotRow.java',
- 'home/BookmarksListAdapter.java',
- 'home/BookmarksListView.java',
- 'home/BookmarksPanel.java',
- 'home/BrowserSearch.java',
- 'home/ClientsAdapter.java',
- 'home/CombinedHistoryAdapter.java',
- 'home/CombinedHistoryItem.java',
- 'home/CombinedHistoryPanel.java',
- 'home/CombinedHistoryRecyclerView.java',
- 'home/DynamicPanel.java',
- 'home/FramePanelLayout.java',
- 'home/HistorySectionsHelper.java',
- 'home/HomeAdapter.java',
- 'home/HomeBanner.java',
- 'home/HomeConfig.java',
- 'home/HomeConfigLoader.java',
- 'home/HomeConfigPrefsBackend.java',
- 'home/HomeContextMenuInfo.java',
- 'home/HomeExpandableListView.java',
- 'home/HomeFragment.java',
- 'home/HomeListView.java',
- 'home/HomePager.java',
- 'home/HomePanelsManager.java',
- 'home/HomeScreen.java',
- 'home/ImageLoader.java',
- 'home/MultiTypeCursorAdapter.java',
- 'home/PanelAuthCache.java',
- 'home/PanelAuthLayout.java',
- 'home/PanelBackItemView.java',
- 'home/PanelHeaderView.java',
- 'home/PanelInfoManager.java',
- 'home/PanelItemView.java',
- 'home/PanelLayout.java',
- 'home/PanelListView.java',
- 'home/PanelRecyclerView.java',
- 'home/PanelRecyclerViewAdapter.java',
- 'home/PanelRefreshLayout.java',
- 'home/PanelViewAdapter.java',
- 'home/PanelViewItemHandler.java',
- 'home/PinSiteDialog.java',
- 'home/RecentTabsAdapter.java',
- 'home/RemoteTabsExpandableListState.java',
- 'home/SearchEngine.java',
- 'home/SearchEngineAdapter.java',
- 'home/SearchEngineBar.java',
- 'home/SearchEngineRow.java',
- 'home/SearchLoader.java',
- 'home/SimpleCursorLoader.java',
- 'home/SpacingDecoration.java',
- 'home/TabMenuStrip.java',
- 'home/TabMenuStripLayout.java',
- 'home/TopSitesGridItemView.java',
- 'home/TopSitesGridView.java',
- 'home/TopSitesPanel.java',
- 'home/TopSitesThumbnailView.java',
- 'home/TwoLinePageRow.java',
- 'icons/decoders/FaviconDecoder.java',
- 'icons/decoders/ICODecoder.java',
- 'icons/decoders/IconDirectoryEntry.java',
- 'icons/decoders/LoadFaviconResult.java',
- 'icons/IconCallback.java',
- 'icons/IconDescriptor.java',
- 'icons/IconDescriptorComparator.java',
- 'icons/IconRequest.java',
- 'icons/IconRequestBuilder.java',
- 'icons/IconRequestExecutor.java',
- 'icons/IconResponse.java',
- 'icons/Icons.java',
- 'icons/IconsHelper.java',
- 'icons/IconTask.java',
- 'icons/loader/ContentProviderLoader.java',
- 'icons/loader/DataUriLoader.java',
- 'icons/loader/DiskLoader.java',
- 'icons/loader/IconDownloader.java',
- 'icons/loader/IconGenerator.java',
- 'icons/loader/IconLoader.java',
- 'icons/loader/JarLoader.java',
- 'icons/loader/LegacyLoader.java',
- 'icons/loader/MemoryLoader.java',
- 'icons/preparation/AboutPagesPreparer.java',
- 'icons/preparation/AddDefaultIconUrl.java',
- 'icons/preparation/FilterKnownFailureUrls.java',
- 'icons/preparation/FilterMimeTypes.java',
- 'icons/preparation/FilterPrivilegedUrls.java',
- 'icons/preparation/LookupIconUrl.java',
- 'icons/preparation/Preparer.java',
- 'icons/processing/ColorProcessor.java',
- 'icons/processing/DiskProcessor.java',
- 'icons/processing/MemoryProcessor.java',
- 'icons/processing/Processor.java',
- 'icons/processing/ResizingProcessor.java',
- 'icons/storage/DiskStorage.java',
- 'icons/storage/FailureCache.java',
- 'icons/storage/MemoryStorage.java',
- 'IntentHelper.java',
- 'javaaddons/JavaAddonManager.java',
- 'javaaddons/JavaAddonManagerV1.java',
- 'LauncherActivity.java',
- 'lwt/LightweightTheme.java',
- 'lwt/LightweightThemeDrawable.java',
- 'mdns/MulticastDNSManager.java',
- 'media/AsyncCodec.java',
- 'media/AsyncCodecFactory.java',
- 'media/AudioFocusAgent.java',
- 'media/Codec.java',
- 'media/CodecProxy.java',
- 'media/FormatParam.java',
- 'media/GeckoMediaDrm.java',
- 'media/GeckoMediaDrmBridgeV21.java',
- 'media/GeckoMediaDrmBridgeV23.java',
- 'media/JellyBeanAsyncCodec.java',
- 'media/LocalMediaDrmBridge.java',
- 'media/MediaControlService.java',
- 'media/MediaDrmProxy.java',
- 'media/MediaManager.java',
- 'media/RemoteManager.java',
- 'media/RemoteMediaDrmBridge.java',
- 'media/RemoteMediaDrmBridgeStub.java',
- 'media/Sample.java',
- 'media/SamplePool.java',
- 'media/SessionKeyInfo.java',
- 'media/VideoPlayer.java',
- 'MediaCastingBar.java',
- 'MemoryMonitor.java',
- 'menu/GeckoMenu.java',
- 'menu/GeckoMenuInflater.java',
- 'menu/GeckoMenuItem.java',
- 'menu/GeckoSubMenu.java',
- 'menu/MenuItemActionBar.java',
- 'menu/MenuItemDefault.java',
- 'menu/MenuItemSwitcherLayout.java',
- 'menu/MenuPanel.java',
- 'menu/MenuPopup.java',
- 'MotionEventInterceptor.java',
- 'mozglue/SharedMemBuffer.java',
- 'mozglue/SharedMemory.java',
- 'notifications/NotificationClient.java',
- 'notifications/NotificationHelper.java',
- 'notifications/NotificationReceiver.java',
- 'notifications/NotificationService.java',
- 'notifications/WhatsNewReceiver.java',
- 'overlays/OverlayConstants.java',
- 'overlays/service/OverlayActionService.java',
- 'overlays/service/ShareData.java',
- 'overlays/service/sharemethods/AddBookmark.java',
- 'overlays/service/sharemethods/SendTab.java',
- 'overlays/service/sharemethods/ShareMethod.java',
- 'overlays/ui/OverlayDialogButton.java',
- 'overlays/ui/SendTabDeviceListArrayAdapter.java',
- 'overlays/ui/SendTabList.java',
- 'overlays/ui/SendTabTargetSelectedListener.java',
- 'overlays/ui/ShareDialog.java',
- 'PackageReplacedReceiver.java',
- 'preferences/AlignRightLinkPreference.java',
- 'preferences/AndroidImport.java',
- 'preferences/AndroidImportPreference.java',
- 'preferences/AppCompatPreferenceActivity.java',
- 'preferences/ClearOnShutdownPref.java',
- 'preferences/CustomCheckBoxPreference.java',
- 'preferences/CustomListCategory.java',
- 'preferences/CustomListPreference.java',
- 'preferences/DistroSharedPrefsImport.java',
- 'preferences/FontSizePreference.java',
- 'preferences/GeckoPreferenceFragment.java',
- 'preferences/GeckoPreferences.java',
- 'preferences/LinkPreference.java',
- 'preferences/ListCheckboxPreference.java',
- 'preferences/LocaleListPreference.java',
- 'preferences/ModifiableHintPreference.java',
- 'preferences/MultiChoicePreference.java',
- 'preferences/MultiPrefMultiChoicePreference.java',
- 'preferences/PanelsPreference.java',
- 'preferences/PanelsPreferenceCategory.java',
- 'preferences/PrivateDataPreference.java',
- 'preferences/SearchEnginePreference.java',
- 'preferences/SearchPreferenceCategory.java',
- 'preferences/SetHomepagePreference.java',
- 'preferences/SyncPreference.java',
- 'PresentationView.java',
- 'PrintHelper.java',
- 'PrivateTab.java',
- 'promotion/AddToHomeScreenPromotion.java',
- 'promotion/HomeScreenPrompt.java',
- 'promotion/ReaderViewBookmarkPromotion.java',
- 'promotion/SimpleHelperUI.java',
- 'prompts/ColorPickerInput.java',
- 'prompts/IconGridInput.java',
- 'prompts/IntentChooserPrompt.java',
- 'prompts/IntentHandler.java',
- 'prompts/Prompt.java',
- 'prompts/PromptInput.java',
- 'prompts/PromptListAdapter.java',
- 'prompts/PromptListItem.java',
- 'prompts/PromptService.java',
- 'prompts/TabInput.java',
- 'reader/ReaderModeUtils.java',
- 'reader/ReadingListHelper.java',
- 'reader/SavedReaderViewHelper.java',
- 'RemoteClientsDialogFragment.java',
- 'Restarter.java',
- 'restrictions/DefaultConfiguration.java',
- 'restrictions/GuestProfileConfiguration.java',
- 'restrictions/Restrictable.java',
- 'restrictions/RestrictedProfileConfiguration.java',
- 'restrictions/RestrictionCache.java',
- 'restrictions/RestrictionConfiguration.java',
- 'restrictions/RestrictionProvider.java',
- 'restrictions/Restrictions.java',
- 'ScreenManagerHelper.java',
- 'ScreenshotObserver.java',
- 'search/SearchEngine.java',
- 'search/SearchEngineManager.java',
- 'SessionParser.java',
- 'SharedPreferencesHelper.java',
- 'SiteIdentity.java',
- 'SnackbarBuilder.java',
- 'SuggestClient.java',
- 'Tab.java',
- 'tabqueue/TabQueueHelper.java',
- 'tabqueue/TabQueuePrompt.java',
- 'tabqueue/TabQueueService.java',
- 'tabqueue/TabReceivedService.java',
- 'Tabs.java',
- 'tabs/PrivateTabsPanel.java',
- 'tabs/TabCurve.java',
- 'tabs/TabHistoryController.java',
- 'tabs/TabHistoryFragment.java',
- 'tabs/TabHistoryItemRow.java',
- 'tabs/TabHistoryPage.java',
- 'tabs/TabPanelBackButton.java',
- 'tabs/TabsGridLayout.java',
- 'tabs/TabsLayout.java',
- 'tabs/TabsLayoutAdapter.java',
- 'tabs/TabsLayoutItemView.java',
- 'tabs/TabsLayoutRecyclerAdapter.java',
- 'tabs/TabsListLayout.java',
- 'tabs/TabsListLayoutAnimator.java',
- 'tabs/TabsPanel.java',
- 'tabs/TabsPanelThumbnailView.java',
- 'tabs/TabsTouchHelperCallback.java',
- 'Telemetry.java',
- 'telemetry/measurements/CampaignIdMeasurements.java',
- 'telemetry/measurements/SearchCountMeasurements.java',
- 'telemetry/measurements/SessionMeasurements.java',
- 'telemetry/pingbuilders/TelemetryCorePingBuilder.java',
- 'telemetry/pingbuilders/TelemetryPingBuilder.java',
- 'telemetry/schedulers/TelemetryUploadAllPingsImmediatelyScheduler.java',
- 'telemetry/schedulers/TelemetryUploadScheduler.java',
- 'telemetry/stores/TelemetryJSONFilePingStore.java',
- 'telemetry/stores/TelemetryPingStore.java',
- 'telemetry/TelemetryConstants.java',
- 'telemetry/TelemetryCorePingDelegate.java',
- 'telemetry/TelemetryDispatcher.java',
- 'telemetry/TelemetryPing.java',
- 'telemetry/TelemetryPreferences.java',
- 'telemetry/TelemetryUploadService.java',
- 'TelemetryContract.java',
- 'text/FloatingActionModeCallback.java',
- 'text/FloatingToolbarTextSelection.java',
- 'text/TextAction.java',
- 'text/TextSelection.java',
- 'ThumbnailHelper.java',
- 'toolbar/AutocompleteHandler.java',
- 'toolbar/BackButton.java',
- 'toolbar/BrowserToolbar.java',
- 'toolbar/BrowserToolbarPhone.java',
- 'toolbar/BrowserToolbarPhoneBase.java',
- 'toolbar/BrowserToolbarTablet.java',
- 'toolbar/BrowserToolbarTabletBase.java',
- 'toolbar/CanvasDelegate.java',
- 'toolbar/ForwardButton.java',
- 'toolbar/NavButton.java',
- 'toolbar/PageActionLayout.java',
- 'toolbar/PhoneTabsButton.java',
- 'toolbar/ShapedButton.java',
- 'toolbar/ShapedButtonFrameLayout.java',
- 'toolbar/SiteIdentityPopup.java',
- 'toolbar/TabCounter.java',
- 'toolbar/ToolbarDisplayLayout.java',
- 'toolbar/ToolbarEditLayout.java',
- 'toolbar/ToolbarEditText.java',
- 'toolbar/ToolbarPrefs.java',
- 'toolbar/ToolbarProgressView.java',
- 'trackingprotection/TrackingProtectionPrompt.java',
- 'updater/PostUpdateHandler.java',
- 'updater/UpdateService.java',
- 'updater/UpdateServiceHelper.java',
- 'util/ColorUtil.java',
- 'util/DrawableUtil.java',
- 'util/ResourceDrawableUtils.java',
- 'util/TouchTargetUtil.java',
- 'util/ViewUtil.java',
- 'widget/ActivityChooserModel.java',
- 'widget/AllCapsTextView.java',
- 'widget/AnchoredPopup.java',
- 'widget/AnimatedHeightLayout.java',
- 'widget/BasicColorPicker.java',
- 'widget/CheckableLinearLayout.java',
- 'widget/ClickableWhenDisabledEditText.java',
- 'widget/ContentSecurityDoorHanger.java',
- 'widget/CropImageView.java',
- 'widget/DateTimePicker.java',
- 'widget/DefaultDoorHanger.java',
- 'widget/DefaultItemAnimatorBase.java',
- 'widget/DoorHanger.java',
- 'widget/DoorhangerConfig.java',
- 'widget/EllipsisTextView.java',
- 'widget/ExternalIntentDuringPrivateBrowsingPromptFragment.java',
- 'widget/FadedMultiColorTextView.java',
- 'widget/FadedSingleColorTextView.java',
- 'widget/FadedTextView.java',
- 'widget/FaviconView.java',
- 'widget/FilledCardView.java',
- 'widget/FlowLayout.java',
- 'widget/GeckoActionProvider.java',
- 'widget/GeckoPopupMenu.java',
- 'widget/HistoryDividerItemDecoration.java',
- 'widget/IconTabWidget.java',
- 'widget/LoginDoorHanger.java',
- 'widget/RecyclerViewClickSupport.java',
- 'widget/ResizablePathDrawable.java',
- 'widget/RoundedCornerLayout.java',
- 'widget/SiteLogins.java',
- 'widget/SquaredImageView.java',
- 'widget/SquaredRelativeLayout.java',
- 'widget/SwipeDismissListViewTouchListener.java',
- 'widget/TabThumbnailWrapper.java',
- 'widget/ThumbnailView.java',
- 'widget/TouchDelegateWithReset.java',
- 'widget/TwoWayView.java',
- 'ZoomedView.java',
-]]
-# The following sources are checked in to version control but
-# generated by a script (java/org/mozilla/gecko/widget/themed/generate_themed_views.py).
-# If you're editing this list, make sure to edit that script.
-gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
- 'widget/themed/ThemedEditText.java',
- 'widget/themed/ThemedFrameLayout.java',
- 'widget/themed/ThemedImageButton.java',
- 'widget/themed/ThemedImageView.java',
- 'widget/themed/ThemedLinearLayout.java',
- 'widget/themed/ThemedRelativeLayout.java',
- 'widget/themed/ThemedTextSwitcher.java',
- 'widget/themed/ThemedTextView.java',
- 'widget/themed/ThemedView.java',
-]]
-android_package_dir = CONFIG['ANDROID_PACKAGE_NAME'].replace('.', '/')
-gbjar.generated_sources = [] # Keep it this way.
-gbjar.extra_jars += [
- CONFIG['ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB'],
- CONFIG['ANDROID_SUPPORT_V4_AAR_LIB'],
- CONFIG['ANDROID_SUPPORT_V4_AAR_INTERNAL_LIB'],
- 'constants.jar'
-]
-if CONFIG['MOZ_ANDROID_GCM']:
- gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
- 'gcm/GcmInstanceIDListenerService.java',
- 'gcm/GcmMessageListenerService.java',
- 'gcm/GcmTokenClient.java',
- 'push/Fetched.java',
- 'push/PushClient.java',
- 'push/PushManager.java',
- 'push/PushRegistration.java',
- 'push/PushService.java',
- 'push/PushState.java',
- 'push/PushSubscription.java',
- ]]
-
-if (CONFIG['MOZ_ANDROID_MAX_SDK_VERSION']):
- max_sdk_version = int(CONFIG['MOZ_ANDROID_MAX_SDK_VERSION'])
-else:
- max_sdk_version = 999
-
-# Only bother to include new tablet code if we're building for tablet-capable
-# OS releases.
-if max_sdk_version >= 11:
- gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
- 'tabs/TabStrip.java',
- 'tabs/TabStripAdapter.java',
- 'tabs/TabStripItemView.java',
- 'tabs/TabStripView.java'
- ]]
-
-gbjar.extra_jars += [
- OBJDIR + '/../javaaddons/javaaddons-1.0.jar',
- 'gecko-R.jar',
- 'gecko-mozglue.jar',
- 'gecko-thirdparty.jar',
- 'gecko-util.jar',
- 'gecko-view.jar',
- 'sync-thirdparty.jar',
- 'services.jar',
-]
-
-moz_native_devices_jars = [
- CONFIG['ANDROID_MEDIAROUTER_V7_AAR_LIB'],
- CONFIG['ANDROID_MEDIAROUTER_V7_AAR_INTERNAL_LIB'],
- CONFIG['ANDROID_PLAY_SERVICES_BASE_AAR_LIB'],
- CONFIG['ANDROID_PLAY_SERVICES_BASEMENT_AAR_LIB'],
- CONFIG['ANDROID_PLAY_SERVICES_CAST_AAR_LIB'],
-]
-moz_native_devices_sources = ['java/org/mozilla/gecko/' + x for x in [
- 'ChromeCastDisplay.java',
- 'ChromeCastPlayer.java',
- 'GeckoMediaPlayer.java',
- 'GeckoPresentationDisplay.java',
- 'MediaPlayerManager.java',
- 'PresentationMediaPlayerManager.java',
- 'RemotePresentationService.java',
-]]
-if CONFIG['MOZ_NATIVE_DEVICES']:
- gbjar.extra_jars += moz_native_devices_jars
- gbjar.sources += moz_native_devices_sources
-
- if CONFIG['ANDROID_MEDIAROUTER_V7_AAR']:
- ANDROID_EXTRA_PACKAGES += ['android.support.v7.mediarouter']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_MEDIAROUTER_V7_AAR_RES']]
- resjar.generated_sources += ['android/support/v7/mediarouter/R.java']
-
- if CONFIG['ANDROID_PLAY_SERVICES_BASE_AAR']:
- ANDROID_EXTRA_PACKAGES += ['com.google.android.gms.base']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_PLAY_SERVICES_BASE_AAR_RES']]
- resjar.generated_sources += ['com/google/android/gms/base/R.java']
-
- if CONFIG['ANDROID_PLAY_SERVICES_BASEMENT_AAR']:
- ANDROID_EXTRA_PACKAGES += ['com.google.android.gms']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_PLAY_SERVICES_BASEMENT_AAR_RES']]
- resjar.generated_sources += ['com/google/android/gms/R.java']
-
- if CONFIG['ANDROID_PLAY_SERVICES_CAST_AAR']:
- ANDROID_EXTRA_PACKAGES += ['com.google.android.gms.cast']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_PLAY_SERVICES_CAST_AAR_RES']]
- resjar.generated_sources += ['com/google/android/gms/cast/R.java']
-
-if CONFIG['MOZ_ANDROID_GCM']:
- gbjar.extra_jars += [
- CONFIG['ANDROID_PLAY_SERVICES_BASE_AAR_LIB'],
- CONFIG['ANDROID_PLAY_SERVICES_BASEMENT_AAR_LIB'],
- CONFIG['ANDROID_PLAY_SERVICES_GCM_AAR_LIB'],
- CONFIG['ANDROID_PLAY_SERVICES_MEASUREMENT_AAR_LIB'],
- ]
-
- if CONFIG['ANDROID_PLAY_SERVICES_BASE_AAR']:
- ANDROID_EXTRA_PACKAGES += ['com.google.android.gms']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_PLAY_SERVICES_BASE_AAR_RES']]
- resjar.generated_sources += ['com/google/android/gms/R.java']
-
- if CONFIG['ANDROID_PLAY_SERVICES_BASEMENT_AAR']:
- ANDROID_EXTRA_PACKAGES += ['com.google.android.gms']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_PLAY_SERVICES_BASEMENT_AAR_RES']]
- resjar.generated_sources += ['com/google/android/gms/R.java']
-
- if CONFIG['ANDROID_PLAY_SERVICES_GCM_AAR']:
- ANDROID_EXTRA_PACKAGES += ['com.google.android.gms.gcm']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_PLAY_SERVICES_GCM_AAR_RES']]
-# (no resources) resjar.generated_sources += ['com/google/android/gms/gcm/R.java']
-
- if CONFIG['ANDROID_PLAY_SERVICES_MEASUREMENT_AAR']:
- ANDROID_EXTRA_PACKAGES += ['com.google.android.gms.measurement']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_PLAY_SERVICES_MEASUREMENT_AAR_RES']]
-# (no resources) resjar.generated_sources += ['android/support/v7/palette/R.java']
-
-if CONFIG['MOZ_INSTALL_TRACKING']:
- gbjar.extra_jars += [
- CONFIG['ANDROID_PLAY_SERVICES_ADS_AAR_LIB'],
- CONFIG['ANDROID_PLAY_SERVICES_BASEMENT_AAR_LIB'],
- ]
-
- if CONFIG['ANDROID_PLAY_SERVICES_ADS_AAR']:
- ANDROID_EXTRA_PACKAGES += ['com.google.android.gms.ads']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_PLAY_SERVICES_ADS_AAR_RES']]
- resjar.generated_sources += ['com/google/android/gms/ads/R.java']
-
- if CONFIG['ANDROID_PLAY_SERVICES_BASEMENT_AAR']:
- ANDROID_EXTRA_PACKAGES += ['com.google.android.gms']
- ANDROID_EXTRA_RES_DIRS += ['%' + CONFIG['ANDROID_PLAY_SERVICES_BASEMENT_AAR_RES']]
- resjar.generated_sources += ['com/google/android/gms/R.java']
-
-gbjar.extra_jars += [CONFIG['ANDROID_APPCOMPAT_V7_AAR_LIB']]
-gbjar.extra_jars += [CONFIG['ANDROID_SUPPORT_VECTOR_DRAWABLE_AAR_LIB']]
-gbjar.extra_jars += [CONFIG['ANDROID_ANIMATED_VECTOR_DRAWABLE_AAR_LIB']]
-gbjar.extra_jars += [CONFIG['ANDROID_CARDVIEW_V7_AAR_LIB']]
-gbjar.extra_jars += [CONFIG['ANDROID_DESIGN_AAR_LIB']]
-gbjar.extra_jars += [CONFIG['ANDROID_RECYCLERVIEW_V7_AAR_LIB']]
-gbjar.extra_jars += [CONFIG['ANDROID_CUSTOMTABS_AAR_LIB']]
-gbjar.extra_jars += [CONFIG['ANDROID_PALETTE_V7_AAR_LIB']]
-
-gbjar.javac_flags += ['-Xlint:all,-deprecation,-fallthrough', '-J-Xmx512m', '-J-Xms128m']
-
-# gecko-thirdparty is a good place to put small independent libraries
-gtjar = add_java_jar('gecko-thirdparty')
-gtjar.sources += [ thirdparty_source_dir + f for f in [
- 'com/jakewharton/disklrucache/DiskLruCache.java',
- 'com/jakewharton/disklrucache/StrictLineReader.java',
- 'com/jakewharton/disklrucache/Util.java',
- 'com/keepsafe/switchboard/AsyncConfigLoader.java',
- 'com/keepsafe/switchboard/DeviceUuidFactory.java',
- 'com/keepsafe/switchboard/Preferences.java',
- 'com/keepsafe/switchboard/Switch.java',
- 'com/keepsafe/switchboard/SwitchBoard.java',
- 'com/squareup/leakcanary/LeakCanary.java',
- 'com/squareup/leakcanary/RefWatcher.java',
- 'com/squareup/picasso/Action.java',
- 'com/squareup/picasso/AssetBitmapHunter.java',
- 'com/squareup/picasso/BitmapHunter.java',
- 'com/squareup/picasso/Cache.java',
- 'com/squareup/picasso/Callback.java',
- 'com/squareup/picasso/ContactsPhotoBitmapHunter.java',
- 'com/squareup/picasso/ContentStreamBitmapHunter.java',
- 'com/squareup/picasso/DeferredRequestCreator.java',
- 'com/squareup/picasso/Dispatcher.java',
- 'com/squareup/picasso/Downloader.java',
- 'com/squareup/picasso/FetchAction.java',
- 'com/squareup/picasso/FileBitmapHunter.java',
- 'com/squareup/picasso/GetAction.java',
- 'com/squareup/picasso/ImageViewAction.java',
- 'com/squareup/picasso/LruCache.java',
- 'com/squareup/picasso/MarkableInputStream.java',
- 'com/squareup/picasso/MediaStoreBitmapHunter.java',
- 'com/squareup/picasso/NetworkBitmapHunter.java',
- 'com/squareup/picasso/Picasso.java',
- 'com/squareup/picasso/PicassoDrawable.java',
- 'com/squareup/picasso/PicassoExecutorService.java',
- 'com/squareup/picasso/Request.java',
- 'com/squareup/picasso/RequestCreator.java',
- 'com/squareup/picasso/ResourceBitmapHunter.java',
- 'com/squareup/picasso/Stats.java',
- 'com/squareup/picasso/StatsSnapshot.java',
- 'com/squareup/picasso/Target.java',
- 'com/squareup/picasso/TargetAction.java',
- 'com/squareup/picasso/Transformation.java',
- 'com/squareup/picasso/UrlConnectionDownloader.java',
- 'com/squareup/picasso/Utils.java'
-] ]
-gtjar.extra_jars = [
- CONFIG['ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB'],
- CONFIG['ANDROID_SUPPORT_V4_AAR_LIB'],
- CONFIG['ANDROID_SUPPORT_V4_AAR_INTERNAL_LIB'],
-]
-
-if not CONFIG['MOZILLA_OFFICIAL']:
- gtjar.sources += [ thirdparty_source_dir + f for f in [
- 'org/lucasr/dspec/DesignSpec.java',
- 'org/lucasr/dspec/RawResource.java'
- ] ]
-
-if CONFIG['MOZ_INSTALL_TRACKING']:
- adjustjar = add_java_jar('gecko-thirdparty-adjust_sdk')
- adjustjar.sources += [ thirdparty_source_dir + f for f in [
- 'com/adjust/sdk/ActivityHandler.java',
- 'com/adjust/sdk/ActivityKind.java',
- 'com/adjust/sdk/ActivityPackage.java',
- 'com/adjust/sdk/ActivityState.java',
- 'com/adjust/sdk/Adjust.java',
- 'com/adjust/sdk/AdjustAttribution.java',
- 'com/adjust/sdk/AdjustConfig.java',
- 'com/adjust/sdk/AdjustEvent.java',
- 'com/adjust/sdk/AdjustFactory.java',
- 'com/adjust/sdk/AdjustInstance.java',
- 'com/adjust/sdk/AdjustReferrerReceiver.java',
- 'com/adjust/sdk/AttributionHandler.java',
- 'com/adjust/sdk/Constants.java',
- 'com/adjust/sdk/DeviceInfo.java',
- 'com/adjust/sdk/IActivityHandler.java',
- 'com/adjust/sdk/IAttributionHandler.java',
- 'com/adjust/sdk/ILogger.java',
- 'com/adjust/sdk/IPackageHandler.java',
- 'com/adjust/sdk/IRequestHandler.java',
- 'com/adjust/sdk/Logger.java',
- 'com/adjust/sdk/LogLevel.java',
- 'com/adjust/sdk/OnAttributionChangedListener.java',
- 'com/adjust/sdk/PackageBuilder.java',
- 'com/adjust/sdk/PackageHandler.java',
- 'com/adjust/sdk/plugin/AndroidIdUtil.java',
- 'com/adjust/sdk/plugin/MacAddressUtil.java',
- 'com/adjust/sdk/plugin/Plugin.java',
- 'com/adjust/sdk/Reflection.java',
- 'com/adjust/sdk/RequestHandler.java',
- 'com/adjust/sdk/UnitTestActivity.java',
- 'com/adjust/sdk/Util.java'
- ] ]
- adjustjar.extra_jars += [
- 'sync-thirdparty.jar',
- ]
-
-# Putting branding earlier allows branders to override default resources.
-ANDROID_RES_DIRS += [
- '/' + CONFIG['MOZ_BRANDING_DIRECTORY'] + '/res',
- 'resources',
- '/mobile/android/services/src/main/res',
- '!res',
-]
-
-ANDROID_GENERATED_RESFILES += [
- 'res/raw/browsersearch.json',
- 'res/raw/suggestedsites.json',
- 'res/values/strings.xml',
-]
-
-ANDROID_ASSETS_DIRS += [
- '/mobile/android/app/assets',
-]
-
-if CONFIG['MOZ_ANDROID_DISTRIBUTION_DIRECTORY']:
- # If you change this, also change its equivalent in mobile/android/bouncer.
- if not CONFIG['MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER']:
- # If we are packaging the bouncer, it will have the distribution, so don't put
- # it in the main APK as well.
- ANDROID_ASSETS_DIRS += [
- '%' + CONFIG['MOZ_ANDROID_DISTRIBUTION_DIRECTORY'] + '/assets',
- ]
-
-# We do not expose MOZ_ADJUST_SDK_KEY here because that # would leak the value
-# to build logs. Instead we expose the token quietly where appropriate in
-# Makefile.in.
-for var in ('MOZ_ANDROID_ANR_REPORTER', 'MOZ_DEBUG',
- 'MOZ_ANDROID_SEARCH_ACTIVITY', 'MOZ_NATIVE_DEVICES', 'MOZ_ANDROID_MLS_STUMBLER',
- 'MOZ_ANDROID_DOWNLOADS_INTEGRATION', 'MOZ_INSTALL_TRACKING',
- 'MOZ_ANDROID_GCM', 'MOZ_ANDROID_EXCLUDE_FONTS', 'MOZ_LOCALE_SWITCHER',
- 'MOZ_ANDROID_BEAM', 'MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE',
- 'MOZ_SWITCHBOARD', 'MOZ_ANDROID_CUSTOM_TABS',
- 'MOZ_ANDROID_ACTIVITY_STREAM'):
- if CONFIG[var]:
- DEFINES[var] = 1
-
-for var in ('MOZ_UPDATER', 'MOZ_PKG_SPECIAL', 'MOZ_ANDROID_GCM_SENDERID'):
- if CONFIG[var]:
- DEFINES[var] = CONFIG[var]
-
-for var in ('ANDROID_PACKAGE_NAME', 'ANDROID_CPU_ARCH',
- 'GRE_MILESTONE', 'MOZ_APP_BASENAME', 'MOZ_MOZILLA_API_KEY',
- 'MOZ_APP_DISPLAYNAME', 'MOZ_APP_UA_NAME', 'MOZ_APP_ID', 'MOZ_APP_NAME',
- 'MOZ_APP_VENDOR', 'MOZ_APP_VERSION', 'MOZ_CHILD_PROCESS_NAME',
- 'MOZ_ANDROID_APPLICATION_CLASS', 'MOZ_ANDROID_BROWSER_INTENT_CLASS', 'MOZ_ANDROID_SEARCH_INTENT_CLASS',
- 'MOZ_UPDATE_CHANNEL', 'OMNIJAR_NAME',
- 'OS_TARGET', 'TARGET_XPCOM_ABI'):
- DEFINES[var] = CONFIG[var]
-
-# Mangle our package name to avoid Bug 750548.
-DEFINES['MANGLED_ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME'].replace('fennec', 'f3nn3c')
-DEFINES['MOZ_APP_ABI'] = CONFIG['TARGET_XPCOM_ABI']
-if not CONFIG['COMPILE_ENVIRONMENT']:
- # These should really come from the included binaries, but that's not easy.
- DEFINES['MOZ_APP_ABI'] = 'arm-eabi-gcc3' # Observe quote differences here ...
- DEFINES['TARGET_XPCOM_ABI'] = '"arm-eabi-gcc3"' # ... and here.
-
-if '-march=armv7' in CONFIG['OS_CFLAGS']:
- DEFINES['MOZ_MIN_CPU_VERSION'] = 7
-else:
- DEFINES['MOZ_MIN_CPU_VERSION'] = 5
-
-if CONFIG['MOZ_ANDROID_SEARCH_ACTIVITY']:
- # The Search Activity is mostly independent of Fennec proper, but
- # it does depend on Geckoview. Therefore, we build it as a jar
- # that depends on the Geckoview jars.
- search_source_dir = SRCDIR + '/../search'
- include('../search/search_activity_sources.mozbuild')
-
- search_activity = add_java_jar('search-activity')
- search_activity.sources += [search_source_dir + '/' + f for f in search_activity_sources]
- search_activity.javac_flags += ['-Xlint:all']
- search_activity.extra_jars = [
- CONFIG['ANDROID_SUPPORT_ANNOTATIONS_JAR_LIB'],
- CONFIG['ANDROID_SUPPORT_V4_AAR_LIB'],
- CONFIG['ANDROID_SUPPORT_V4_AAR_INTERNAL_LIB'],
- 'constants.jar',
- 'gecko-R.jar',
- 'gecko-browser.jar',
- 'gecko-mozglue.jar',
- 'gecko-thirdparty.jar',
- 'gecko-util.jar',
- 'gecko-view.jar',
- ]
-
-FINAL_TARGET_PP_FILES += ['package-name.txt.in']
-
-DEFINES['OBJDIR'] = OBJDIR
-DEFINES['TOPOBJDIR'] = TOPOBJDIR
-
-OBJDIR_PP_FILES.mobile.android.base += [
- 'AndroidManifest.xml.in',
-]
-
-gbjar.sources += ['generated/org/mozilla/gecko/' + x for x in [
- 'media/ICodec.java',
- 'media/ICodecCallbacks.java',
- 'media/IMediaDrmBridge.java',
- 'media/IMediaDrmBridgeCallbacks.java',
- 'media/IMediaManager.java',
-]]
diff --git a/mobile/android/base/package-name.txt.in b/mobile/android/base/package-name.txt.in
deleted file mode 100644
index c7b9731cb..000000000
--- a/mobile/android/base/package-name.txt.in
+++ /dev/null
@@ -1,2 +0,0 @@
-#filter substitution
-@ANDROID_PACKAGE_NAME@
diff --git a/mobile/android/base/resources/anim/grow_fade_in.xml b/mobile/android/base/resources/anim/grow_fade_in.xml
deleted file mode 100644
index bf7a3f23d..000000000
--- a/mobile/android/base/resources/anim/grow_fade_in.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
- <scale android:fromXScale="1.0"
- android:toXScale="1.0"
- android:fromYScale="0.3"
- android:toYScale="1.0"
- android:pivotX="0%"
- android:pivotY="0%"
- android:duration="150"/>
-
- <alpha android:interpolator="@android:anim/decelerate_interpolator"
- android:fromAlpha="0.0"
- android:toAlpha="1.0"
- android:duration="150"/>
-
-</set>
diff --git a/mobile/android/base/resources/anim/overlay_check_entry.xml b/mobile/android/base/resources/anim/overlay_check_entry.xml
deleted file mode 100644
index 5a80612a7..000000000
--- a/mobile/android/base/resources/anim/overlay_check_entry.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<alpha
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:duration="250"
- android:fromAlpha="0.0"
- android:toAlpha="1.0" />
diff --git a/mobile/android/base/resources/anim/overlay_check_exit.xml b/mobile/android/base/resources/anim/overlay_check_exit.xml
deleted file mode 100644
index 9c42eaa0b..000000000
--- a/mobile/android/base/resources/anim/overlay_check_exit.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<alpha
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:duration="250"
- android:fromAlpha="1.0"
- android:toAlpha="0.0"
- android:fillAfter="true" />
diff --git a/mobile/android/base/resources/anim/overlay_pop.xml b/mobile/android/base/resources/anim/overlay_pop.xml
deleted file mode 100644
index 6b5c412dc..000000000
--- a/mobile/android/base/resources/anim/overlay_pop.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:fillAfter="true" >
-
- <scale
- android:duration="300"
- android:fromXScale="1"
- android:fromYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:toXScale="2"
- android:toYScale="2" >
- </scale>
-
- <scale
- android:duration="300"
- android:fromXScale="1"
- android:fromYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:toXScale="0.5"
- android:toYScale="0.5" >
- </scale>
-
-</set>
diff --git a/mobile/android/base/resources/anim/overlay_slide_down.xml b/mobile/android/base/resources/anim/overlay_slide_down.xml
deleted file mode 100644
index c7007458c..000000000
--- a/mobile/android/base/resources/anim/overlay_slide_down.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<translate xmlns:android="http://schemas.android.com/apk/res/android"
- android:duration="@android:integer/config_longAnimTime"
- android:fromYDelta="0"
- android:toYDelta="100%p"
- android:fillAfter="true" />
diff --git a/mobile/android/base/resources/anim/overlay_slide_up.xml b/mobile/android/base/resources/anim/overlay_slide_up.xml
deleted file mode 100644
index 2541493ed..000000000
--- a/mobile/android/base/resources/anim/overlay_slide_up.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<translate xmlns:android="http://schemas.android.com/apk/res/android"
- android:duration="@android:integer/config_longAnimTime"
- android:fromYDelta="100%p"
- android:toYDelta="0" />
diff --git a/mobile/android/base/resources/anim/popup_hide.xml b/mobile/android/base/resources/anim/popup_hide.xml
deleted file mode 100644
index 5ea5bc2cb..000000000
--- a/mobile/android/base/resources/anim/popup_hide.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:duration="150">
-
- <translate android:fromYDelta="-0"
- android:toYDelta="-20"/>
-
- <alpha android:fromAlpha="1.0"
- android:toAlpha="0.0"/>
-
-</set>
diff --git a/mobile/android/base/resources/anim/popup_show.xml b/mobile/android/base/resources/anim/popup_show.xml
deleted file mode 100644
index 6057b9063..000000000
--- a/mobile/android/base/resources/anim/popup_show.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:anim/decelerate_interpolator"
- android:duration="150">
-
- <translate android:fromYDelta="-20"
- android:toYDelta="0"/>
-
- <alpha android:fromAlpha="0.0"
- android:toAlpha="1.0"/>
-
-</set>
diff --git a/mobile/android/base/resources/color/action_bar_menu_item_colors.xml b/mobile/android/base/resources/color/action_bar_menu_item_colors.xml
deleted file mode 100644
index 756463d68..000000000
--- a/mobile/android/base/resources/color/action_bar_menu_item_colors.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <item gecko:state_private="true"
- android:state_enabled="false"
- android:color="@color/toolbar_icon_grey"
- />
-
- <item gecko:state_private="true"
- android:state_enabled="true"
- android:color="@color/tabs_tray_icon_grey"
- />
-
- <item gecko:state_private="false"
- android:state_enabled="false"
- android:color="@color/disabled_grey"
- />
-
- <item android:color="@color/toolbar_icon_grey" />
-
-</selector>
diff --git a/mobile/android/base/resources/color/action_bar_secondary_menu_item_colors.xml b/mobile/android/base/resources/color/action_bar_secondary_menu_item_colors.xml
deleted file mode 100644
index c72ce0923..000000000
--- a/mobile/android/base/resources/color/action_bar_secondary_menu_item_colors.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <item android:state_enabled="false"
- android:color="@color/disabled_grey"
- />
-
- <item android:color="@color/toolbar_icon_grey" />
-
-</selector>
diff --git a/mobile/android/base/resources/color/facet_button_text_color.xml b/mobile/android/base/resources/color/facet_button_text_color.xml
deleted file mode 100644
index 90da6e324..000000000
--- a/mobile/android/base/resources/color/facet_button_text_color.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- facet is selected -->
- <item android:state_checked="true" android:color="@color/facet_button_text_color_selected" />
-
- <!-- default -->
- <item android:color="@color/facet_button_text_color_default" />
-</selector>
diff --git a/mobile/android/base/resources/color/pressed_about_page_header_grey.xml b/mobile/android/base/resources/color/pressed_about_page_header_grey.xml
deleted file mode 100644
index 18533ff67..000000000
--- a/mobile/android/base/resources/color/pressed_about_page_header_grey.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_pressed="true"
- android:drawable="@color/about_page_header_grey" />
-
- <item android:drawable="@android:color/transparent"/>
-
-</selector>
diff --git a/mobile/android/base/resources/color/primary_text.xml b/mobile/android/base/resources/color/primary_text.xml
deleted file mode 100644
index 59d5699db..000000000
--- a/mobile/android/base/resources/color/primary_text.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_enabled="false" android:color="@color/text_color_primary_disable_only" />
- <item android:color="@color/placeholder_active_grey"/>
-
-</selector>
diff --git a/mobile/android/base/resources/color/primary_text_selector.xml b/mobile/android/base/resources/color/primary_text_selector.xml
deleted file mode 100644
index d21a60880..000000000
--- a/mobile/android/base/resources/color/primary_text_selector.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_enabled="false" android:color="@color/disabled_grey" />
- <item android:color="@color/text_and_tabs_tray_grey" />
-
-</selector>
diff --git a/mobile/android/base/resources/color/recyclerview_selector.xml b/mobile/android/base/resources/color/recyclerview_selector.xml
deleted file mode 100644
index 6ecc3c5e2..000000000
--- a/mobile/android/base/resources/color/recyclerview_selector.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_pressed="true"
- android:drawable="@color/highlight" />
-
- <item android:drawable="@android:color/transparent"/>
-
-</selector>
diff --git a/mobile/android/base/resources/color/secondary_text.xml b/mobile/android/base/resources/color/secondary_text.xml
deleted file mode 100644
index 73bebcd15..000000000
--- a/mobile/android/base/resources/color/secondary_text.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_enabled="false" android:color="@color/text_color_primary_disable_only" />
- <item android:color="@color/placeholder_grey"/>
-
-</selector>
diff --git a/mobile/android/base/resources/color/select_item_multichoice.xml b/mobile/android/base/resources/color/select_item_multichoice.xml
deleted file mode 100644
index 9eb9e2178..000000000
--- a/mobile/android/base/resources/color/select_item_multichoice.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_enabled="false" android:color="#999" />
- <item android:color="#000"/>
-
-</selector>
diff --git a/mobile/android/base/resources/color/state_pressed_toolbar_grey_pressed.xml b/mobile/android/base/resources/color/state_pressed_toolbar_grey_pressed.xml
deleted file mode 100644
index 41eb992de..000000000
--- a/mobile/android/base/resources/color/state_pressed_toolbar_grey_pressed.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_pressed="true"
- android:drawable="@color/toolbar_grey_pressed" />
-
- <item android:drawable="@android:color/transparent"/>
-
-</selector>
diff --git a/mobile/android/base/resources/color/tab_item_title.xml b/mobile/android/base/resources/color/tab_item_title.xml
deleted file mode 100644
index 443d4c11d..000000000
--- a/mobile/android/base/resources/color/tab_item_title.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_checked="true"
- android:color="@color/about_page_header_grey"/>
-
- <item android:state_pressed="true"
- android:color="@color/about_page_header_grey"/>
-
- <item android:color="@color/tabs_tray_icon_grey"/>
-
-</selector>
diff --git a/mobile/android/base/resources/color/tab_new_tab_strip_colors.xml b/mobile/android/base/resources/color/tab_new_tab_strip_colors.xml
deleted file mode 100644
index fd7a97b21..000000000
--- a/mobile/android/base/resources/color/tab_new_tab_strip_colors.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <item gecko:state_light="true"
- android:color="@color/toolbar_icon_grey"/>
-
- <item android:color="@color/tabs_tray_icon_grey"/>
-
-</selector> \ No newline at end of file
diff --git a/mobile/android/base/resources/color/tab_strip_item_bg.xml b/mobile/android/base/resources/color/tab_strip_item_bg.xml
deleted file mode 100644
index b8e7e298b..000000000
--- a/mobile/android/base/resources/color/tab_strip_item_bg.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <item android:state_pressed="true"
- android:state_checked="true"
- gecko:state_private="true"
- android:color="@color/highlight_nav_pb" />
-
- <item android:state_checked="true"
- gecko:state_private="true"
- android:color="@color/tabs_tray_grey_pressed" />
-
- <item android:state_pressed="true"
- gecko:state_private="true"
- android:color="@color/highlight_dark_focused" />
-
- <item android:state_pressed="true"
- android:state_checked="true"
- android:color="@color/toolbar_grey_pressed" />
-
- <!-- Lightweight Themes -->
- <item android:state_checked="true"
- gecko:state_light="true"
- android:color="@color/background_normal_lwt" />
- <item android:state_checked="true"
- gecko:state_dark="true"
- android:color="@color/background_normal_lwt" />
- <item android:state_pressed="true"
- gecko:state_light="true"
- android:color="@color/tablet_highlight_lwt" />
- <item android:state_pressed="true"
- gecko:state_dark="true"
- android:color="@color/tablet_highlight_dark_lwt" />
-
- <item android:state_checked="true"
- android:color="@color/toolbar_grey" />
-
- <item android:state_pressed="true"
- android:color="@color/tabs_tray_grey_pressed" />
-
- <item android:color="@android:color/transparent"/>
-
-</selector>
diff --git a/mobile/android/base/resources/color/tab_strip_item_title.xml b/mobile/android/base/resources/color/tab_strip_item_title.xml
deleted file mode 100644
index f4fca9750..000000000
--- a/mobile/android/base/resources/color/tab_strip_item_title.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <item android:state_checked="true"
- gecko:state_private="true"
- android:color="@color/about_page_header_grey"/>
-
- <item android:state_checked="true"
- android:color="@color/placeholder_grey"/>
-
- <item android:state_checked="false"
- gecko:state_dark="true"
- android:color="@color/about_page_header_grey"/>
-
- <item android:color="@color/placeholder_grey"/>
-
-</selector>
diff --git a/mobile/android/base/resources/color/tab_text_color.xml b/mobile/android/base/resources/color/tab_text_color.xml
deleted file mode 100644
index 51aba7e2a..000000000
--- a/mobile/android/base/resources/color/tab_text_color.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true"
- android:color="@color/placeholder_grey" />
-
- <item android:color="@color/panel_tab_text_normal"/>
-</selector> \ No newline at end of file
diff --git a/mobile/android/base/resources/color/tabs_counter_text_color.xml b/mobile/android/base/resources/color/tabs_counter_text_color.xml
deleted file mode 100644
index 945634b7a..000000000
--- a/mobile/android/base/resources/color/tabs_counter_text_color.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <item gecko:state_private="true"
- android:color="@color/tabs_tray_grey_pressed"/>
-
- <item android:color="@color/toolbar_grey"/>
-
-</selector> \ No newline at end of file
diff --git a/mobile/android/base/resources/color/tertiary_text.xml b/mobile/android/base/resources/color/tertiary_text.xml
deleted file mode 100644
index 54884519c..000000000
--- a/mobile/android/base/resources/color/tertiary_text.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_enabled="false" android:color="@color/text_color_primary_disable_only" />
- <item android:color="@color/text_color_tertiary"/>
-
-</selector>
diff --git a/mobile/android/base/resources/color/toolbar_display_layout_bg.xml b/mobile/android/base/resources/color/toolbar_display_layout_bg.xml
deleted file mode 100644
index 9a99e0858..000000000
--- a/mobile/android/base/resources/color/toolbar_display_layout_bg.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- These colors are defined from the drawables url_bar_entry_default_* -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <item gecko:state_private="true" android:color="@color/placeholder_active_grey"/>
-
- <item android:color="#FFFFFF"/>
-
-</selector>
diff --git a/mobile/android/base/resources/color/top_sites_grid_item_title.xml b/mobile/android/base/resources/color/top_sites_grid_item_title.xml
deleted file mode 100644
index 89ca50294..000000000
--- a/mobile/android/base/resources/color/top_sites_grid_item_title.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <!-- empty with no url -->
- <item android:state_empty="true" android:color="#80777777" />
-
- <!-- default -->
- <item android:color="@color/placeholder_grey"/>
-
-</selector>
diff --git a/mobile/android/base/resources/color/url_bar_title.xml b/mobile/android/base/resources/color/url_bar_title.xml
deleted file mode 100644
index 473bee41d..000000000
--- a/mobile/android/base/resources/color/url_bar_title.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <!-- private browsing mode -->
- <item gecko:state_private="true" android:color="@color/private_active_text" />
-
- <!-- dark theme -->
- <item gecko:state_dark="true" android:color="@color/private_active_text"/>
-
- <!-- light theme -->
- <item gecko:state_light="true" android:color="@color/placeholder_active_grey"/>
-
- <!-- normal mode -->
- <item android:color="@color/placeholder_active_grey"/>
-
-</selector>
diff --git a/mobile/android/base/resources/color/url_bar_title_hint.xml b/mobile/android/base/resources/color/url_bar_title_hint.xml
deleted file mode 100644
index 0c748ac0b..000000000
--- a/mobile/android/base/resources/color/url_bar_title_hint.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <!-- private browsing mode -->
- <item gecko:state_private="true" android:color="#FF7F828A" />
-
- <!-- normal mode -->
- <item android:color="#FF666666"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable-hdpi-v11/alert_camera.png b/mobile/android/base/resources/drawable-hdpi-v11/alert_camera.png
deleted file mode 100644
index 11800d0f1..000000000
--- a/mobile/android/base/resources/drawable-hdpi-v11/alert_camera.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi-v11/alert_download.png b/mobile/android/base/resources/drawable-hdpi-v11/alert_download.png
deleted file mode 100644
index 1b8f59e56..000000000
--- a/mobile/android/base/resources/drawable-hdpi-v11/alert_download.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi-v11/alert_guest.png b/mobile/android/base/resources/drawable-hdpi-v11/alert_guest.png
deleted file mode 100644
index 650b8246d..000000000
--- a/mobile/android/base/resources/drawable-hdpi-v11/alert_guest.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi-v11/alert_mic.png b/mobile/android/base/resources/drawable-hdpi-v11/alert_mic.png
deleted file mode 100644
index 4b0248b8b..000000000
--- a/mobile/android/base/resources/drawable-hdpi-v11/alert_mic.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi-v11/alert_mic_camera.png b/mobile/android/base/resources/drawable-hdpi-v11/alert_mic_camera.png
deleted file mode 100644
index 091ec077d..000000000
--- a/mobile/android/base/resources/drawable-hdpi-v11/alert_mic_camera.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi-v11/ic_menu_back.png b/mobile/android/base/resources/drawable-hdpi-v11/ic_menu_back.png
deleted file mode 100644
index d0ec44a96..000000000
--- a/mobile/android/base/resources/drawable-hdpi-v11/ic_menu_back.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi-v11/ic_menu_bookmark_add.png b/mobile/android/base/resources/drawable-hdpi-v11/ic_menu_bookmark_add.png
deleted file mode 100644
index a58abd2f1..000000000
--- a/mobile/android/base/resources/drawable-hdpi-v11/ic_menu_bookmark_add.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi-v11/ic_menu_forward.png b/mobile/android/base/resources/drawable-hdpi-v11/ic_menu_forward.png
deleted file mode 100644
index 3a8db422f..000000000
--- a/mobile/android/base/resources/drawable-hdpi-v11/ic_menu_forward.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi-v11/ic_menu_reload.png b/mobile/android/base/resources/drawable-hdpi-v11/ic_menu_reload.png
deleted file mode 100644
index d0ae488c7..000000000
--- a/mobile/android/base/resources/drawable-hdpi-v11/ic_menu_reload.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi-v11/ic_status_logo.png b/mobile/android/base/resources/drawable-hdpi-v11/ic_status_logo.png
deleted file mode 100644
index 5524dd072..000000000
--- a/mobile/android/base/resources/drawable-hdpi-v11/ic_status_logo.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi-v11/star_blue.png b/mobile/android/base/resources/drawable-hdpi-v11/star_blue.png
deleted file mode 100644
index fc292debb..000000000
--- a/mobile/android/base/resources/drawable-hdpi-v11/star_blue.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/ab_add_search_engine.png b/mobile/android/base/resources/drawable-hdpi/ab_add_search_engine.png
deleted file mode 100644
index 77b99cdc7..000000000
--- a/mobile/android/base/resources/drawable-hdpi/ab_add_search_engine.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/ab_copy.png b/mobile/android/base/resources/drawable-hdpi/ab_copy.png
deleted file mode 100644
index 6bf796c4f..000000000
--- a/mobile/android/base/resources/drawable-hdpi/ab_copy.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/ab_cut.png b/mobile/android/base/resources/drawable-hdpi/ab_cut.png
deleted file mode 100644
index 0b41f5431..000000000
--- a/mobile/android/base/resources/drawable-hdpi/ab_cut.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/ab_done.png b/mobile/android/base/resources/drawable-hdpi/ab_done.png
deleted file mode 100644
index 6b81da3c0..000000000
--- a/mobile/android/base/resources/drawable-hdpi/ab_done.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/ab_mic.png b/mobile/android/base/resources/drawable-hdpi/ab_mic.png
deleted file mode 100644
index 13f8eb356..000000000
--- a/mobile/android/base/resources/drawable-hdpi/ab_mic.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/ab_paste.png b/mobile/android/base/resources/drawable-hdpi/ab_paste.png
deleted file mode 100644
index 58fb8a31e..000000000
--- a/mobile/android/base/resources/drawable-hdpi/ab_paste.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/ab_qrcode.png b/mobile/android/base/resources/drawable-hdpi/ab_qrcode.png
deleted file mode 100644
index 727201a2c..000000000
--- a/mobile/android/base/resources/drawable-hdpi/ab_qrcode.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/ab_search.png b/mobile/android/base/resources/drawable-hdpi/ab_search.png
deleted file mode 100644
index 0d217f11a..000000000
--- a/mobile/android/base/resources/drawable-hdpi/ab_search.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/ab_select_all.png b/mobile/android/base/resources/drawable-hdpi/ab_select_all.png
deleted file mode 100644
index 7488ed571..000000000
--- a/mobile/android/base/resources/drawable-hdpi/ab_select_all.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/alert_camera.png b/mobile/android/base/resources/drawable-hdpi/alert_camera.png
deleted file mode 100644
index 6ae4be078..000000000
--- a/mobile/android/base/resources/drawable-hdpi/alert_camera.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/alert_download.png b/mobile/android/base/resources/drawable-hdpi/alert_download.png
deleted file mode 100644
index b55e779fc..000000000
--- a/mobile/android/base/resources/drawable-hdpi/alert_download.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/alert_download_animation_1.png b/mobile/android/base/resources/drawable-hdpi/alert_download_animation_1.png
deleted file mode 100644
index 22d6288ea..000000000
--- a/mobile/android/base/resources/drawable-hdpi/alert_download_animation_1.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/alert_download_animation_2.png b/mobile/android/base/resources/drawable-hdpi/alert_download_animation_2.png
deleted file mode 100644
index dc402e9b8..000000000
--- a/mobile/android/base/resources/drawable-hdpi/alert_download_animation_2.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/alert_download_animation_3.png b/mobile/android/base/resources/drawable-hdpi/alert_download_animation_3.png
deleted file mode 100644
index 719bcbdab..000000000
--- a/mobile/android/base/resources/drawable-hdpi/alert_download_animation_3.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/alert_download_animation_4.png b/mobile/android/base/resources/drawable-hdpi/alert_download_animation_4.png
deleted file mode 100644
index 4319d0039..000000000
--- a/mobile/android/base/resources/drawable-hdpi/alert_download_animation_4.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/alert_download_animation_5.png b/mobile/android/base/resources/drawable-hdpi/alert_download_animation_5.png
deleted file mode 100644
index f8d1a15d6..000000000
--- a/mobile/android/base/resources/drawable-hdpi/alert_download_animation_5.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/alert_download_animation_6.png b/mobile/android/base/resources/drawable-hdpi/alert_download_animation_6.png
deleted file mode 100644
index dffe36338..000000000
--- a/mobile/android/base/resources/drawable-hdpi/alert_download_animation_6.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/alert_guest.png b/mobile/android/base/resources/drawable-hdpi/alert_guest.png
deleted file mode 100644
index 17fc059c3..000000000
--- a/mobile/android/base/resources/drawable-hdpi/alert_guest.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/alert_mic.png b/mobile/android/base/resources/drawable-hdpi/alert_mic.png
deleted file mode 100644
index d218bc88c..000000000
--- a/mobile/android/base/resources/drawable-hdpi/alert_mic.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/alert_mic_camera.png b/mobile/android/base/resources/drawable-hdpi/alert_mic_camera.png
deleted file mode 100644
index ef33012b5..000000000
--- a/mobile/android/base/resources/drawable-hdpi/alert_mic_camera.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/arrow_up.png b/mobile/android/base/resources/drawable-hdpi/arrow_up.png
deleted file mode 100644
index dfa79aaff..000000000
--- a/mobile/android/base/resources/drawable-hdpi/arrow_up.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/blank.png b/mobile/android/base/resources/drawable-hdpi/blank.png
deleted file mode 100644
index 56535e7ef..000000000
--- a/mobile/android/base/resources/drawable-hdpi/blank.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/casting.png b/mobile/android/base/resources/drawable-hdpi/casting.png
deleted file mode 100644
index 3da63dc55..000000000
--- a/mobile/android/base/resources/drawable-hdpi/casting.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/casting_active.png b/mobile/android/base/resources/drawable-hdpi/casting_active.png
deleted file mode 100644
index baf55c4cc..000000000
--- a/mobile/android/base/resources/drawable-hdpi/casting_active.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/close.png b/mobile/android/base/resources/drawable-hdpi/close.png
deleted file mode 100644
index b14612fa0..000000000
--- a/mobile/android/base/resources/drawable-hdpi/close.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/close_edit_mode_dark.png b/mobile/android/base/resources/drawable-hdpi/close_edit_mode_dark.png
deleted file mode 100644
index 1e28f00c5..000000000
--- a/mobile/android/base/resources/drawable-hdpi/close_edit_mode_dark.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/close_edit_mode_light.png b/mobile/android/base/resources/drawable-hdpi/close_edit_mode_light.png
deleted file mode 100644
index 4c82e5696..000000000
--- a/mobile/android/base/resources/drawable-hdpi/close_edit_mode_light.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/color_picker_row_bg.9.png b/mobile/android/base/resources/drawable-hdpi/color_picker_row_bg.9.png
deleted file mode 100644
index 50460696a..000000000
--- a/mobile/android/base/resources/drawable-hdpi/color_picker_row_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/device_desktop.png b/mobile/android/base/resources/drawable-hdpi/device_desktop.png
deleted file mode 100644
index ef5300abd..000000000
--- a/mobile/android/base/resources/drawable-hdpi/device_desktop.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/device_mobile.png b/mobile/android/base/resources/drawable-hdpi/device_mobile.png
deleted file mode 100644
index 6e58c9449..000000000
--- a/mobile/android/base/resources/drawable-hdpi/device_mobile.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/dropshadow.9.png b/mobile/android/base/resources/drawable-hdpi/dropshadow.9.png
deleted file mode 100644
index 1273996c5..000000000
--- a/mobile/android/base/resources/drawable-hdpi/dropshadow.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/favicon_globe.png b/mobile/android/base/resources/drawable-hdpi/favicon_globe.png
deleted file mode 100644
index 235af2720..000000000
--- a/mobile/android/base/resources/drawable-hdpi/favicon_globe.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/find_close.png b/mobile/android/base/resources/drawable-hdpi/find_close.png
deleted file mode 100644
index e98f59e68..000000000
--- a/mobile/android/base/resources/drawable-hdpi/find_close.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/find_next.png b/mobile/android/base/resources/drawable-hdpi/find_next.png
deleted file mode 100644
index 788f45c7e..000000000
--- a/mobile/android/base/resources/drawable-hdpi/find_next.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/find_prev.png b/mobile/android/base/resources/drawable-hdpi/find_prev.png
deleted file mode 100644
index 4c40a1e8a..000000000
--- a/mobile/android/base/resources/drawable-hdpi/find_prev.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/firefox_settings_alert.png b/mobile/android/base/resources/drawable-hdpi/firefox_settings_alert.png
deleted file mode 100644
index 7094c9aad..000000000
--- a/mobile/android/base/resources/drawable-hdpi/firefox_settings_alert.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/flat_icon.png b/mobile/android/base/resources/drawable-hdpi/flat_icon.png
deleted file mode 100644
index d4b946f08..000000000
--- a/mobile/android/base/resources/drawable-hdpi/flat_icon.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/folder_closed.png b/mobile/android/base/resources/drawable-hdpi/folder_closed.png
deleted file mode 100644
index 87adf55bf..000000000
--- a/mobile/android/base/resources/drawable-hdpi/folder_closed.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/globe_light.png b/mobile/android/base/resources/drawable-hdpi/globe_light.png
deleted file mode 100644
index 39098f776..000000000
--- a/mobile/android/base/resources/drawable-hdpi/globe_light.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/grid_icon_bg_activated.9.png b/mobile/android/base/resources/drawable-hdpi/grid_icon_bg_activated.9.png
deleted file mode 100644
index d2ca3fdd4..000000000
--- a/mobile/android/base/resources/drawable-hdpi/grid_icon_bg_activated.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/grid_icon_bg_focused.9.png b/mobile/android/base/resources/drawable-hdpi/grid_icon_bg_focused.9.png
deleted file mode 100644
index 510d297ea..000000000
--- a/mobile/android/base/resources/drawable-hdpi/grid_icon_bg_focused.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/handle_end.png b/mobile/android/base/resources/drawable-hdpi/handle_end.png
deleted file mode 100644
index 9cb9d6fbb..000000000
--- a/mobile/android/base/resources/drawable-hdpi/handle_end.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/handle_middle.png b/mobile/android/base/resources/drawable-hdpi/handle_middle.png
deleted file mode 100644
index 075a30029..000000000
--- a/mobile/android/base/resources/drawable-hdpi/handle_middle.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/handle_start.png b/mobile/android/base/resources/drawable-hdpi/handle_start.png
deleted file mode 100644
index ccbc1767d..000000000
--- a/mobile/android/base/resources/drawable-hdpi/handle_start.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/helper_readerview_bookmark.webp b/mobile/android/base/resources/drawable-hdpi/helper_readerview_bookmark.webp
deleted file mode 100644
index f1f22df5c..000000000
--- a/mobile/android/base/resources/drawable-hdpi/helper_readerview_bookmark.webp
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/home_bg.png b/mobile/android/base/resources/drawable-hdpi/home_bg.png
deleted file mode 100644
index 6cde34825..000000000
--- a/mobile/android/base/resources/drawable-hdpi/home_bg.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/home_group_collapsed.png b/mobile/android/base/resources/drawable-hdpi/home_group_collapsed.png
deleted file mode 100644
index ca2f764f1..000000000
--- a/mobile/android/base/resources/drawable-hdpi/home_group_collapsed.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/home_star.png b/mobile/android/base/resources/drawable-hdpi/home_star.png
deleted file mode 100644
index 29ff5b0f1..000000000
--- a/mobile/android/base/resources/drawable-hdpi/home_star.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/home_tab_menu_strip.9.png b/mobile/android/base/resources/drawable-hdpi/home_tab_menu_strip.9.png
deleted file mode 100644
index 319cc773c..000000000
--- a/mobile/android/base/resources/drawable-hdpi/home_tab_menu_strip.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/homepage_banner_firstrun.png b/mobile/android/base/resources/drawable-hdpi/homepage_banner_firstrun.png
deleted file mode 100644
index 915eac7de..000000000
--- a/mobile/android/base/resources/drawable-hdpi/homepage_banner_firstrun.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/ic_action_settings.png b/mobile/android/base/resources/drawable-hdpi/ic_action_settings.png
deleted file mode 100644
index de96174a3..000000000
--- a/mobile/android/base/resources/drawable-hdpi/ic_action_settings.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/ic_media_pause.png b/mobile/android/base/resources/drawable-hdpi/ic_media_pause.png
deleted file mode 100644
index 3f4f2b316..000000000
--- a/mobile/android/base/resources/drawable-hdpi/ic_media_pause.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/ic_media_play.png b/mobile/android/base/resources/drawable-hdpi/ic_media_play.png
deleted file mode 100644
index 172dfd9d6..000000000
--- a/mobile/android/base/resources/drawable-hdpi/ic_media_play.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/ic_menu_share.png b/mobile/android/base/resources/drawable-hdpi/ic_menu_share.png
deleted file mode 100644
index 66a0ad198..000000000
--- a/mobile/android/base/resources/drawable-hdpi/ic_menu_share.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/ic_status_logo.png b/mobile/android/base/resources/drawable-hdpi/ic_status_logo.png
deleted file mode 100644
index 4cd8e3c07..000000000
--- a/mobile/android/base/resources/drawable-hdpi/ic_status_logo.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/ic_url_bar_tab.png b/mobile/android/base/resources/drawable-hdpi/ic_url_bar_tab.png
deleted file mode 100644
index 97272c7f8..000000000
--- a/mobile/android/base/resources/drawable-hdpi/ic_url_bar_tab.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/ic_widget_new_tab.png b/mobile/android/base/resources/drawable-hdpi/ic_widget_new_tab.png
deleted file mode 100644
index 48a966414..000000000
--- a/mobile/android/base/resources/drawable-hdpi/ic_widget_new_tab.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/ic_widget_search.png b/mobile/android/base/resources/drawable-hdpi/ic_widget_search.png
deleted file mode 100644
index c885787b6..000000000
--- a/mobile/android/base/resources/drawable-hdpi/ic_widget_search.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/icon_bookmarks_empty.png b/mobile/android/base/resources/drawable-hdpi/icon_bookmarks_empty.png
deleted file mode 100644
index a3645a054..000000000
--- a/mobile/android/base/resources/drawable-hdpi/icon_bookmarks_empty.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/icon_home_empty_firefox.png b/mobile/android/base/resources/drawable-hdpi/icon_home_empty_firefox.png
deleted file mode 100644
index 2a490d7ac..000000000
--- a/mobile/android/base/resources/drawable-hdpi/icon_home_empty_firefox.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/icon_key.png b/mobile/android/base/resources/drawable-hdpi/icon_key.png
deleted file mode 100644
index bc742e161..000000000
--- a/mobile/android/base/resources/drawable-hdpi/icon_key.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/icon_most_recent_empty.png b/mobile/android/base/resources/drawable-hdpi/icon_most_recent_empty.png
deleted file mode 100644
index 92b41a224..000000000
--- a/mobile/android/base/resources/drawable-hdpi/icon_most_recent_empty.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/icon_openinapp.png b/mobile/android/base/resources/drawable-hdpi/icon_openinapp.png
deleted file mode 100644
index 0527956da..000000000
--- a/mobile/android/base/resources/drawable-hdpi/icon_openinapp.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/icon_pageaction.png b/mobile/android/base/resources/drawable-hdpi/icon_pageaction.png
deleted file mode 100644
index 2b4b09003..000000000
--- a/mobile/android/base/resources/drawable-hdpi/icon_pageaction.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/icon_remote_tabs_empty.png b/mobile/android/base/resources/drawable-hdpi/icon_remote_tabs_empty.png
deleted file mode 100644
index e520d2db8..000000000
--- a/mobile/android/base/resources/drawable-hdpi/icon_remote_tabs_empty.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/icon_search_empty_firefox.png b/mobile/android/base/resources/drawable-hdpi/icon_search_empty_firefox.png
deleted file mode 100644
index fd271d423..000000000
--- a/mobile/android/base/resources/drawable-hdpi/icon_search_empty_firefox.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/icon_shareplane.png b/mobile/android/base/resources/drawable-hdpi/icon_shareplane.png
deleted file mode 100644
index cdc6e01ae..000000000
--- a/mobile/android/base/resources/drawable-hdpi/icon_shareplane.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/img_check.png b/mobile/android/base/resources/drawable-hdpi/img_check.png
deleted file mode 100644
index bb01e0771..000000000
--- a/mobile/android/base/resources/drawable-hdpi/img_check.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/location.png b/mobile/android/base/resources/drawable-hdpi/location.png
deleted file mode 100644
index 18c54718a..000000000
--- a/mobile/android/base/resources/drawable-hdpi/location.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/lock_disabled.png b/mobile/android/base/resources/drawable-hdpi/lock_disabled.png
deleted file mode 100644
index e7f3eb56c..000000000
--- a/mobile/android/base/resources/drawable-hdpi/lock_disabled.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/lock_inactive.png b/mobile/android/base/resources/drawable-hdpi/lock_inactive.png
deleted file mode 100644
index f1bfe63cc..000000000
--- a/mobile/android/base/resources/drawable-hdpi/lock_inactive.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/lock_secure.png b/mobile/android/base/resources/drawable-hdpi/lock_secure.png
deleted file mode 100644
index c1e95f3d8..000000000
--- a/mobile/android/base/resources/drawable-hdpi/lock_secure.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/media_bar_pause.png b/mobile/android/base/resources/drawable-hdpi/media_bar_pause.png
deleted file mode 100644
index 46e838347..000000000
--- a/mobile/android/base/resources/drawable-hdpi/media_bar_pause.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/media_bar_play.png b/mobile/android/base/resources/drawable-hdpi/media_bar_play.png
deleted file mode 100644
index 36f0797f7..000000000
--- a/mobile/android/base/resources/drawable-hdpi/media_bar_play.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/media_bar_stop.png b/mobile/android/base/resources/drawable-hdpi/media_bar_stop.png
deleted file mode 100644
index db971c20f..000000000
--- a/mobile/android/base/resources/drawable-hdpi/media_bar_stop.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/menu.png b/mobile/android/base/resources/drawable-hdpi/menu.png
deleted file mode 100644
index 0e028bb79..000000000
--- a/mobile/android/base/resources/drawable-hdpi/menu.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/menu_item_check.png b/mobile/android/base/resources/drawable-hdpi/menu_item_check.png
deleted file mode 100644
index e13d6288a..000000000
--- a/mobile/android/base/resources/drawable-hdpi/menu_item_check.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/menu_item_more.png b/mobile/android/base/resources/drawable-hdpi/menu_item_more.png
deleted file mode 100644
index 509e8de55..000000000
--- a/mobile/android/base/resources/drawable-hdpi/menu_item_more.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/menu_item_uncheck.png b/mobile/android/base/resources/drawable-hdpi/menu_item_uncheck.png
deleted file mode 100644
index 2fa5d5251..000000000
--- a/mobile/android/base/resources/drawable-hdpi/menu_item_uncheck.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/network_error.png b/mobile/android/base/resources/drawable-hdpi/network_error.png
deleted file mode 100644
index bdaa961d3..000000000
--- a/mobile/android/base/resources/drawable-hdpi/network_error.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/notification_media.webp b/mobile/android/base/resources/drawable-hdpi/notification_media.webp
deleted file mode 100644
index b4fae6846..000000000
--- a/mobile/android/base/resources/drawable-hdpi/notification_media.webp
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/open_in_browser.png b/mobile/android/base/resources/drawable-hdpi/open_in_browser.png
deleted file mode 100644
index e78d96665..000000000
--- a/mobile/android/base/resources/drawable-hdpi/open_in_browser.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/overlay_bookmark_icon.png b/mobile/android/base/resources/drawable-hdpi/overlay_bookmark_icon.png
deleted file mode 100644
index 6cb555993..000000000
--- a/mobile/android/base/resources/drawable-hdpi/overlay_bookmark_icon.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/overlay_bookmarked_already_icon.png b/mobile/android/base/resources/drawable-hdpi/overlay_bookmarked_already_icon.png
deleted file mode 100644
index c5f91c58d..000000000
--- a/mobile/android/base/resources/drawable-hdpi/overlay_bookmarked_already_icon.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/overlay_check.png b/mobile/android/base/resources/drawable-hdpi/overlay_check.png
deleted file mode 100644
index b69ed0dea..000000000
--- a/mobile/android/base/resources/drawable-hdpi/overlay_check.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/pause.png b/mobile/android/base/resources/drawable-hdpi/pause.png
deleted file mode 100644
index 17266e608..000000000
--- a/mobile/android/base/resources/drawable-hdpi/pause.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/phone.png b/mobile/android/base/resources/drawable-hdpi/phone.png
deleted file mode 100644
index 7fad6f05b..000000000
--- a/mobile/android/base/resources/drawable-hdpi/phone.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/pin.png b/mobile/android/base/resources/drawable-hdpi/pin.png
deleted file mode 100644
index 8111a04dc..000000000
--- a/mobile/android/base/resources/drawable-hdpi/pin.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/play.png b/mobile/android/base/resources/drawable-hdpi/play.png
deleted file mode 100644
index 8e599c16d..000000000
--- a/mobile/android/base/resources/drawable-hdpi/play.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/private_masq.png b/mobile/android/base/resources/drawable-hdpi/private_masq.png
deleted file mode 100644
index 8791f24bc..000000000
--- a/mobile/android/base/resources/drawable-hdpi/private_masq.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/progress.9.png b/mobile/android/base/resources/drawable-hdpi/progress.9.png
deleted file mode 100644
index 5293a77d4..000000000
--- a/mobile/android/base/resources/drawable-hdpi/progress.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/reader.png b/mobile/android/base/resources/drawable-hdpi/reader.png
deleted file mode 100644
index 2ac1b8d46..000000000
--- a/mobile/android/base/resources/drawable-hdpi/reader.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/reader_active.png b/mobile/android/base/resources/drawable-hdpi/reader_active.png
deleted file mode 100644
index 99851ae7c..000000000
--- a/mobile/android/base/resources/drawable-hdpi/reader_active.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/reading_list_folder.png b/mobile/android/base/resources/drawable-hdpi/reading_list_folder.png
deleted file mode 100644
index 052994110..000000000
--- a/mobile/android/base/resources/drawable-hdpi/reading_list_folder.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/search_clear.png b/mobile/android/base/resources/drawable-hdpi/search_clear.png
deleted file mode 100644
index 4dbefcb68..000000000
--- a/mobile/android/base/resources/drawable-hdpi/search_clear.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/search_history.png b/mobile/android/base/resources/drawable-hdpi/search_history.png
deleted file mode 100644
index a552fef20..000000000
--- a/mobile/android/base/resources/drawable-hdpi/search_history.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/search_icon_active.png b/mobile/android/base/resources/drawable-hdpi/search_icon_active.png
deleted file mode 100644
index 65e921896..000000000
--- a/mobile/android/base/resources/drawable-hdpi/search_icon_active.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/search_icon_inactive.png b/mobile/android/base/resources/drawable-hdpi/search_icon_inactive.png
deleted file mode 100644
index b777bc3be..000000000
--- a/mobile/android/base/resources/drawable-hdpi/search_icon_inactive.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/search_launcher.png b/mobile/android/base/resources/drawable-hdpi/search_launcher.png
deleted file mode 100644
index 70c0d7630..000000000
--- a/mobile/android/base/resources/drawable-hdpi/search_launcher.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/search_plus.png b/mobile/android/base/resources/drawable-hdpi/search_plus.png
deleted file mode 100644
index e20d9c6d2..000000000
--- a/mobile/android/base/resources/drawable-hdpi/search_plus.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/settings_notifications.png b/mobile/android/base/resources/drawable-hdpi/settings_notifications.png
deleted file mode 100644
index d2b23cf74..000000000
--- a/mobile/android/base/resources/drawable-hdpi/settings_notifications.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/shareplane.png b/mobile/android/base/resources/drawable-hdpi/shareplane.png
deleted file mode 100644
index 748893b49..000000000
--- a/mobile/android/base/resources/drawable-hdpi/shareplane.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/shield_disabled.png b/mobile/android/base/resources/drawable-hdpi/shield_disabled.png
deleted file mode 100644
index 90f669af2..000000000
--- a/mobile/android/base/resources/drawable-hdpi/shield_disabled.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/shield_enabled.png b/mobile/android/base/resources/drawable-hdpi/shield_enabled.png
deleted file mode 100644
index ff6fb6d80..000000000
--- a/mobile/android/base/resources/drawable-hdpi/shield_enabled.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/status_icon_readercache.png b/mobile/android/base/resources/drawable-hdpi/status_icon_readercache.png
deleted file mode 100644
index 142ac4bdd..000000000
--- a/mobile/android/base/resources/drawable-hdpi/status_icon_readercache.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/suggestedsites_amazon.png b/mobile/android/base/resources/drawable-hdpi/suggestedsites_amazon.png
deleted file mode 100644
index 776cc4934..000000000
--- a/mobile/android/base/resources/drawable-hdpi/suggestedsites_amazon.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/suggestedsites_facebook.png b/mobile/android/base/resources/drawable-hdpi/suggestedsites_facebook.png
deleted file mode 100644
index 340b9a5e4..000000000
--- a/mobile/android/base/resources/drawable-hdpi/suggestedsites_facebook.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/suggestedsites_twitter.png b/mobile/android/base/resources/drawable-hdpi/suggestedsites_twitter.png
deleted file mode 100644
index f3d9fd238..000000000
--- a/mobile/android/base/resources/drawable-hdpi/suggestedsites_twitter.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/suggestedsites_wikipedia.png b/mobile/android/base/resources/drawable-hdpi/suggestedsites_wikipedia.png
deleted file mode 100644
index 9c6efc14a..000000000
--- a/mobile/android/base/resources/drawable-hdpi/suggestedsites_wikipedia.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/suggestedsites_youtube.png b/mobile/android/base/resources/drawable-hdpi/suggestedsites_youtube.png
deleted file mode 100644
index 7e73e6075..000000000
--- a/mobile/android/base/resources/drawable-hdpi/suggestedsites_youtube.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/switch_button_icon.png b/mobile/android/base/resources/drawable-hdpi/switch_button_icon.png
deleted file mode 100644
index 56c4cd050..000000000
--- a/mobile/android/base/resources/drawable-hdpi/switch_button_icon.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/tab_audio_playing.png b/mobile/android/base/resources/drawable-hdpi/tab_audio_playing.png
deleted file mode 100644
index 4635cf5ae..000000000
--- a/mobile/android/base/resources/drawable-hdpi/tab_audio_playing.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/tab_close.png b/mobile/android/base/resources/drawable-hdpi/tab_close.png
deleted file mode 100644
index 7aadf4c45..000000000
--- a/mobile/android/base/resources/drawable-hdpi/tab_close.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/tab_close_active.png b/mobile/android/base/resources/drawable-hdpi/tab_close_active.png
deleted file mode 100644
index 39752bb91..000000000
--- a/mobile/android/base/resources/drawable-hdpi/tab_close_active.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/tab_indicator_background.9.png b/mobile/android/base/resources/drawable-hdpi/tab_indicator_background.9.png
deleted file mode 100644
index e225e8ce9..000000000
--- a/mobile/android/base/resources/drawable-hdpi/tab_indicator_background.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/tab_indicator_divider.9.png b/mobile/android/base/resources/drawable-hdpi/tab_indicator_divider.9.png
deleted file mode 100644
index f1338d803..000000000
--- a/mobile/android/base/resources/drawable-hdpi/tab_indicator_divider.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/tab_indicator_selected.9.png b/mobile/android/base/resources/drawable-hdpi/tab_indicator_selected.9.png
deleted file mode 100644
index 7ebd02f6f..000000000
--- a/mobile/android/base/resources/drawable-hdpi/tab_indicator_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/tab_indicator_selected_focused.9.png b/mobile/android/base/resources/drawable-hdpi/tab_indicator_selected_focused.9.png
deleted file mode 100644
index 272bbaead..000000000
--- a/mobile/android/base/resources/drawable-hdpi/tab_indicator_selected_focused.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/tab_new.png b/mobile/android/base/resources/drawable-hdpi/tab_new.png
deleted file mode 100644
index 8e8557c83..000000000
--- a/mobile/android/base/resources/drawable-hdpi/tab_new.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/tab_preview_masq.png b/mobile/android/base/resources/drawable-hdpi/tab_preview_masq.png
deleted file mode 100644
index 69dd8a093..000000000
--- a/mobile/android/base/resources/drawable-hdpi/tab_preview_masq.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/tabs_count.png b/mobile/android/base/resources/drawable-hdpi/tabs_count.png
deleted file mode 100644
index c8db01a1a..000000000
--- a/mobile/android/base/resources/drawable-hdpi/tabs_count.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/tabs_count_foreground.png b/mobile/android/base/resources/drawable-hdpi/tabs_count_foreground.png
deleted file mode 100644
index c9b9c055b..000000000
--- a/mobile/android/base/resources/drawable-hdpi/tabs_count_foreground.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/tabs_normal.png b/mobile/android/base/resources/drawable-hdpi/tabs_normal.png
deleted file mode 100644
index 49b4ec8bd..000000000
--- a/mobile/android/base/resources/drawable-hdpi/tabs_normal.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/tabs_panel_nav_back.png b/mobile/android/base/resources/drawable-hdpi/tabs_panel_nav_back.png
deleted file mode 100644
index cc74ae4d6..000000000
--- a/mobile/android/base/resources/drawable-hdpi/tabs_panel_nav_back.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/tabs_private.png b/mobile/android/base/resources/drawable-hdpi/tabs_private.png
deleted file mode 100644
index 50ff11309..000000000
--- a/mobile/android/base/resources/drawable-hdpi/tabs_private.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/tip_addsearch.png b/mobile/android/base/resources/drawable-hdpi/tip_addsearch.png
deleted file mode 100644
index 8f02949c5..000000000
--- a/mobile/android/base/resources/drawable-hdpi/tip_addsearch.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/top_site_add.png b/mobile/android/base/resources/drawable-hdpi/top_site_add.png
deleted file mode 100644
index cbfd8e4d4..000000000
--- a/mobile/android/base/resources/drawable-hdpi/top_site_add.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/tracking_protection_toolbar_illustration.png b/mobile/android/base/resources/drawable-hdpi/tracking_protection_toolbar_illustration.png
deleted file mode 100644
index 9984d7f3e..000000000
--- a/mobile/android/base/resources/drawable-hdpi/tracking_protection_toolbar_illustration.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/undo_button_icon.png b/mobile/android/base/resources/drawable-hdpi/undo_button_icon.png
deleted file mode 100644
index 9964ccd0f..000000000
--- a/mobile/android/base/resources/drawable-hdpi/undo_button_icon.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/url_bar_entry_default.9.png b/mobile/android/base/resources/drawable-hdpi/url_bar_entry_default.9.png
deleted file mode 100644
index 1d8cc9055..000000000
--- a/mobile/android/base/resources/drawable-hdpi/url_bar_entry_default.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/url_bar_entry_default_pb.9.png b/mobile/android/base/resources/drawable-hdpi/url_bar_entry_default_pb.9.png
deleted file mode 100644
index 0bd6115fc..000000000
--- a/mobile/android/base/resources/drawable-hdpi/url_bar_entry_default_pb.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/url_bar_entry_pressed.9.png b/mobile/android/base/resources/drawable-hdpi/url_bar_entry_pressed.9.png
deleted file mode 100644
index 91be77476..000000000
--- a/mobile/android/base/resources/drawable-hdpi/url_bar_entry_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/url_bar_entry_pressed_pb.9.png b/mobile/android/base/resources/drawable-hdpi/url_bar_entry_pressed_pb.9.png
deleted file mode 100644
index 6b076e75e..000000000
--- a/mobile/android/base/resources/drawable-hdpi/url_bar_entry_pressed_pb.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/urlbar_stop.png b/mobile/android/base/resources/drawable-hdpi/urlbar_stop.png
deleted file mode 100644
index 3e8ac68fc..000000000
--- a/mobile/android/base/resources/drawable-hdpi/urlbar_stop.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/validation_arrow.png b/mobile/android/base/resources/drawable-hdpi/validation_arrow.png
deleted file mode 100644
index c0563ce94..000000000
--- a/mobile/android/base/resources/drawable-hdpi/validation_arrow.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/validation_arrow_inverted.png b/mobile/android/base/resources/drawable-hdpi/validation_arrow_inverted.png
deleted file mode 100644
index 165d3c2e1..000000000
--- a/mobile/android/base/resources/drawable-hdpi/validation_arrow_inverted.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/validation_bg.9.png b/mobile/android/base/resources/drawable-hdpi/validation_bg.9.png
deleted file mode 100644
index bd83160e7..000000000
--- a/mobile/android/base/resources/drawable-hdpi/validation_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/warning_major.png b/mobile/android/base/resources/drawable-hdpi/warning_major.png
deleted file mode 100644
index 61cd21f97..000000000
--- a/mobile/android/base/resources/drawable-hdpi/warning_major.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/warning_minor.png b/mobile/android/base/resources/drawable-hdpi/warning_minor.png
deleted file mode 100644
index c78b77a13..000000000
--- a/mobile/android/base/resources/drawable-hdpi/warning_minor.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-hdpi/widget_bg.9.png b/mobile/android/base/resources/drawable-hdpi/widget_bg.9.png
deleted file mode 100644
index a5df36d99..000000000
--- a/mobile/android/base/resources/drawable-hdpi/widget_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-hdpi-v11/ic_menu_back.png b/mobile/android/base/resources/drawable-large-hdpi-v11/ic_menu_back.png
deleted file mode 100644
index d18fc9cd5..000000000
--- a/mobile/android/base/resources/drawable-large-hdpi-v11/ic_menu_back.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-hdpi-v11/ic_menu_forward.png b/mobile/android/base/resources/drawable-large-hdpi-v11/ic_menu_forward.png
deleted file mode 100644
index 22a822d67..000000000
--- a/mobile/android/base/resources/drawable-large-hdpi-v11/ic_menu_forward.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-hdpi-v11/ic_menu_reload.png b/mobile/android/base/resources/drawable-large-hdpi-v11/ic_menu_reload.png
deleted file mode 100644
index 7cc5d918b..000000000
--- a/mobile/android/base/resources/drawable-large-hdpi-v11/ic_menu_reload.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-hdpi-v11/tabs_count.png b/mobile/android/base/resources/drawable-large-hdpi-v11/tabs_count.png
deleted file mode 100644
index 7db22b0ca..000000000
--- a/mobile/android/base/resources/drawable-large-hdpi-v11/tabs_count.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-hdpi-v11/tabs_count_foreground.png b/mobile/android/base/resources/drawable-large-hdpi-v11/tabs_count_foreground.png
deleted file mode 100644
index 3875c80cc..000000000
--- a/mobile/android/base/resources/drawable-large-hdpi-v11/tabs_count_foreground.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-hdpi-v11/toolbar_favicon_default.png b/mobile/android/base/resources/drawable-large-hdpi-v11/toolbar_favicon_default.png
deleted file mode 100644
index 9daea10b7..000000000
--- a/mobile/android/base/resources/drawable-large-hdpi-v11/toolbar_favicon_default.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-hdpi-v11/url_bar_entry_default.9.png b/mobile/android/base/resources/drawable-large-hdpi-v11/url_bar_entry_default.9.png
deleted file mode 100644
index a2e973ea0..000000000
--- a/mobile/android/base/resources/drawable-large-hdpi-v11/url_bar_entry_default.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-hdpi-v11/url_bar_entry_default_pb.9.png b/mobile/android/base/resources/drawable-large-hdpi-v11/url_bar_entry_default_pb.9.png
deleted file mode 100644
index 0e91b33ab..000000000
--- a/mobile/android/base/resources/drawable-large-hdpi-v11/url_bar_entry_default_pb.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-hdpi-v11/url_bar_entry_pressed.9.png b/mobile/android/base/resources/drawable-large-hdpi-v11/url_bar_entry_pressed.9.png
deleted file mode 100644
index a55ad0b9c..000000000
--- a/mobile/android/base/resources/drawable-large-hdpi-v11/url_bar_entry_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-hdpi-v11/url_bar_entry_pressed_pb.9.png b/mobile/android/base/resources/drawable-large-hdpi-v11/url_bar_entry_pressed_pb.9.png
deleted file mode 100644
index ac6476de7..000000000
--- a/mobile/android/base/resources/drawable-large-hdpi-v11/url_bar_entry_pressed_pb.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-v11/browser_toolbar_action_bar_button.xml b/mobile/android/base/resources/drawable-large-v11/browser_toolbar_action_bar_button.xml
deleted file mode 100644
index e1654c1cf..000000000
--- a/mobile/android/base/resources/drawable-large-v11/browser_toolbar_action_bar_button.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <item gecko:state_private="true"
- android:state_pressed="true"
- android:state_enabled="true">
-
- <inset android:insetTop="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
- android:insetBottom="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
- android:insetLeft="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal"
- android:insetRight="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal">
- <shape android:shape="rectangle">
- <solid android:color="@color/text_and_tabs_tray_grey"/>
- <corners android:radius="@dimen/standard_corner_radius"/>
- </shape>
- </inset>
-
- </item>
-
- <item gecko:state_private="true"
- android:state_focused="true"
- android:state_pressed="false">
-
- <inset android:insetTop="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
- android:insetBottom="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
- android:insetLeft="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal"
- android:insetRight="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal">
- <shape android:shape="rectangle">
- <solid android:color="@color/placeholder_active_grey"/>
- <corners android:radius="@dimen/standard_corner_radius"/>
- </shape>
- </inset>
-
- </item>
-
- <item android:state_pressed="true"
- android:state_enabled="true">
-
- <inset android:insetTop="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
- android:insetBottom="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
- android:insetLeft="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal"
- android:insetRight="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal">
- <shape android:shape="rectangle">
- <solid android:color="@color/toolbar_grey_pressed"/>
- <corners android:radius="@dimen/standard_corner_radius"/>
- </shape>
- </inset>
-
- </item>
-
- <item android:state_focused="true"
- android:state_pressed="false">
-
- <inset android:insetTop="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
- android:insetBottom="@dimen/tablet_browser_toolbar_menu_item_inset_vertical"
- android:insetLeft="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal"
- android:insetRight="@dimen/tablet_browser_toolbar_menu_item_inset_horizontal">
- <shape android:shape="rectangle">
- <solid android:color="@color/tablet_highlight_focused"/>
- <corners android:radius="@dimen/standard_corner_radius"/>
- </shape>
- </inset>
-
- </item>
-
- <item>
- <shape android:shape="rectangle">
- <solid android:color="@android:color/transparent"/>
- </shape>
- </item>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable-large-v11/url_bar_nav_button.xml b/mobile/android/base/resources/drawable-large-v11/url_bar_nav_button.xml
deleted file mode 100644
index 0a3417927..000000000
--- a/mobile/android/base/resources/drawable-large-v11/url_bar_nav_button.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <!-- private pressed state -->
- <item gecko:state_private="true"
- android:state_pressed="true"
- android:drawable="@color/text_and_tabs_tray_grey"/>
-
- <!-- focused state -->
- <item gecko:state_private="true"
- android:state_focused="true"
- android:state_pressed="false"
- android:drawable="@color/placeholder_active_grey"/>
-
- <!-- pressed state -->
- <item android:state_pressed="true"
- android:drawable="@color/toolbar_grey_pressed"/>
-
- <!-- focused state -->
- <item android:state_focused="true"
- android:state_pressed="false"
- android:drawable="@color/tablet_highlight_focused"/>
-
- <!-- private browsing mode -->
- <item gecko:state_private="true"
- android:drawable="@color/tabs_tray_grey_pressed"/>
-
- <!-- normal mode -->
- <item android:drawable="@color/toolbar_grey"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable-large-xhdpi-v11/ic_menu_back.png b/mobile/android/base/resources/drawable-large-xhdpi-v11/ic_menu_back.png
deleted file mode 100644
index 78a33ffab..000000000
--- a/mobile/android/base/resources/drawable-large-xhdpi-v11/ic_menu_back.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xhdpi-v11/ic_menu_forward.png b/mobile/android/base/resources/drawable-large-xhdpi-v11/ic_menu_forward.png
deleted file mode 100644
index 7a284903f..000000000
--- a/mobile/android/base/resources/drawable-large-xhdpi-v11/ic_menu_forward.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xhdpi-v11/ic_menu_reload.png b/mobile/android/base/resources/drawable-large-xhdpi-v11/ic_menu_reload.png
deleted file mode 100644
index a9c7b3f62..000000000
--- a/mobile/android/base/resources/drawable-large-xhdpi-v11/ic_menu_reload.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xhdpi-v11/tabs_count.png b/mobile/android/base/resources/drawable-large-xhdpi-v11/tabs_count.png
deleted file mode 100644
index 16e41c0ce..000000000
--- a/mobile/android/base/resources/drawable-large-xhdpi-v11/tabs_count.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xhdpi-v11/tabs_count_foreground.png b/mobile/android/base/resources/drawable-large-xhdpi-v11/tabs_count_foreground.png
deleted file mode 100644
index 85dc05e4a..000000000
--- a/mobile/android/base/resources/drawable-large-xhdpi-v11/tabs_count_foreground.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xhdpi-v11/toolbar_favicon_default.png b/mobile/android/base/resources/drawable-large-xhdpi-v11/toolbar_favicon_default.png
deleted file mode 100644
index 1fb9f7386..000000000
--- a/mobile/android/base/resources/drawable-large-xhdpi-v11/toolbar_favicon_default.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xhdpi-v11/url_bar_entry_default.9.png b/mobile/android/base/resources/drawable-large-xhdpi-v11/url_bar_entry_default.9.png
deleted file mode 100644
index 2c8c0d80f..000000000
--- a/mobile/android/base/resources/drawable-large-xhdpi-v11/url_bar_entry_default.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xhdpi-v11/url_bar_entry_default_pb.9.png b/mobile/android/base/resources/drawable-large-xhdpi-v11/url_bar_entry_default_pb.9.png
deleted file mode 100644
index 670104ed3..000000000
--- a/mobile/android/base/resources/drawable-large-xhdpi-v11/url_bar_entry_default_pb.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xhdpi-v11/url_bar_entry_pressed.9.png b/mobile/android/base/resources/drawable-large-xhdpi-v11/url_bar_entry_pressed.9.png
deleted file mode 100644
index 26dc0221b..000000000
--- a/mobile/android/base/resources/drawable-large-xhdpi-v11/url_bar_entry_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xhdpi-v11/url_bar_entry_pressed_pb.9.png b/mobile/android/base/resources/drawable-large-xhdpi-v11/url_bar_entry_pressed_pb.9.png
deleted file mode 100644
index 224738af6..000000000
--- a/mobile/android/base/resources/drawable-large-xhdpi-v11/url_bar_entry_pressed_pb.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xxhdpi-v11/ic_menu_back.png b/mobile/android/base/resources/drawable-large-xxhdpi-v11/ic_menu_back.png
deleted file mode 100644
index 33b45b31d..000000000
--- a/mobile/android/base/resources/drawable-large-xxhdpi-v11/ic_menu_back.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xxhdpi-v11/ic_menu_forward.png b/mobile/android/base/resources/drawable-large-xxhdpi-v11/ic_menu_forward.png
deleted file mode 100644
index ac5166cd8..000000000
--- a/mobile/android/base/resources/drawable-large-xxhdpi-v11/ic_menu_forward.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xxhdpi-v11/ic_menu_reload.png b/mobile/android/base/resources/drawable-large-xxhdpi-v11/ic_menu_reload.png
deleted file mode 100644
index d582128a1..000000000
--- a/mobile/android/base/resources/drawable-large-xxhdpi-v11/ic_menu_reload.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xxhdpi-v11/tabs_count.png b/mobile/android/base/resources/drawable-large-xxhdpi-v11/tabs_count.png
deleted file mode 100644
index af9ee4d3a..000000000
--- a/mobile/android/base/resources/drawable-large-xxhdpi-v11/tabs_count.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xxhdpi-v11/tabs_count_foreground.png b/mobile/android/base/resources/drawable-large-xxhdpi-v11/tabs_count_foreground.png
deleted file mode 100644
index fa0512035..000000000
--- a/mobile/android/base/resources/drawable-large-xxhdpi-v11/tabs_count_foreground.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xxhdpi-v11/toolbar_favicon_default.png b/mobile/android/base/resources/drawable-large-xxhdpi-v11/toolbar_favicon_default.png
deleted file mode 100644
index bbccd51ef..000000000
--- a/mobile/android/base/resources/drawable-large-xxhdpi-v11/toolbar_favicon_default.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xxhdpi-v11/url_bar_entry_default.9.png b/mobile/android/base/resources/drawable-large-xxhdpi-v11/url_bar_entry_default.9.png
deleted file mode 100644
index 1099a2d80..000000000
--- a/mobile/android/base/resources/drawable-large-xxhdpi-v11/url_bar_entry_default.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xxhdpi-v11/url_bar_entry_default_pb.9.png b/mobile/android/base/resources/drawable-large-xxhdpi-v11/url_bar_entry_default_pb.9.png
deleted file mode 100644
index 3aba71bae..000000000
--- a/mobile/android/base/resources/drawable-large-xxhdpi-v11/url_bar_entry_default_pb.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xxhdpi-v11/url_bar_entry_pressed.9.png b/mobile/android/base/resources/drawable-large-xxhdpi-v11/url_bar_entry_pressed.9.png
deleted file mode 100644
index 779f2721f..000000000
--- a/mobile/android/base/resources/drawable-large-xxhdpi-v11/url_bar_entry_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-large-xxhdpi-v11/url_bar_entry_pressed_pb.9.png b/mobile/android/base/resources/drawable-large-xxhdpi-v11/url_bar_entry_pressed_pb.9.png
deleted file mode 100644
index e15f58ab2..000000000
--- a/mobile/android/base/resources/drawable-large-xxhdpi-v11/url_bar_entry_pressed_pb.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-nodpi/cloud.png b/mobile/android/base/resources/drawable-nodpi/cloud.png
deleted file mode 100644
index 22261d812..000000000
--- a/mobile/android/base/resources/drawable-nodpi/cloud.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-nodpi/firstrun_account.png b/mobile/android/base/resources/drawable-nodpi/firstrun_account.png
deleted file mode 100644
index 38c77afaa..000000000
--- a/mobile/android/base/resources/drawable-nodpi/firstrun_account.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-nodpi/firstrun_bookmarks.png b/mobile/android/base/resources/drawable-nodpi/firstrun_bookmarks.png
deleted file mode 100644
index 28dd68f52..000000000
--- a/mobile/android/base/resources/drawable-nodpi/firstrun_bookmarks.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-nodpi/firstrun_data_off.png b/mobile/android/base/resources/drawable-nodpi/firstrun_data_off.png
deleted file mode 100644
index 5985b61c5..000000000
--- a/mobile/android/base/resources/drawable-nodpi/firstrun_data_off.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-nodpi/firstrun_data_on.png b/mobile/android/base/resources/drawable-nodpi/firstrun_data_on.png
deleted file mode 100644
index 3dc037a48..000000000
--- a/mobile/android/base/resources/drawable-nodpi/firstrun_data_on.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-nodpi/firstrun_readerview.png b/mobile/android/base/resources/drawable-nodpi/firstrun_readerview.png
deleted file mode 100644
index c611f08c9..000000000
--- a/mobile/android/base/resources/drawable-nodpi/firstrun_readerview.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-nodpi/firstrun_signin.png b/mobile/android/base/resources/drawable-nodpi/firstrun_signin.png
deleted file mode 100644
index 38d1c8c86..000000000
--- a/mobile/android/base/resources/drawable-nodpi/firstrun_signin.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-nodpi/firstrun_sync.png b/mobile/android/base/resources/drawable-nodpi/firstrun_sync.png
deleted file mode 100644
index 9f2f3cbc8..000000000
--- a/mobile/android/base/resources/drawable-nodpi/firstrun_sync.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-nodpi/firstrun_tabqueue_off.png b/mobile/android/base/resources/drawable-nodpi/firstrun_tabqueue_off.png
deleted file mode 100644
index 9e986685a..000000000
--- a/mobile/android/base/resources/drawable-nodpi/firstrun_tabqueue_off.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-nodpi/firstrun_tabqueue_on.png b/mobile/android/base/resources/drawable-nodpi/firstrun_tabqueue_on.png
deleted file mode 100644
index 5439f7a5c..000000000
--- a/mobile/android/base/resources/drawable-nodpi/firstrun_tabqueue_on.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-nodpi/firstrun_urlbar.png b/mobile/android/base/resources/drawable-nodpi/firstrun_urlbar.png
deleted file mode 100644
index 779cdd972..000000000
--- a/mobile/android/base/resources/drawable-nodpi/firstrun_urlbar.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-nodpi/icon_recent.png b/mobile/android/base/resources/drawable-nodpi/icon_recent.png
deleted file mode 100755
index d93658f08..000000000
--- a/mobile/android/base/resources/drawable-nodpi/icon_recent.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-v12/toast_button_background.xml b/mobile/android/base/resources/drawable-v12/toast_button_background.xml
deleted file mode 100644
index 856d070c2..000000000
--- a/mobile/android/base/resources/drawable-v12/toast_button_background.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true">
- <shape android:shape="rectangle">
- <solid android:color="@color/toast_button_pressed" />
- <corners
- android:topRightRadius="@dimen/toast_button_corner_radius"
- android:bottomRightRadius="@dimen/toast_button_corner_radius"
- android:topLeftRadius="0dp"
- android:bottomLeftRadius="0dp" />
- </shape>
- </item>
- <item>
- <shape android:shape="rectangle">
- <solid android:color="@color/toast_button_background" />
- <corners
- android:topRightRadius="@dimen/toast_button_corner_radius"
- android:bottomRightRadius="@dimen/toast_button_corner_radius"
- android:topLeftRadius="0dp"
- android:bottomLeftRadius="0dp" />
- </shape>
- </item>
-</selector>
diff --git a/mobile/android/base/resources/drawable-v21/logo.xml b/mobile/android/base/resources/drawable-v21/logo.xml
deleted file mode 100644
index 568cbec00..000000000
--- a/mobile/android/base/resources/drawable-v21/logo.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- The action bar scales the application icon to be too large (bug 1132751)
- so add some padding to prevent it from scaling so much. -->
-<inset
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/icon"
- android:insetTop="6dp"
- android:insetBottom="6dp"
- android:insetLeft="6dp"
- android:insetRight="6dp"
- />
diff --git a/mobile/android/base/resources/drawable-xhdpi-v11/alert_camera.png b/mobile/android/base/resources/drawable-xhdpi-v11/alert_camera.png
deleted file mode 100644
index ead824430..000000000
--- a/mobile/android/base/resources/drawable-xhdpi-v11/alert_camera.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi-v11/alert_download.png b/mobile/android/base/resources/drawable-xhdpi-v11/alert_download.png
deleted file mode 100644
index 1b8f59e56..000000000
--- a/mobile/android/base/resources/drawable-xhdpi-v11/alert_download.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi-v11/alert_guest.png b/mobile/android/base/resources/drawable-xhdpi-v11/alert_guest.png
deleted file mode 100644
index 1a17f03be..000000000
--- a/mobile/android/base/resources/drawable-xhdpi-v11/alert_guest.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi-v11/alert_mic.png b/mobile/android/base/resources/drawable-xhdpi-v11/alert_mic.png
deleted file mode 100644
index 79489919e..000000000
--- a/mobile/android/base/resources/drawable-xhdpi-v11/alert_mic.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi-v11/alert_mic_camera.png b/mobile/android/base/resources/drawable-xhdpi-v11/alert_mic_camera.png
deleted file mode 100644
index 26ba6520b..000000000
--- a/mobile/android/base/resources/drawable-xhdpi-v11/alert_mic_camera.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi-v11/ic_menu_back.png b/mobile/android/base/resources/drawable-xhdpi-v11/ic_menu_back.png
deleted file mode 100644
index c26cdd753..000000000
--- a/mobile/android/base/resources/drawable-xhdpi-v11/ic_menu_back.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi-v11/ic_menu_bookmark_add.png b/mobile/android/base/resources/drawable-xhdpi-v11/ic_menu_bookmark_add.png
deleted file mode 100644
index 8084baf98..000000000
--- a/mobile/android/base/resources/drawable-xhdpi-v11/ic_menu_bookmark_add.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi-v11/ic_menu_forward.png b/mobile/android/base/resources/drawable-xhdpi-v11/ic_menu_forward.png
deleted file mode 100644
index 2d6ba52ac..000000000
--- a/mobile/android/base/resources/drawable-xhdpi-v11/ic_menu_forward.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi-v11/ic_menu_reload.png b/mobile/android/base/resources/drawable-xhdpi-v11/ic_menu_reload.png
deleted file mode 100644
index d456630ee..000000000
--- a/mobile/android/base/resources/drawable-xhdpi-v11/ic_menu_reload.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi-v11/ic_status_logo.png b/mobile/android/base/resources/drawable-xhdpi-v11/ic_status_logo.png
deleted file mode 100644
index c9045fd0e..000000000
--- a/mobile/android/base/resources/drawable-xhdpi-v11/ic_status_logo.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi-v11/star_blue.png b/mobile/android/base/resources/drawable-xhdpi-v11/star_blue.png
deleted file mode 100644
index 56c9367c0..000000000
--- a/mobile/android/base/resources/drawable-xhdpi-v11/star_blue.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/ab_add_search_engine.png b/mobile/android/base/resources/drawable-xhdpi/ab_add_search_engine.png
deleted file mode 100644
index 2ba5dc0b7..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/ab_add_search_engine.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/ab_copy.png b/mobile/android/base/resources/drawable-xhdpi/ab_copy.png
deleted file mode 100644
index 131b4bb1b..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/ab_copy.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/ab_cut.png b/mobile/android/base/resources/drawable-xhdpi/ab_cut.png
deleted file mode 100644
index 0805bbbef..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/ab_cut.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/ab_done.png b/mobile/android/base/resources/drawable-xhdpi/ab_done.png
deleted file mode 100644
index 0639b034e..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/ab_done.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/ab_mic.png b/mobile/android/base/resources/drawable-xhdpi/ab_mic.png
deleted file mode 100644
index 79c459bdb..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/ab_mic.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/ab_paste.png b/mobile/android/base/resources/drawable-xhdpi/ab_paste.png
deleted file mode 100644
index 744320f16..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/ab_paste.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/ab_qrcode.png b/mobile/android/base/resources/drawable-xhdpi/ab_qrcode.png
deleted file mode 100644
index d0d4c3a0c..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/ab_qrcode.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/ab_search.png b/mobile/android/base/resources/drawable-xhdpi/ab_search.png
deleted file mode 100644
index 67063dd6c..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/ab_search.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/ab_select_all.png b/mobile/android/base/resources/drawable-xhdpi/ab_select_all.png
deleted file mode 100644
index 028299a83..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/ab_select_all.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/alert_camera.png b/mobile/android/base/resources/drawable-xhdpi/alert_camera.png
deleted file mode 100644
index bfc736c55..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/alert_camera.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/alert_download.png b/mobile/android/base/resources/drawable-xhdpi/alert_download.png
deleted file mode 100644
index 283ba0867..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/alert_download.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/alert_download_animation_1.png b/mobile/android/base/resources/drawable-xhdpi/alert_download_animation_1.png
deleted file mode 100644
index 22d6288ea..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/alert_download_animation_1.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/alert_download_animation_2.png b/mobile/android/base/resources/drawable-xhdpi/alert_download_animation_2.png
deleted file mode 100644
index dc402e9b8..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/alert_download_animation_2.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/alert_download_animation_3.png b/mobile/android/base/resources/drawable-xhdpi/alert_download_animation_3.png
deleted file mode 100644
index 719bcbdab..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/alert_download_animation_3.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/alert_download_animation_4.png b/mobile/android/base/resources/drawable-xhdpi/alert_download_animation_4.png
deleted file mode 100644
index 4319d0039..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/alert_download_animation_4.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/alert_download_animation_5.png b/mobile/android/base/resources/drawable-xhdpi/alert_download_animation_5.png
deleted file mode 100644
index f8d1a15d6..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/alert_download_animation_5.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/alert_download_animation_6.png b/mobile/android/base/resources/drawable-xhdpi/alert_download_animation_6.png
deleted file mode 100644
index dffe36338..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/alert_download_animation_6.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/alert_guest.png b/mobile/android/base/resources/drawable-xhdpi/alert_guest.png
deleted file mode 100644
index f48ae407c..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/alert_guest.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/alert_mic.png b/mobile/android/base/resources/drawable-xhdpi/alert_mic.png
deleted file mode 100644
index 527d42c42..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/alert_mic.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/alert_mic_camera.png b/mobile/android/base/resources/drawable-xhdpi/alert_mic_camera.png
deleted file mode 100644
index 1e2c29861..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/alert_mic_camera.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/arrow_up.png b/mobile/android/base/resources/drawable-xhdpi/arrow_up.png
deleted file mode 100644
index ea344e7a4..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/arrow_up.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/blank.png b/mobile/android/base/resources/drawable-xhdpi/blank.png
deleted file mode 100644
index c6efe7e8e..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/blank.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/casting.png b/mobile/android/base/resources/drawable-xhdpi/casting.png
deleted file mode 100644
index a20840234..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/casting.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/casting_active.png b/mobile/android/base/resources/drawable-xhdpi/casting_active.png
deleted file mode 100644
index 0f5d93c73..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/casting_active.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/close.png b/mobile/android/base/resources/drawable-xhdpi/close.png
deleted file mode 100644
index e23ea4666..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/close.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/close_edit_mode_dark.png b/mobile/android/base/resources/drawable-xhdpi/close_edit_mode_dark.png
deleted file mode 100644
index 93bee42da..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/close_edit_mode_dark.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/close_edit_mode_light.png b/mobile/android/base/resources/drawable-xhdpi/close_edit_mode_light.png
deleted file mode 100644
index 11e9d19ce..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/close_edit_mode_light.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/color_picker_row_bg.9.png b/mobile/android/base/resources/drawable-xhdpi/color_picker_row_bg.9.png
deleted file mode 100644
index 106c4c0b7..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/color_picker_row_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/device_desktop.png b/mobile/android/base/resources/drawable-xhdpi/device_desktop.png
deleted file mode 100644
index 5b0abd02d..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/device_desktop.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/device_mobile.png b/mobile/android/base/resources/drawable-xhdpi/device_mobile.png
deleted file mode 100644
index ee665d2b6..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/device_mobile.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/dropshadow.9.png b/mobile/android/base/resources/drawable-xhdpi/dropshadow.9.png
deleted file mode 100644
index 5f346ab70..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/dropshadow.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/favicon_globe.png b/mobile/android/base/resources/drawable-xhdpi/favicon_globe.png
deleted file mode 100644
index e4d594911..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/favicon_globe.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/find_close.png b/mobile/android/base/resources/drawable-xhdpi/find_close.png
deleted file mode 100644
index a76c69e09..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/find_close.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/find_next.png b/mobile/android/base/resources/drawable-xhdpi/find_next.png
deleted file mode 100644
index 3a7dda2d9..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/find_next.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/find_prev.png b/mobile/android/base/resources/drawable-xhdpi/find_prev.png
deleted file mode 100644
index 9e3c8f2da..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/find_prev.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/firefox_settings_alert.png b/mobile/android/base/resources/drawable-xhdpi/firefox_settings_alert.png
deleted file mode 100644
index b62d67c88..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/firefox_settings_alert.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/flat_icon.png b/mobile/android/base/resources/drawable-xhdpi/flat_icon.png
deleted file mode 100644
index bc9569f47..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/flat_icon.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/folder_closed.png b/mobile/android/base/resources/drawable-xhdpi/folder_closed.png
deleted file mode 100644
index 63bb529a3..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/folder_closed.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/globe_light.png b/mobile/android/base/resources/drawable-xhdpi/globe_light.png
deleted file mode 100644
index 1bb177089..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/globe_light.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/grid_icon_bg_activated.9.png b/mobile/android/base/resources/drawable-xhdpi/grid_icon_bg_activated.9.png
deleted file mode 100644
index ef25e2076..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/grid_icon_bg_activated.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/grid_icon_bg_focused.9.png b/mobile/android/base/resources/drawable-xhdpi/grid_icon_bg_focused.9.png
deleted file mode 100644
index 27660f0d3..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/grid_icon_bg_focused.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/handle_end.png b/mobile/android/base/resources/drawable-xhdpi/handle_end.png
deleted file mode 100644
index a637c3746..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/handle_end.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/handle_middle.png b/mobile/android/base/resources/drawable-xhdpi/handle_middle.png
deleted file mode 100644
index 95b85bc23..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/handle_middle.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/handle_start.png b/mobile/android/base/resources/drawable-xhdpi/handle_start.png
deleted file mode 100644
index 05532b239..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/handle_start.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/helper_readerview_bookmark.webp b/mobile/android/base/resources/drawable-xhdpi/helper_readerview_bookmark.webp
deleted file mode 100644
index c7ef9cc7d..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/helper_readerview_bookmark.webp
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/home_group_collapsed.png b/mobile/android/base/resources/drawable-xhdpi/home_group_collapsed.png
deleted file mode 100644
index 80aea0ddc..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/home_group_collapsed.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/home_tab_menu_strip.9.png b/mobile/android/base/resources/drawable-xhdpi/home_tab_menu_strip.9.png
deleted file mode 100644
index b9847a1a3..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/home_tab_menu_strip.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/homepage_banner_firstrun.png b/mobile/android/base/resources/drawable-xhdpi/homepage_banner_firstrun.png
deleted file mode 100644
index b53bcb944..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/homepage_banner_firstrun.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/ic_action_settings.png b/mobile/android/base/resources/drawable-xhdpi/ic_action_settings.png
deleted file mode 100644
index c76a98b64..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/ic_action_settings.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/ic_media_pause.png b/mobile/android/base/resources/drawable-xhdpi/ic_media_pause.png
deleted file mode 100644
index 6174480ea..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/ic_media_pause.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/ic_media_play.png b/mobile/android/base/resources/drawable-xhdpi/ic_media_play.png
deleted file mode 100644
index 601f4293b..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/ic_media_play.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/ic_menu_share.png b/mobile/android/base/resources/drawable-xhdpi/ic_menu_share.png
deleted file mode 100644
index 31cca8e15..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/ic_menu_share.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/ic_status_logo.png b/mobile/android/base/resources/drawable-xhdpi/ic_status_logo.png
deleted file mode 100644
index 915d7510a..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/ic_status_logo.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/ic_url_bar_tab.png b/mobile/android/base/resources/drawable-xhdpi/ic_url_bar_tab.png
deleted file mode 100644
index be2b74138..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/ic_url_bar_tab.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/ic_widget_new_tab.png b/mobile/android/base/resources/drawable-xhdpi/ic_widget_new_tab.png
deleted file mode 100644
index 8a5cbae01..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/ic_widget_new_tab.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/ic_widget_search.png b/mobile/android/base/resources/drawable-xhdpi/ic_widget_search.png
deleted file mode 100644
index e846f008d..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/ic_widget_search.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/icon_bookmarks_empty.png b/mobile/android/base/resources/drawable-xhdpi/icon_bookmarks_empty.png
deleted file mode 100644
index 168e76e92..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/icon_bookmarks_empty.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/icon_home_empty_firefox.png b/mobile/android/base/resources/drawable-xhdpi/icon_home_empty_firefox.png
deleted file mode 100644
index 92b966071..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/icon_home_empty_firefox.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/icon_key.png b/mobile/android/base/resources/drawable-xhdpi/icon_key.png
deleted file mode 100644
index 747733125..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/icon_key.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/icon_most_recent_empty.png b/mobile/android/base/resources/drawable-xhdpi/icon_most_recent_empty.png
deleted file mode 100644
index 710fded09..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/icon_most_recent_empty.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/icon_openinapp.png b/mobile/android/base/resources/drawable-xhdpi/icon_openinapp.png
deleted file mode 100644
index 4caa44ea8..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/icon_openinapp.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/icon_pageaction.png b/mobile/android/base/resources/drawable-xhdpi/icon_pageaction.png
deleted file mode 100644
index 2dd6b052b..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/icon_pageaction.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/icon_remote_tabs_empty.png b/mobile/android/base/resources/drawable-xhdpi/icon_remote_tabs_empty.png
deleted file mode 100644
index 04c0f0d73..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/icon_remote_tabs_empty.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/icon_search_empty_firefox.png b/mobile/android/base/resources/drawable-xhdpi/icon_search_empty_firefox.png
deleted file mode 100644
index 8332271d8..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/icon_search_empty_firefox.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/icon_shareplane.png b/mobile/android/base/resources/drawable-xhdpi/icon_shareplane.png
deleted file mode 100644
index f2dfe2d5a..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/icon_shareplane.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/img_check.png b/mobile/android/base/resources/drawable-xhdpi/img_check.png
deleted file mode 100644
index 986143b24..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/img_check.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/location.png b/mobile/android/base/resources/drawable-xhdpi/location.png
deleted file mode 100644
index 8a6b546bd..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/location.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/lock_disabled.png b/mobile/android/base/resources/drawable-xhdpi/lock_disabled.png
deleted file mode 100644
index 9a879161b..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/lock_disabled.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/lock_inactive.png b/mobile/android/base/resources/drawable-xhdpi/lock_inactive.png
deleted file mode 100644
index 84611eb42..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/lock_inactive.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/lock_secure.png b/mobile/android/base/resources/drawable-xhdpi/lock_secure.png
deleted file mode 100644
index 75a43cd30..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/lock_secure.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/media_bar_pause.png b/mobile/android/base/resources/drawable-xhdpi/media_bar_pause.png
deleted file mode 100644
index e19fbd864..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/media_bar_pause.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/media_bar_play.png b/mobile/android/base/resources/drawable-xhdpi/media_bar_play.png
deleted file mode 100644
index 6e1b7d214..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/media_bar_play.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/media_bar_stop.png b/mobile/android/base/resources/drawable-xhdpi/media_bar_stop.png
deleted file mode 100644
index 6b63c3ea3..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/media_bar_stop.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/menu.png b/mobile/android/base/resources/drawable-xhdpi/menu.png
deleted file mode 100644
index 01af3bb04..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/menu.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/menu_item_check.png b/mobile/android/base/resources/drawable-xhdpi/menu_item_check.png
deleted file mode 100644
index 9943ead84..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/menu_item_check.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/menu_item_more.png b/mobile/android/base/resources/drawable-xhdpi/menu_item_more.png
deleted file mode 100644
index e6f71ec5b..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/menu_item_more.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/menu_item_uncheck.png b/mobile/android/base/resources/drawable-xhdpi/menu_item_uncheck.png
deleted file mode 100644
index 01c610fbf..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/menu_item_uncheck.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/network_error.png b/mobile/android/base/resources/drawable-xhdpi/network_error.png
deleted file mode 100644
index c5613865c..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/network_error.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/notification_media.webp b/mobile/android/base/resources/drawable-xhdpi/notification_media.webp
deleted file mode 100644
index 460fe7a12..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/notification_media.webp
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/open_in_browser.png b/mobile/android/base/resources/drawable-xhdpi/open_in_browser.png
deleted file mode 100644
index c03c0207b..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/open_in_browser.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/overlay_bookmark_icon.png b/mobile/android/base/resources/drawable-xhdpi/overlay_bookmark_icon.png
deleted file mode 100644
index db438939c..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/overlay_bookmark_icon.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/overlay_bookmarked_already_icon.png b/mobile/android/base/resources/drawable-xhdpi/overlay_bookmarked_already_icon.png
deleted file mode 100644
index 5995d2eb1..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/overlay_bookmarked_already_icon.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/overlay_check.png b/mobile/android/base/resources/drawable-xhdpi/overlay_check.png
deleted file mode 100644
index db17eda8d..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/overlay_check.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/pause.png b/mobile/android/base/resources/drawable-xhdpi/pause.png
deleted file mode 100644
index 6aae6c5eb..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/pause.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/phone.png b/mobile/android/base/resources/drawable-xhdpi/phone.png
deleted file mode 100644
index 19308f870..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/phone.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/pin.png b/mobile/android/base/resources/drawable-xhdpi/pin.png
deleted file mode 100644
index 644e0043c..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/pin.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/play.png b/mobile/android/base/resources/drawable-xhdpi/play.png
deleted file mode 100644
index cc5c7b9e4..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/play.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/private_masq.png b/mobile/android/base/resources/drawable-xhdpi/private_masq.png
deleted file mode 100644
index 5ea5f5beb..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/private_masq.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/progress.9.png b/mobile/android/base/resources/drawable-xhdpi/progress.9.png
deleted file mode 100644
index 8682ea26e..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/progress.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/push_notification.png b/mobile/android/base/resources/drawable-xhdpi/push_notification.png
deleted file mode 100644
index 77e0be689..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/push_notification.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/reader.png b/mobile/android/base/resources/drawable-xhdpi/reader.png
deleted file mode 100644
index b202314d4..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/reader.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/reader_active.png b/mobile/android/base/resources/drawable-xhdpi/reader_active.png
deleted file mode 100644
index 8a9872be1..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/reader_active.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/reading_list_folder.png b/mobile/android/base/resources/drawable-xhdpi/reading_list_folder.png
deleted file mode 100644
index c7ad1cade..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/reading_list_folder.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/search_clear.png b/mobile/android/base/resources/drawable-xhdpi/search_clear.png
deleted file mode 100644
index b0881ed32..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/search_clear.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/search_history.png b/mobile/android/base/resources/drawable-xhdpi/search_history.png
deleted file mode 100644
index ed661a729..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/search_history.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/search_icon_active.png b/mobile/android/base/resources/drawable-xhdpi/search_icon_active.png
deleted file mode 100644
index baef8bea8..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/search_icon_active.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/search_icon_inactive.png b/mobile/android/base/resources/drawable-xhdpi/search_icon_inactive.png
deleted file mode 100644
index d3f73d7e2..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/search_icon_inactive.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/search_launcher.png b/mobile/android/base/resources/drawable-xhdpi/search_launcher.png
deleted file mode 100644
index be0fd65fb..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/search_launcher.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/search_plus.png b/mobile/android/base/resources/drawable-xhdpi/search_plus.png
deleted file mode 100644
index de3b631cf..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/search_plus.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/settings_notifications.png b/mobile/android/base/resources/drawable-xhdpi/settings_notifications.png
deleted file mode 100644
index 3e96f6deb..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/settings_notifications.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/shareplane.png b/mobile/android/base/resources/drawable-xhdpi/shareplane.png
deleted file mode 100644
index 44e6878aa..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/shareplane.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/shield_disabled.png b/mobile/android/base/resources/drawable-xhdpi/shield_disabled.png
deleted file mode 100644
index 20e58daf7..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/shield_disabled.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/shield_enabled.png b/mobile/android/base/resources/drawable-xhdpi/shield_enabled.png
deleted file mode 100644
index c95a4876e..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/shield_enabled.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/status_icon_readercache.png b/mobile/android/base/resources/drawable-xhdpi/status_icon_readercache.png
deleted file mode 100644
index e5211c71b..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/status_icon_readercache.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/suggestedsites_amazon.png b/mobile/android/base/resources/drawable-xhdpi/suggestedsites_amazon.png
deleted file mode 100644
index 012060dd9..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/suggestedsites_amazon.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/suggestedsites_facebook.png b/mobile/android/base/resources/drawable-xhdpi/suggestedsites_facebook.png
deleted file mode 100644
index b97f9cba9..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/suggestedsites_facebook.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/suggestedsites_restricted_fxsupport.png b/mobile/android/base/resources/drawable-xhdpi/suggestedsites_restricted_fxsupport.png
deleted file mode 100644
index 25338e447..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/suggestedsites_restricted_fxsupport.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/suggestedsites_restricted_mozilla.png b/mobile/android/base/resources/drawable-xhdpi/suggestedsites_restricted_mozilla.png
deleted file mode 100644
index 03a2e9491..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/suggestedsites_restricted_mozilla.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/suggestedsites_twitter.png b/mobile/android/base/resources/drawable-xhdpi/suggestedsites_twitter.png
deleted file mode 100644
index 75bc311c0..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/suggestedsites_twitter.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/suggestedsites_webmaker.png b/mobile/android/base/resources/drawable-xhdpi/suggestedsites_webmaker.png
deleted file mode 100644
index 92f7cfecf..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/suggestedsites_webmaker.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/suggestedsites_wikipedia.png b/mobile/android/base/resources/drawable-xhdpi/suggestedsites_wikipedia.png
deleted file mode 100644
index 76140d48b..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/suggestedsites_wikipedia.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/suggestedsites_youtube.png b/mobile/android/base/resources/drawable-xhdpi/suggestedsites_youtube.png
deleted file mode 100644
index 2896a9c17..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/suggestedsites_youtube.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/switch_button_icon.png b/mobile/android/base/resources/drawable-xhdpi/switch_button_icon.png
deleted file mode 100644
index 6dfa79e51..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/switch_button_icon.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/tab_audio_playing.png b/mobile/android/base/resources/drawable-xhdpi/tab_audio_playing.png
deleted file mode 100644
index cb4466e0e..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/tab_audio_playing.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/tab_close.png b/mobile/android/base/resources/drawable-xhdpi/tab_close.png
deleted file mode 100644
index 8e4908e0c..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/tab_close.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/tab_close_active.png b/mobile/android/base/resources/drawable-xhdpi/tab_close_active.png
deleted file mode 100644
index d2071e7c3..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/tab_close_active.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/tab_indicator_background.9.png b/mobile/android/base/resources/drawable-xhdpi/tab_indicator_background.9.png
deleted file mode 100644
index 8b561749a..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/tab_indicator_background.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/tab_indicator_divider.9.png b/mobile/android/base/resources/drawable-xhdpi/tab_indicator_divider.9.png
deleted file mode 100644
index f1338d803..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/tab_indicator_divider.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/tab_indicator_selected.9.png b/mobile/android/base/resources/drawable-xhdpi/tab_indicator_selected.9.png
deleted file mode 100644
index e78a2ddba..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/tab_indicator_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/tab_indicator_selected_focused.9.png b/mobile/android/base/resources/drawable-xhdpi/tab_indicator_selected_focused.9.png
deleted file mode 100644
index 3e1fe5560..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/tab_indicator_selected_focused.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/tab_new.png b/mobile/android/base/resources/drawable-xhdpi/tab_new.png
deleted file mode 100644
index 76a5a1182..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/tab_new.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/tab_preview_masq.png b/mobile/android/base/resources/drawable-xhdpi/tab_preview_masq.png
deleted file mode 100644
index 119542216..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/tab_preview_masq.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/tabs_count.png b/mobile/android/base/resources/drawable-xhdpi/tabs_count.png
deleted file mode 100644
index e4c71cb14..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/tabs_count.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/tabs_count_foreground.png b/mobile/android/base/resources/drawable-xhdpi/tabs_count_foreground.png
deleted file mode 100644
index a5a75227c..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/tabs_count_foreground.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/tabs_normal.png b/mobile/android/base/resources/drawable-xhdpi/tabs_normal.png
deleted file mode 100644
index f76b0221c..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/tabs_normal.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/tabs_panel_nav_back.png b/mobile/android/base/resources/drawable-xhdpi/tabs_panel_nav_back.png
deleted file mode 100644
index 79d8ae285..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/tabs_panel_nav_back.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/tabs_private.png b/mobile/android/base/resources/drawable-xhdpi/tabs_private.png
deleted file mode 100644
index 14a3f4b79..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/tabs_private.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/tip_addsearch.png b/mobile/android/base/resources/drawable-xhdpi/tip_addsearch.png
deleted file mode 100644
index d7c18bdbf..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/tip_addsearch.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/top_site_add.png b/mobile/android/base/resources/drawable-xhdpi/top_site_add.png
deleted file mode 100644
index 7ddc503bf..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/top_site_add.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/tracking_protection_toolbar_illustration.png b/mobile/android/base/resources/drawable-xhdpi/tracking_protection_toolbar_illustration.png
deleted file mode 100644
index 744d3573c..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/tracking_protection_toolbar_illustration.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/undo_button_icon.png b/mobile/android/base/resources/drawable-xhdpi/undo_button_icon.png
deleted file mode 100644
index 5992b4f5c..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/undo_button_icon.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/url_bar_entry_default.9.png b/mobile/android/base/resources/drawable-xhdpi/url_bar_entry_default.9.png
deleted file mode 100644
index e0cf4cf90..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/url_bar_entry_default.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/url_bar_entry_default_pb.9.png b/mobile/android/base/resources/drawable-xhdpi/url_bar_entry_default_pb.9.png
deleted file mode 100644
index ca9e114b6..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/url_bar_entry_default_pb.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/url_bar_entry_pressed.9.png b/mobile/android/base/resources/drawable-xhdpi/url_bar_entry_pressed.9.png
deleted file mode 100644
index 8cbbff190..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/url_bar_entry_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/url_bar_entry_pressed_pb.9.png b/mobile/android/base/resources/drawable-xhdpi/url_bar_entry_pressed_pb.9.png
deleted file mode 100644
index f34244e4c..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/url_bar_entry_pressed_pb.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/urlbar_stop.png b/mobile/android/base/resources/drawable-xhdpi/urlbar_stop.png
deleted file mode 100644
index 015195159..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/urlbar_stop.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/validation_arrow.png b/mobile/android/base/resources/drawable-xhdpi/validation_arrow.png
deleted file mode 100644
index 0dfea5c2f..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/validation_arrow.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/validation_arrow_inverted.png b/mobile/android/base/resources/drawable-xhdpi/validation_arrow_inverted.png
deleted file mode 100644
index 9122e2ce6..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/validation_arrow_inverted.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/validation_bg.9.png b/mobile/android/base/resources/drawable-xhdpi/validation_bg.9.png
deleted file mode 100644
index 773aa03a8..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/validation_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/warning_major.png b/mobile/android/base/resources/drawable-xhdpi/warning_major.png
deleted file mode 100644
index 896062e98..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/warning_major.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/warning_minor.png b/mobile/android/base/resources/drawable-xhdpi/warning_minor.png
deleted file mode 100644
index 84fcff9bb..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/warning_minor.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xhdpi/widget_bg.9.png b/mobile/android/base/resources/drawable-xhdpi/widget_bg.9.png
deleted file mode 100644
index cbf377ac3..000000000
--- a/mobile/android/base/resources/drawable-xhdpi/widget_bg.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xlarge-hdpi-v11/ic_menu_bookmark_add.png b/mobile/android/base/resources/drawable-xlarge-hdpi-v11/ic_menu_bookmark_add.png
deleted file mode 100644
index 54d88fd13..000000000
--- a/mobile/android/base/resources/drawable-xlarge-hdpi-v11/ic_menu_bookmark_add.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xlarge-hdpi-v11/star_blue.png b/mobile/android/base/resources/drawable-xlarge-hdpi-v11/star_blue.png
deleted file mode 100644
index b80c5ac44..000000000
--- a/mobile/android/base/resources/drawable-xlarge-hdpi-v11/star_blue.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xlarge-xhdpi-v11/ic_menu_bookmark_add.png b/mobile/android/base/resources/drawable-xlarge-xhdpi-v11/ic_menu_bookmark_add.png
deleted file mode 100644
index e54d42905..000000000
--- a/mobile/android/base/resources/drawable-xlarge-xhdpi-v11/ic_menu_bookmark_add.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xlarge-xhdpi-v11/star_blue.png b/mobile/android/base/resources/drawable-xlarge-xhdpi-v11/star_blue.png
deleted file mode 100644
index c0278d574..000000000
--- a/mobile/android/base/resources/drawable-xlarge-xhdpi-v11/star_blue.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xlarge-xxhdpi-v11/ic_menu_bookmark_add.png b/mobile/android/base/resources/drawable-xlarge-xxhdpi-v11/ic_menu_bookmark_add.png
deleted file mode 100644
index 0fa071137..000000000
--- a/mobile/android/base/resources/drawable-xlarge-xxhdpi-v11/ic_menu_bookmark_add.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xlarge-xxhdpi-v11/star_blue.png b/mobile/android/base/resources/drawable-xlarge-xxhdpi-v11/star_blue.png
deleted file mode 100644
index c9cf49622..000000000
--- a/mobile/android/base/resources/drawable-xlarge-xxhdpi-v11/star_blue.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi-v11/ic_status_logo.png b/mobile/android/base/resources/drawable-xxhdpi-v11/ic_status_logo.png
deleted file mode 100644
index 0bb9777d7..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi-v11/ic_status_logo.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/ab_mic.png b/mobile/android/base/resources/drawable-xxhdpi/ab_mic.png
deleted file mode 100644
index 14e4cff78..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/ab_mic.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/ab_qrcode.png b/mobile/android/base/resources/drawable-xxhdpi/ab_qrcode.png
deleted file mode 100644
index 8ad785974..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/ab_qrcode.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/arrow_up.png b/mobile/android/base/resources/drawable-xxhdpi/arrow_up.png
deleted file mode 100644
index 193bbc3b4..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/arrow_up.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/close_edit_mode_dark.png b/mobile/android/base/resources/drawable-xxhdpi/close_edit_mode_dark.png
deleted file mode 100644
index 0f0e95deb..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/close_edit_mode_dark.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/close_edit_mode_light.png b/mobile/android/base/resources/drawable-xxhdpi/close_edit_mode_light.png
deleted file mode 100644
index 5f9a2f7e5..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/close_edit_mode_light.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/device_desktop.png b/mobile/android/base/resources/drawable-xxhdpi/device_desktop.png
deleted file mode 100644
index 6a386c4e7..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/device_desktop.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/device_mobile.png b/mobile/android/base/resources/drawable-xxhdpi/device_mobile.png
deleted file mode 100644
index d32a9b353..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/device_mobile.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/dropshadow.9.png b/mobile/android/base/resources/drawable-xxhdpi/dropshadow.9.png
deleted file mode 100644
index a408d91af..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/dropshadow.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/exit_fullscreen.png b/mobile/android/base/resources/drawable-xxhdpi/exit_fullscreen.png
deleted file mode 100644
index 2be3dbac7..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/exit_fullscreen.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/flat_icon.png b/mobile/android/base/resources/drawable-xxhdpi/flat_icon.png
deleted file mode 100644
index 57d83a59e..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/flat_icon.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/folder_closed.png b/mobile/android/base/resources/drawable-xxhdpi/folder_closed.png
deleted file mode 100644
index 15d03e7e4..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/folder_closed.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/fullscreen.png b/mobile/android/base/resources/drawable-xxhdpi/fullscreen.png
deleted file mode 100644
index 2e39898be..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/fullscreen.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/globe_light.png b/mobile/android/base/resources/drawable-xxhdpi/globe_light.png
deleted file mode 100644
index 120bfd2e6..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/globe_light.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/helper_readerview_bookmark.webp b/mobile/android/base/resources/drawable-xxhdpi/helper_readerview_bookmark.webp
deleted file mode 100644
index 04fd2c54f..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/helper_readerview_bookmark.webp
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/home_group_collapsed.png b/mobile/android/base/resources/drawable-xxhdpi/home_group_collapsed.png
deleted file mode 100644
index 319ab3d50..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/home_group_collapsed.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/homepage_banner_firstrun.png b/mobile/android/base/resources/drawable-xxhdpi/homepage_banner_firstrun.png
deleted file mode 100644
index 3ef8f157d..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/homepage_banner_firstrun.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/ic_action_settings.png b/mobile/android/base/resources/drawable-xxhdpi/ic_action_settings.png
deleted file mode 100644
index 9ce42fffe..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/ic_action_settings.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/ic_media_pause.png b/mobile/android/base/resources/drawable-xxhdpi/ic_media_pause.png
deleted file mode 100644
index 29d216e70..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/ic_media_pause.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/ic_media_play.png b/mobile/android/base/resources/drawable-xxhdpi/ic_media_play.png
deleted file mode 100644
index 648e6f67a..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/ic_media_play.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/ic_menu_share.png b/mobile/android/base/resources/drawable-xxhdpi/ic_menu_share.png
deleted file mode 100644
index de6f092ee..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/ic_menu_share.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/ic_widget_new_tab.png b/mobile/android/base/resources/drawable-xxhdpi/ic_widget_new_tab.png
deleted file mode 100644
index 2ab09b8fb..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/ic_widget_new_tab.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/ic_widget_search.png b/mobile/android/base/resources/drawable-xxhdpi/ic_widget_search.png
deleted file mode 100644
index d6ba7c846..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/ic_widget_search.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/icon_key.png b/mobile/android/base/resources/drawable-xxhdpi/icon_key.png
deleted file mode 100644
index 0f6925b04..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/icon_key.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/icon_search_empty_firefox.png b/mobile/android/base/resources/drawable-xxhdpi/icon_search_empty_firefox.png
deleted file mode 100644
index b16c1ab79..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/icon_search_empty_firefox.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/icon_shareplane.png b/mobile/android/base/resources/drawable-xxhdpi/icon_shareplane.png
deleted file mode 100644
index 63e9f2519..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/icon_shareplane.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/img_check.png b/mobile/android/base/resources/drawable-xxhdpi/img_check.png
deleted file mode 100644
index 13e920464..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/img_check.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/location.png b/mobile/android/base/resources/drawable-xxhdpi/location.png
deleted file mode 100644
index 7abc57ef8..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/location.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/lock_disabled.png b/mobile/android/base/resources/drawable-xxhdpi/lock_disabled.png
deleted file mode 100644
index 0396ff06e..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/lock_disabled.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/lock_inactive.png b/mobile/android/base/resources/drawable-xxhdpi/lock_inactive.png
deleted file mode 100644
index 3276f338b..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/lock_inactive.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/lock_secure.png b/mobile/android/base/resources/drawable-xxhdpi/lock_secure.png
deleted file mode 100644
index 19e3a8fad..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/lock_secure.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/menu.png b/mobile/android/base/resources/drawable-xxhdpi/menu.png
deleted file mode 100644
index a6b457fb1..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/menu.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/menu_item_check.png b/mobile/android/base/resources/drawable-xxhdpi/menu_item_check.png
deleted file mode 100644
index 381f91856..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/menu_item_check.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/menu_item_uncheck.png b/mobile/android/base/resources/drawable-xxhdpi/menu_item_uncheck.png
deleted file mode 100644
index 11be568d9..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/menu_item_uncheck.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/network_error.png b/mobile/android/base/resources/drawable-xxhdpi/network_error.png
deleted file mode 100644
index 4074ac2a8..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/network_error.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/notification_media.webp b/mobile/android/base/resources/drawable-xxhdpi/notification_media.webp
deleted file mode 100644
index 2485a4bf1..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/notification_media.webp
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/overlay_bookmark_icon.png b/mobile/android/base/resources/drawable-xxhdpi/overlay_bookmark_icon.png
deleted file mode 100644
index 05dc926f2..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/overlay_bookmark_icon.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/overlay_bookmarked_already_icon.png b/mobile/android/base/resources/drawable-xxhdpi/overlay_bookmarked_already_icon.png
deleted file mode 100644
index 9afb290c4..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/overlay_bookmarked_already_icon.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/overlay_check.png b/mobile/android/base/resources/drawable-xxhdpi/overlay_check.png
deleted file mode 100644
index 1bc3abe8e..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/overlay_check.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/private_masq.png b/mobile/android/base/resources/drawable-xxhdpi/private_masq.png
deleted file mode 100644
index e62cdbe13..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/private_masq.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/push_notification.png b/mobile/android/base/resources/drawable-xxhdpi/push_notification.png
deleted file mode 100644
index 3c4ba474e..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/push_notification.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/reading_list_folder.png b/mobile/android/base/resources/drawable-xxhdpi/reading_list_folder.png
deleted file mode 100644
index c29d1b9a5..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/reading_list_folder.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/search_clear.png b/mobile/android/base/resources/drawable-xxhdpi/search_clear.png
deleted file mode 100644
index f21257bea..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/search_clear.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/search_history.png b/mobile/android/base/resources/drawable-xxhdpi/search_history.png
deleted file mode 100644
index fbcdbdbba..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/search_history.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/search_icon_active.png b/mobile/android/base/resources/drawable-xxhdpi/search_icon_active.png
deleted file mode 100644
index 093b066c9..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/search_icon_active.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/search_icon_inactive.png b/mobile/android/base/resources/drawable-xxhdpi/search_icon_inactive.png
deleted file mode 100644
index 4117c6332..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/search_icon_inactive.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/search_launcher.png b/mobile/android/base/resources/drawable-xxhdpi/search_launcher.png
deleted file mode 100644
index 6c8fc7678..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/search_launcher.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/search_plus.png b/mobile/android/base/resources/drawable-xxhdpi/search_plus.png
deleted file mode 100644
index 8ac7df9e9..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/search_plus.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/shareplane.png b/mobile/android/base/resources/drawable-xxhdpi/shareplane.png
deleted file mode 100644
index c9436aab8..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/shareplane.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/shield_disabled.png b/mobile/android/base/resources/drawable-xxhdpi/shield_disabled.png
deleted file mode 100644
index 57d669d5a..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/shield_disabled.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/shield_enabled.png b/mobile/android/base/resources/drawable-xxhdpi/shield_enabled.png
deleted file mode 100644
index edf20af11..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/shield_enabled.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/status_icon_readercache.png b/mobile/android/base/resources/drawable-xxhdpi/status_icon_readercache.png
deleted file mode 100644
index f6e070b02..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/status_icon_readercache.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/suggestedsites_amazon.png b/mobile/android/base/resources/drawable-xxhdpi/suggestedsites_amazon.png
deleted file mode 100644
index 5b0d2fdc5..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/suggestedsites_amazon.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/suggestedsites_facebook.png b/mobile/android/base/resources/drawable-xxhdpi/suggestedsites_facebook.png
deleted file mode 100644
index 46e2db588..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/suggestedsites_facebook.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/suggestedsites_twitter.png b/mobile/android/base/resources/drawable-xxhdpi/suggestedsites_twitter.png
deleted file mode 100644
index 781b7b5a1..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/suggestedsites_twitter.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/suggestedsites_wikipedia.png b/mobile/android/base/resources/drawable-xxhdpi/suggestedsites_wikipedia.png
deleted file mode 100644
index a324694e9..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/suggestedsites_wikipedia.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/suggestedsites_youtube.png b/mobile/android/base/resources/drawable-xxhdpi/suggestedsites_youtube.png
deleted file mode 100644
index d201b6221..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/suggestedsites_youtube.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/tab_close.png b/mobile/android/base/resources/drawable-xxhdpi/tab_close.png
deleted file mode 100644
index 400319394..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/tab_close.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/tab_close_active.png b/mobile/android/base/resources/drawable-xxhdpi/tab_close_active.png
deleted file mode 100644
index 279135f93..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/tab_close_active.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/tab_new.png b/mobile/android/base/resources/drawable-xxhdpi/tab_new.png
deleted file mode 100644
index e857037c6..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/tab_new.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/tab_preview_masq.png b/mobile/android/base/resources/drawable-xxhdpi/tab_preview_masq.png
deleted file mode 100644
index 9b24f329f..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/tab_preview_masq.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/tabs_panel_nav_back.png b/mobile/android/base/resources/drawable-xxhdpi/tabs_panel_nav_back.png
deleted file mode 100644
index 3b21f3aa2..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/tabs_panel_nav_back.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/tracking_protection_toolbar_illustration.png b/mobile/android/base/resources/drawable-xxhdpi/tracking_protection_toolbar_illustration.png
deleted file mode 100644
index 2c86b5baa..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/tracking_protection_toolbar_illustration.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/url_bar_entry_default.9.png b/mobile/android/base/resources/drawable-xxhdpi/url_bar_entry_default.9.png
deleted file mode 100644
index e7b58136c..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/url_bar_entry_default.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/url_bar_entry_default_pb.9.png b/mobile/android/base/resources/drawable-xxhdpi/url_bar_entry_default_pb.9.png
deleted file mode 100644
index b5b5a8d32..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/url_bar_entry_default_pb.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/url_bar_entry_pressed.9.png b/mobile/android/base/resources/drawable-xxhdpi/url_bar_entry_pressed.9.png
deleted file mode 100644
index 7c2ac33cb..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/url_bar_entry_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/url_bar_entry_pressed_pb.9.png b/mobile/android/base/resources/drawable-xxhdpi/url_bar_entry_pressed_pb.9.png
deleted file mode 100644
index 5eb9cecb4..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/url_bar_entry_pressed_pb.9.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/urlbar_stop.png b/mobile/android/base/resources/drawable-xxhdpi/urlbar_stop.png
deleted file mode 100644
index 510cd7b3c..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/urlbar_stop.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/warning_major.png b/mobile/android/base/resources/drawable-xxhdpi/warning_major.png
deleted file mode 100644
index 172160fb5..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/warning_major.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxhdpi/warning_minor.png b/mobile/android/base/resources/drawable-xxhdpi/warning_minor.png
deleted file mode 100644
index e93ead0e8..000000000
--- a/mobile/android/base/resources/drawable-xxhdpi/warning_minor.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable-xxxhdpi/search_launcher.png b/mobile/android/base/resources/drawable-xxxhdpi/search_launcher.png
deleted file mode 100644
index 1f70d13db..000000000
--- a/mobile/android/base/resources/drawable-xxxhdpi/search_launcher.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable/action_bar_button.xml b/mobile/android/base/resources/drawable/action_bar_button.xml
deleted file mode 100644
index fe36bc43d..000000000
--- a/mobile/android/base/resources/drawable/action_bar_button.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_pressed="true"
- android:state_enabled="true">
- <shape>
- <solid android:color="@color/highlight" />
- </shape>
- </item>
-
- <item android:state_focused="true"
- android:state_pressed="false">
- <shape>
- <solid android:color="@color/highlight_focused" />
- </shape>
- </item>
-
- <item android:drawable="@android:color/transparent"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/action_bar_button_inverse.xml b/mobile/android/base/resources/drawable/action_bar_button_inverse.xml
deleted file mode 100644
index b85387331..000000000
--- a/mobile/android/base/resources/drawable/action_bar_button_inverse.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_pressed="true">
- <shape>
- <solid android:color="@color/highlight_dark" />
- </shape>
- </item>
-
- <item android:state_focused="true"
- android:state_pressed="false">
- <shape>
- <solid android:color="@color/highlight_dark_focused" />
- </shape>
- </item>
-
- <item android:drawable="@android:color/transparent"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/action_bar_button_negative.xml b/mobile/android/base/resources/drawable/action_bar_button_negative.xml
deleted file mode 100644
index 7611d70ba..000000000
--- a/mobile/android/base/resources/drawable/action_bar_button_negative.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_pressed="false">
- <shape>
- <solid android:color="@color/toolbar_menu_dark_grey" />
- </shape>
- </item>
-
- <item android:state_pressed="true">
- <shape>
- <solid android:color="@color/toolbar_grey_pressed" />
- </shape>
- </item>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/action_bar_button_positive.xml b/mobile/android/base/resources/drawable/action_bar_button_positive.xml
deleted file mode 100644
index ac7020b97..000000000
--- a/mobile/android/base/resources/drawable/action_bar_button_positive.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_pressed="false">
- <shape>
- <solid android:color="@color/link_blue"/>
- </shape>
- </item>
-
- <item android:state_pressed="true">
- <shape>
- <solid android:color="@color/link_blue_pressed" />
- </shape>
- </item>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/alert_download_animation.xml b/mobile/android/base/resources/drawable/alert_download_animation.xml
deleted file mode 100644
index e50472f0b..000000000
--- a/mobile/android/base/resources/drawable/alert_download_animation.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
- android:oneshot="false">
-
- <item android:drawable="@drawable/alert_download_animation_1" android:duration="150" />
- <item android:drawable="@drawable/alert_download_animation_2" android:duration="150" />
- <item android:drawable="@drawable/alert_download_animation_3" android:duration="150" />
- <item android:drawable="@drawable/alert_download_animation_4" android:duration="150" />
- <item android:drawable="@drawable/alert_download_animation_5" android:duration="150" />
- <item android:drawable="@drawable/alert_download_animation_6" android:duration="150" />
-
-</animation-list>
diff --git a/mobile/android/base/resources/drawable/arrow_down.xml b/mobile/android/base/resources/drawable/arrow_down.xml
deleted file mode 100644
index cfb14ed4c..000000000
--- a/mobile/android/base/resources/drawable/arrow_down.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<rotate xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/arrow_up"
- android:fromDegrees="180"
- android:toDegrees="180"/>
-
diff --git a/mobile/android/base/resources/drawable/as_bin.xml b/mobile/android/base/resources/drawable/as_bin.xml
deleted file mode 100644
index 46de6104e..000000000
--- a/mobile/android/base/resources/drawable/as_bin.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M20.75,5L16,5v-2.75a1.252,1.252 0,0 0,-1.25 -1.25h-5.5a1.252,1.252 0,0 0,-1.25 1.25L8,5h-4.75a1.25,1.25 0,0 0,0 2.5L5,7.5v14.25a1.252,1.252 0,0 0,1.25 1.25h11.5a1.252,1.252 0,0 0,1.25 -1.25L19,7.5h1.75A1.25,1.25 0,0 0,20.75 5ZM10.5,3.5h3v1.5h-3v-1.5ZM16.5,20.5h-9v-13h9v13ZM10.5,18h0a0.5,0.5 0,0 1,-0.5 -0.5v-7a0.5,0.5 0,0 1,0.5 -0.5h0a0.5,0.5 0,0 1,0.5 0.5v7A0.5,0.5 0,0 1,10.5 18ZM13.5,18h0a0.5,0.5 0,0 1,-0.5 -0.5v-7a0.5,0.5 0,0 1,0.5 -0.5h0a0.5,0.5 0,0 1,0.5 0.5v7A0.5,0.5 0,0 1,13.5 18Z"/>
-</vector>
diff --git a/mobile/android/base/resources/drawable/as_bookmark.xml b/mobile/android/base/resources/drawable/as_bookmark.xml
deleted file mode 100644
index 890838be3..000000000
--- a/mobile/android/base/resources/drawable/as_bookmark.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M12.037,5.333l1.706,3.361 0.569,1.121 1.239,0.211 3.617,0.619 -2.617,2.851 -0.81,0.884 0.181,1.185 0.583,3.8 -3.312,-1.758 -1.179,-0.626 -1.176,0.632 -3.232,1.736 0.582,-3.788 0.184,-1.194 -0.822,-0.886 -2.633,-2.84 3.676,-0.619 1.272,-0.214 0.563,-1.161 1.609,-3.319M12.01,1a1.335,1.335 0,0 0,-1.085 0.895l-2.747,5.667 -5.877,0.99c-1.345,0.219 -1.679,1.186 -0.744,2.148l4.16,4.486 -0.969,6.311c-0.147,0.948 0.242,1.5 0.925,1.5a2,2 0,0 0,0.891 -0.249l5.457,-2.931 5.521,2.931a2,2 0,0 0,0.892 0.249c0.683,0 1.07,-0.555 0.926,-1.5l-0.966,-6.311 4.111,-4.481c0.936,-0.966 0.6,-1.934 -0.744,-2.153l-5.789,-0.99L13.1,1.9A1.333,1.333 0,0 0,12.01 1h0Z"/>
-</vector>
diff --git a/mobile/android/base/resources/drawable/as_contextmenu_divider.xml b/mobile/android/base/resources/drawable/as_contextmenu_divider.xml
deleted file mode 100644
index f24f9a238..000000000
--- a/mobile/android/base/resources/drawable/as_contextmenu_divider.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetLeft="72dp">
- <shape>
- <size
- android:height="1dp"/>
- <solid android:color="@color/disabled_grey"/>
- </shape>
-</inset> \ No newline at end of file
diff --git a/mobile/android/base/resources/drawable/as_copy.xml b/mobile/android/base/resources/drawable/as_copy.xml
deleted file mode 100644
index 516459edb..000000000
--- a/mobile/android/base/resources/drawable/as_copy.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M21.75,23h-12.5a1.25,1.25 0,0 1,-1.25 -1.25v-14.5a1.25,1.25 0,0 1,1.25 -1.25h9a1.252,1.252 0,0 1,0.884 0.366l3.5,3.5a1.252,1.252 0,0 1,0.366 0.884v11A1.25,1.25 0,0 1,21.75 23ZM10.5,20.5h10v-9.232l-2.768,-2.768L10.5,8.5v12ZM7,15.5h-3.5v-12h7.232l1.5,1.5h3.511a1.16,1.16 0,0 0,-0.109 -0.134l-3.5,-3.5A1.252,1.252 0,0 0,11.25 1h-9a1.25,1.25 0,0 0,-1.25 1.25v14.5a1.25,1.25 0,0 0,1.25 1.25L7,18v-2.5ZM18.5,12L17,12v-1.5a0.5,0.5 0,0 0,-1 0v2a0.5,0.5 0,0 0,0.5 0.5h2A0.5,0.5 0,0 0,18.5 12Z"/>
-</vector>
diff --git a/mobile/android/base/resources/drawable/as_dimiss.xml b/mobile/android/base/resources/drawable/as_dimiss.xml
deleted file mode 100644
index ccc028e48..000000000
--- a/mobile/android/base/resources/drawable/as_dimiss.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M13.779,12l7.867,-7.866a1.25,1.25 0,0 0,-1.768 -1.768l-7.866,7.866 -7.866,-7.866a1.25,1.25 0,1 0,-1.768 1.768L10.244,12l-7.866,7.866a1.25,1.25 0,0 0,1.768 1.768l7.866,-7.866 7.866,7.866a1.25,1.25 0,0 0,1.768 -1.768Z"/>
-</vector>
diff --git a/mobile/android/base/resources/drawable/as_home.xml b/mobile/android/base/resources/drawable/as_home.xml
deleted file mode 100644
index aece2b195..000000000
--- a/mobile/android/base/resources/drawable/as_home.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18.012,22.969h-4v-6h-4v6h-4a1.075,1.075 0,0 1,-1 -1v-8.5a1.091,1.091 0,0 1,0.5 -1l5.5,-5.5a1.413,1.413 0,0 1,2 0l5.5,5.5a1.538,1.538 0,0 1,0.5 1v8.5A1.075,1.075 0,0 1,18.012 22.969ZM22.012,13.281a1.246,1.246 0,0 1,-0.884 -0.366l-9.116,-9.116 -9.116,9.116a1.25,1.25 0,0 1,-1.768 -1.768l10,-10a1.251,1.251 0,0 1,1.768 0l10,10A1.25,1.25 0,0 1,22.012 13.281Z"/>
-</vector>
diff --git a/mobile/android/base/resources/drawable/as_private.xml b/mobile/android/base/resources/drawable/as_private.xml
deleted file mode 100644
index 96c8fbdd2..000000000
--- a/mobile/android/base/resources/drawable/as_private.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M18,17.823c-2.29,0 -3.873,-2.692 -6.069,-2.692s-3.916,2.692 -6.069,2.692c-2.826,0 -4.912,-2.616 -4.946,-7.1 -0.021,-2.783 0.829,-3.671 4.5,-3.671s4.742,1.468 6.519,1.468 2.852,-1.468 6.519,-1.468 4.517,0.888 4.5,3.671C22.909,15.207 20.823,17.823 18,17.823ZM7.21,10.481c-2.229,0.1 -3.147,1.393 -3.147,1.713s1.478,1.224 2.923,1.224 3.147,-0.518 3.147,-0.979A2.611,2.611 0,0 0,7.207 10.481ZM16.652,10.481a2.611,2.611 0,0 0,-2.923 1.958c0,0.461 1.7,0.979 3.147,0.979s2.923,-0.9 2.923,-1.224S18.878,10.576 16.649,10.481Z"/>
-</vector>
diff --git a/mobile/android/base/resources/drawable/as_share.xml b/mobile/android/base/resources/drawable/as_share.xml
deleted file mode 100644
index ecb0f200b..000000000
--- a/mobile/android/base/resources/drawable/as_share.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M19,15a3.987,3.987 0,0 0,-2.839 1.18l-7.208,-3.6a3.6,3.6 0,0 0,0 -1.16l7.208,-3.6A4,4 0,1 0,15 5a3.936,3.936 0,0 0,0.047 0.58l-7.208,3.6a4,4 0,1 0,0 5.64l7.208,3.6a3.936,3.936 0,0 0,-0.047 0.58A4,4 0,1 0,19 15Z"/>
-</vector>
diff --git a/mobile/android/base/resources/drawable/as_tab.xml b/mobile/android/base/resources/drawable/as_tab.xml
deleted file mode 100644
index b8c4b19e9..000000000
--- a/mobile/android/base/resources/drawable/as_tab.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FF000000"
- android:pathData="M22,20.015L2,20.015a1.266,1.266 0,0 1,-1.266 -1.266v-2.531A1.266,1.266 0,0 1,2 14.952c0.618,0 1.248,-3.239 1.484,-4.459 0.622,-3.2 1.266,-6.508 3.971,-6.508h9.09c2.705,0 3.349,3.309 3.971,6.508 0.236,1.22 0.866,4.459 1.484,4.459a1.266,1.266 0,0 1,1.266 1.266v2.531A1.266,1.266 0,0 1,22 20.015ZM3.266,17.483h17.468v-0.3c-1.668,-0.883 -2.193,-3.583 -2.7,-6.21 -0.237,-1.219 -0.867,-4.459 -1.485,-4.459h-9.09c-0.618,0 -1.248,3.24 -1.485,4.459 -0.511,2.627 -1.036,5.327 -2.7,6.21v0.3Z"/>
-</vector>
diff --git a/mobile/android/base/resources/drawable/autocomplete_list_bg.xml b/mobile/android/base/resources/drawable/autocomplete_list_bg.xml
deleted file mode 100644
index 9747b48a2..000000000
--- a/mobile/android/base/resources/drawable/autocomplete_list_bg.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
-
- <solid android:color="@android:color/white"/>
-
- <stroke android:width="1dp"
- android:color="@color/placeholder_grey" />
-
-</shape> \ No newline at end of file
diff --git a/mobile/android/base/resources/drawable/bookmark_folder_arrow_up.xml b/mobile/android/base/resources/drawable/bookmark_folder_arrow_up.xml
deleted file mode 100644
index 717cc5952..000000000
--- a/mobile/android/base/resources/drawable/bookmark_folder_arrow_up.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- State drawables will stretch drawables to be the same size and neither
- android:constantSize nor variablePadding fix this, so we hard-code padding
- in to ensure that they display at their true size. -->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/arrow_up"
- android:insetTop="4dp"
- android:insetRight="5dp"
- android:insetBottom="4dp"
- android:insetLeft="5dp" />
diff --git a/mobile/android/base/resources/drawable/button_background_action_blue_round.xml b/mobile/android/base/resources/drawable/button_background_action_blue_round.xml
deleted file mode 100644
index 9b5ba8068..000000000
--- a/mobile/android/base/resources/drawable/button_background_action_blue_round.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true"
- android:drawable="@drawable/button_pressed_action_blue_round" />
- <item android:state_enabled="true"
- android:drawable="@drawable/button_enabled_action_blue_round" />
-</selector>
diff --git a/mobile/android/base/resources/drawable/button_background_action_orange_round.xml b/mobile/android/base/resources/drawable/button_background_action_orange_round.xml
deleted file mode 100644
index 02a6c6673..000000000
--- a/mobile/android/base/resources/drawable/button_background_action_orange_round.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true"
- android:drawable="@drawable/button_pressed_action_orange_round" />
- <item android:state_enabled="true"
- android:drawable="@drawable/button_enabled_action_orange_round" />
-</selector>
diff --git a/mobile/android/base/resources/drawable/button_enabled_action_blue_round.xml b/mobile/android/base/resources/drawable/button_enabled_action_blue_round.xml
deleted file mode 100644
index 94a6b9935..000000000
--- a/mobile/android/base/resources/drawable/button_enabled_action_blue_round.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle" >
- <solid android:color="@color/link_blue_pressed" />
- <corners
- android:radius="@dimen/standard_corner_radius" />
-</shape>
diff --git a/mobile/android/base/resources/drawable/button_enabled_action_orange_round.xml b/mobile/android/base/resources/drawable/button_enabled_action_orange_round.xml
deleted file mode 100644
index 7a9d55b10..000000000
--- a/mobile/android/base/resources/drawable/button_enabled_action_orange_round.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle" >
- <solid android:color="@color/action_orange" />
- <corners
- android:radius="@dimen/standard_corner_radius" />
-</shape>
diff --git a/mobile/android/base/resources/drawable/button_pressed_action_blue_round.xml b/mobile/android/base/resources/drawable/button_pressed_action_blue_round.xml
deleted file mode 100644
index bca19fab6..000000000
--- a/mobile/android/base/resources/drawable/button_pressed_action_blue_round.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle" >
- <solid android:color="@color/link_blue" />
- <corners
- android:radius="@dimen/standard_corner_radius" />
-</shape>
diff --git a/mobile/android/base/resources/drawable/button_pressed_action_orange_round.xml b/mobile/android/base/resources/drawable/button_pressed_action_orange_round.xml
deleted file mode 100644
index 2720b7fba..000000000
--- a/mobile/android/base/resources/drawable/button_pressed_action_orange_round.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle" >
- <solid android:color="@color/action_orange_pressed" />
- <corners
- android:radius="@dimen/standard_corner_radius" />
-</shape>
diff --git a/mobile/android/base/resources/drawable/close_edit_mode_selector.xml b/mobile/android/base/resources/drawable/close_edit_mode_selector.xml
deleted file mode 100644
index aab2a469c..000000000
--- a/mobile/android/base/resources/drawable/close_edit_mode_selector.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <item gecko:state_dark="true"
- android:drawable="@drawable/close_edit_mode_dark"/>
-
- <item gecko:state_private="true"
- android:drawable="@drawable/close_edit_mode_light"/>
-
- <item android:drawable="@drawable/close_edit_mode_light"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/color_picker_checkmark.xml b/mobile/android/base/resources/drawable/color_picker_checkmark.xml
deleted file mode 100644
index 645ec8115..000000000
--- a/mobile/android/base/resources/drawable/color_picker_checkmark.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="ring"
- android:innerRadius="15dip"
- android:thickness="4dip"
- android:useLevel="false">
- <solid android:color="@android:color/white"/>
-</shape>
diff --git a/mobile/android/base/resources/drawable/divider_vertical.xml b/mobile/android/base/resources/drawable/divider_vertical.xml
deleted file mode 100644
index d326d94f9..000000000
--- a/mobile/android/base/resources/drawable/divider_vertical.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
-
- <solid android:color="@color/toolbar_divider_grey"/>
- <size android:width="1dp" />
-
-</shape>
diff --git a/mobile/android/base/resources/drawable/edit_text_default.xml b/mobile/android/base/resources/drawable/edit_text_default.xml
deleted file mode 100644
index edb6632db..000000000
--- a/mobile/android/base/resources/drawable/edit_text_default.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-
- <!-- Make sure the border only appears at the bottom of the background -->
- <item
- android:top="-2dp"
- android:right="-2dp"
- android:left="-2dp">
- <shape>
- <!-- Padding creates vertical space between the text and the underline,
- as well as right padding for search icon/clear button -->
- <padding
- android:top="@dimen/search_bar_padding_y"
- android:bottom="@dimen/search_bar_padding_y"
- android:right="@dimen/search_bar_padding_right"/>
- <solid android:color="@android:color/transparent"/>
- <stroke android:width="1dp" android:color="@color/tabs_tray_icon_grey"/>
- </shape>
- </item>
-
-</layer-list>
diff --git a/mobile/android/base/resources/drawable/edit_text_focused.xml b/mobile/android/base/resources/drawable/edit_text_focused.xml
deleted file mode 100644
index 38782652e..000000000
--- a/mobile/android/base/resources/drawable/edit_text_focused.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-
- <!-- Make sure the border only appears at the bottom of the background -->
- <item
- android:top="-3dp"
- android:right="-3dp"
- android:left="-3dp">
- <shape>
- <!-- Padding creates vertical space between the text and the underline,
- as well as right padding for search icon/clear button -->
- <padding
- android:top="@dimen/search_bar_padding_y"
- android:bottom="@dimen/search_bar_padding_y"
- android:right="@dimen/search_bar_padding_right"/>
- <solid android:color="@android:color/transparent"/>
- <!-- We apply a color filter to set the color for the selected search engine -->
- <stroke android:width="2dp" android:color="@android:color/white"/>
- </shape>
- </item>
-
-</layer-list>
diff --git a/mobile/android/base/resources/drawable/facet_button_background.xml b/mobile/android/base/resources/drawable/facet_button_background.xml
deleted file mode 100644
index de1b6e996..000000000
--- a/mobile/android/base/resources/drawable/facet_button_background.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <!--facet button is pressed (omitting currently-selected facet)-->
- <item
- android:state_pressed="true"
- android:state_checked="false"
- android:drawable="@drawable/facet_button_background_pressed"/>
-
- <!--default-->
- <item
- android:drawable="@drawable/facet_button_background_default"/>
-</selector>
diff --git a/mobile/android/base/resources/drawable/facet_button_background_default.xml b/mobile/android/base/resources/drawable/facet_button_background_default.xml
deleted file mode 100644
index b3358d2ed..000000000
--- a/mobile/android/base/resources/drawable/facet_button_background_default.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="@color/facet_button_background_color_default" />
-</shape>
diff --git a/mobile/android/base/resources/drawable/facet_button_background_pressed.xml b/mobile/android/base/resources/drawable/facet_button_background_pressed.xml
deleted file mode 100644
index 0a46f6057..000000000
--- a/mobile/android/base/resources/drawable/facet_button_background_pressed.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="@color/facet_button_background_color_pressed" />
-</shape>
diff --git a/mobile/android/base/resources/drawable/home_banner.xml b/mobile/android/base/resources/drawable/home_banner.xml
deleted file mode 100644
index ea536ced0..000000000
--- a/mobile/android/base/resources/drawable/home_banner.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_pressed="true">
- <layer-list>
- <item android:left="-2dp"
- android:right="-2dp"
- android:bottom="-2dp">
-
- <shape android:shape="rectangle" >
- <stroke android:width="2dp"
- android:color="#FFE0E4E7" />
- <solid android:color="#FFC5D0DA" />
- </shape>
- </item>
- </layer-list>
- </item>
-
- <item>
- <layer-list>
- <item android:left="-2dp"
- android:right="-2dp"
- android:bottom="-2dp">
-
- <shape android:shape="rectangle" >
- <stroke android:width="2dp"
- android:color="#FFE0E4E7" />
- <solid android:color="@color/about_page_header_grey" />
- </shape>
- </item>
- </layer-list>
- </item>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/home_history_clear_button_bg.xml b/mobile/android/base/resources/drawable/home_history_clear_button_bg.xml
deleted file mode 100644
index f4fee99f4..000000000
--- a/mobile/android/base/resources/drawable/home_history_clear_button_bg.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- - This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/.
- -->
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
- <item>
- <shape android:shape="rectangle" >
- <stroke android:width="1dp"
- android:color="@color/toolbar_divider_grey" />
- <padding android:top="1dp" />
- </shape>
- </item>
- <item>
- <selector>
- <item android:state_pressed="true"
- android:drawable="@color/toolbar_grey_pressed" />
- <item android:drawable="@color/toolbar_grey"/>
- </selector>
- </item>
-</layer-list> \ No newline at end of file
diff --git a/mobile/android/base/resources/drawable/home_pager_empty_state.xml b/mobile/android/base/resources/drawable/home_pager_empty_state.xml
deleted file mode 100644
index 71389ebc6..000000000
--- a/mobile/android/base/resources/drawable/home_pager_empty_state.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <item android:maxLevel="0" android:drawable="@android:color/white"/>
-
- <item>
- <bitmap android:src="@drawable/icon_home_empty_firefox"
- android:gravity="center"/>
- </item>
-
-</layer-list> \ No newline at end of file
diff --git a/mobile/android/base/resources/drawable/ic_as_bookmarked.xml b/mobile/android/base/resources/drawable/ic_as_bookmarked.xml
deleted file mode 100644
index 033718641..000000000
--- a/mobile/android/base/resources/drawable/ic_as_bookmarked.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="@color/activity_stream_icon"
- android:pathData="M12.01,1a1.34,1.34 0,0 0,-1.085 0.89l-2.747,5.67 -5.877,0.99c-1.345,0.22 -1.679,1.19 -0.744,2.15l4.16,4.49 -0.969,6.31c-0.147,0.94 0.242,1.5 0.925,1.5a1.986,1.986 0,0 0,0.891 -0.25l5.457,-2.93 5.521,2.93a1.993,1.993 0,0 0,0.892 0.25c0.683,0 1.07,-0.56 0.926,-1.5l-0.966,-6.31 4.111,-4.49c0.936,-0.96 0.6,-1.93 -0.744,-2.15l-5.789,-0.99 -2.877,-5.67a1.339,1.339 0,0 0,-1.085 -0.89h0Z"/>
-</vector>
diff --git a/mobile/android/base/resources/drawable/ic_as_visited.xml b/mobile/android/base/resources/drawable/ic_as_visited.xml
deleted file mode 100644
index 05006ef95..000000000
--- a/mobile/android/base/resources/drawable/ic_as_visited.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="@color/activity_stream_icon"
- android:pathData="M12,18a6,6 0,1 1,6 -6A6,6 0,0 1,12 18ZM12,9a3,3 0,1 0,3 3A3,3 0,0 0,12 9Z"/>
-</vector>
diff --git a/mobile/android/base/resources/drawable/icon_grid_item_bg.xml b/mobile/android/base/resources/drawable/icon_grid_item_bg.xml
deleted file mode 100644
index 45c9cb1f3..000000000
--- a/mobile/android/base/resources/drawable/icon_grid_item_bg.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_focused="true"
- android:state_pressed="true"
- android:drawable="@drawable/grid_icon_bg_focused" />
-
- <item android:state_activated="true"
- android:drawable="@drawable/grid_icon_bg_activated" />
-
- <item android:drawable="@android:color/transparent" />
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/logo.xml b/mobile/android/base/resources/drawable/logo.xml
deleted file mode 100644
index e188f80dc..000000000
--- a/mobile/android/base/resources/drawable/logo.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- Overidden. -->
-<bitmap
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:src="@drawable/icon"/>
diff --git a/mobile/android/base/resources/drawable/menu_item_action_bar_bg.xml b/mobile/android/base/resources/drawable/menu_item_action_bar_bg.xml
deleted file mode 100644
index ad00f49f9..000000000
--- a/mobile/android/base/resources/drawable/menu_item_action_bar_bg.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_pressed="true"
- android:state_enabled="true">
- <shape>
- <solid android:color="@color/toolbar_grey_pressed" />
- </shape>
- </item>
-
- <item android:state_focused="true"
- android:state_pressed="false">
- <shape>
- <solid android:color="@color/highlight_focused" />
- </shape>
- </item>
-
- <item android:drawable="@android:color/transparent"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/menu_item_state.xml b/mobile/android/base/resources/drawable/menu_item_state.xml
deleted file mode 100644
index c7063f4e3..000000000
--- a/mobile/android/base/resources/drawable/menu_item_state.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <item gecko:state_more="true"
- android:drawable="@drawable/menu_item_more"/>
-
- <item gecko:state_more="false"
- android:state_checkable="true"
- android:state_checked="true"
- android:drawable="@drawable/menu_item_check"/>
-
- <item gecko:state_more="false"
- android:state_checkable="true"
- android:state_checked="false"
- android:drawable="@drawable/menu_item_uncheck"/>
-
- <item android:drawable="@android:color/transparent"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/overlay_share_bookmark_button.xml b/mobile/android/base/resources/drawable/overlay_share_bookmark_button.xml
deleted file mode 100644
index bc1d51c5d..000000000
--- a/mobile/android/base/resources/drawable/overlay_share_bookmark_button.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item
- android:state_enabled="false"
- android:drawable="@drawable/overlay_bookmarked_already_icon"/>
- <item
- android:drawable="@drawable/overlay_bookmark_icon"/>
-</selector>
diff --git a/mobile/android/base/resources/drawable/overlay_share_button_background.xml b/mobile/android/base/resources/drawable/overlay_share_button_background.xml
deleted file mode 100644
index 6077ad38a..000000000
--- a/mobile/android/base/resources/drawable/overlay_share_button_background.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- - This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/.
- -->
-
-<!-- Should be kept in sync with overlay_share_button_background_first.xml -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_pressed="true" android:drawable="@color/toolbar_grey_pressed" />
- <item android:drawable="@color/toolbar_grey"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/overlay_share_button_background_first.xml b/mobile/android/base/resources/drawable/overlay_share_button_background_first.xml
deleted file mode 100644
index 65ee5de9d..000000000
--- a/mobile/android/base/resources/drawable/overlay_share_button_background_first.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- - This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/.
- -->
-
-<!-- Should be kept in sync with overlay_share_button_background.xml
-
- This first item in the list has rounded corners. -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_pressed="true">
- <shape>
- <solid android:color="@color/toolbar_grey_pressed"/>
- <corners android:topLeftRadius="@dimen/standard_corner_radius"
- android:topRightRadius="@dimen/standard_corner_radius"/>
- </shape>
- </item>
-
- <item>
- <shape>
- <solid android:color="@color/toolbar_grey"/>
- <corners android:topLeftRadius="@dimen/standard_corner_radius"
- android:topRightRadius="@dimen/standard_corner_radius"/>
- </shape>
- </item>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/panel_auth_button.xml b/mobile/android/base/resources/drawable/panel_auth_button.xml
deleted file mode 100644
index 4a9adf428..000000000
--- a/mobile/android/base/resources/drawable/panel_auth_button.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_pressed="true">
- <layer-list>
- <item android:left="-2dp"
- android:right="-2dp"
- android:top="-2dp">
-
- <shape android:shape="rectangle" >
- <stroke android:width="2dp"
- android:color="#FFE0E4E7" />
- <solid android:color="#FFC5D0DA" />
- </shape>
- </item>
- </layer-list>
- </item>
-
- <item>
- <layer-list>
- <item android:left="-2dp"
- android:right="-2dp"
- android:top="-2dp">
-
- <shape android:shape="rectangle" >
- <stroke android:width="2dp"
- android:color="#FFE0E4E7" />
- <solid android:color="@color/about_page_header_grey" />
- </shape>
- </item>
- </layer-list>
- </item>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/progressbar.xml b/mobile/android/base/resources/drawable/progressbar.xml
deleted file mode 100644
index 627484a1f..000000000
--- a/mobile/android/base/resources/drawable/progressbar.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@android:id/progress">
- <clip>
- <shape>
- <solid android:color="@color/fennec_ui_orange"/>
- </shape>
- </clip>
- </item>
-</layer-list>
diff --git a/mobile/android/base/resources/drawable/push_notification.png b/mobile/android/base/resources/drawable/push_notification.png
deleted file mode 100644
index 2a52dbd50..000000000
--- a/mobile/android/base/resources/drawable/push_notification.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/drawable/remote_tabs_setup_button_background.xml b/mobile/android/base/resources/drawable/remote_tabs_setup_button_background.xml
deleted file mode 100644
index dad4e5124..000000000
--- a/mobile/android/base/resources/drawable/remote_tabs_setup_button_background.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true">
- <shape android:shape="rectangle">
- <solid android:color="@color/remote_tabs_setup_button_background_hit"/>
- <corners android:radius="@dimen/standard_corner_radius"/>
- </shape>
- </item>
-
- <item>
- <shape android:shape="rectangle">
- <solid android:color="@color/action_orange"/>
- <corners android:radius="@dimen/standard_corner_radius"/>
- </shape>
- </item>
-</selector>
diff --git a/mobile/android/base/resources/drawable/search_row_background.xml b/mobile/android/base/resources/drawable/search_row_background.xml
deleted file mode 100644
index ded70ec6d..000000000
--- a/mobile/android/base/resources/drawable/search_row_background.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_pressed="true" android:drawable="@color/row_background_pressed" />
- <item android:drawable="@color/row_background"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/search_suggestion_button.xml b/mobile/android/base/resources/drawable/search_suggestion_button.xml
deleted file mode 100644
index b91fd4bf0..000000000
--- a/mobile/android/base/resources/drawable/search_suggestion_button.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true">
- <shape>
- <solid android:color="@color/toolbar_grey_pressed"/>
- <corners android:radius="@dimen/standard_corner_radius"/>
- </shape>
- </item>
-
- <item>
- <shape>
- <solid android:color="@color/toolbar_grey"/>
- <corners android:radius="@dimen/standard_corner_radius"/>
- </shape>
- </item>
-</selector>
diff --git a/mobile/android/base/resources/drawable/search_suggestion_prompt_no.xml b/mobile/android/base/resources/drawable/search_suggestion_prompt_no.xml
deleted file mode 100644
index b976cfa5a..000000000
--- a/mobile/android/base/resources/drawable/search_suggestion_prompt_no.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true">
- <shape>
- <solid android:color="@color/toolbar_grey_pressed"/>
- <corners android:radius="@dimen/doorhanger_rounded_corner_radius"/>
- </shape>
- </item>
-
- <item>
- <shape>
- <solid android:color="@color/toolbar_menu_dark_grey"/>
- <corners android:radius="@dimen/doorhanger_rounded_corner_radius"/>
- </shape>
- </item>
-</selector>
diff --git a/mobile/android/base/resources/drawable/search_suggestion_prompt_yes.xml b/mobile/android/base/resources/drawable/search_suggestion_prompt_yes.xml
deleted file mode 100644
index 12551497b..000000000
--- a/mobile/android/base/resources/drawable/search_suggestion_prompt_yes.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-<item android:state_pressed="true">
- <shape>
- <solid android:color="@color/link_blue_pressed"/>
- <corners android:radius="@dimen/doorhanger_rounded_corner_radius"/>
- </shape>
-</item>
-
-<item>
- <shape>
- <solid android:color="@color/link_blue"/>
- <corners android:radius="@dimen/doorhanger_rounded_corner_radius"/>
- </shape>
-</item>
-</selector>
diff --git a/mobile/android/base/resources/drawable/shaped_button.xml b/mobile/android/base/resources/drawable/shaped_button.xml
deleted file mode 100644
index 74fc45f85..000000000
--- a/mobile/android/base/resources/drawable/shaped_button.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- If you change this view, update ShapedButton*,
- which dynamically resets to this view. -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_pressed="true"
- android:drawable="@color/highlight_shaped"/>
-
- <item android:state_focused="true"
- android:state_pressed="false"
- android:drawable="@color/highlight_shaped_focused"/>
-
- <item android:drawable="@color/text_and_tabs_tray_grey"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/site_security_level.xml b/mobile/android/base/resources/drawable/site_security_level.xml
deleted file mode 100644
index 5fee9fffa..000000000
--- a/mobile/android/base/resources/drawable/site_security_level.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<level-list xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:maxLevel="0" android:drawable="@drawable/site_security_unknown"/>
- <item android:maxLevel="1" android:drawable="@drawable/lock_secure"/>
- <item android:maxLevel="2" android:drawable="@drawable/lock_secure"/>
- <item android:maxLevel="3" android:drawable="@drawable/warning_minor"/>
- <item android:maxLevel="4" android:drawable="@drawable/lock_disabled"/>
- <item android:maxLevel="5" android:drawable="@drawable/shield_enabled"/>
- <item android:maxLevel="6" android:drawable="@drawable/shield_disabled"/>
-
- <!-- Special icon used for about:home -->
- <item android:maxLevel="999" android:drawable="@drawable/search_icon_inactive" />
-</level-list>
diff --git a/mobile/android/base/resources/drawable/site_security_unknown.xml b/mobile/android/base/resources/drawable/site_security_unknown.xml
deleted file mode 100644
index 86ff863e1..000000000
--- a/mobile/android/base/resources/drawable/site_security_unknown.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- The favicon drawable is not the same dimensions as the site security
- lock icons so we offset it using this drawable to compensate. -->
-<inset
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/favicon_globe"
- android:insetTop="@dimen/site_security_unknown_inset_top"
- android:insetBottom="@dimen/site_security_unknown_inset_bottom"/>
diff --git a/mobile/android/base/resources/drawable/tab_history_bg.xml b/mobile/android/base/resources/drawable/tab_history_bg.xml
deleted file mode 100644
index 1819ddd58..000000000
--- a/mobile/android/base/resources/drawable/tab_history_bg.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
- <item>
- <shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle" >
- <stroke
- android:width="@dimen/tab_history_bg_width"
- android:color="@color/tab_history_border_color" />
-
- <padding android:top="@dimen/tab_history_border_padding" />
- </shape>
- </item>
- <item>
- <shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle" >
- <solid
- android:width="@dimen/tab_history_bg_width"
- android:color="@color/toolbar_grey" />
- </shape>
- </item>
-</layer-list>
diff --git a/mobile/android/base/resources/drawable/tab_history_icon_state.xml b/mobile/android/base/resources/drawable/tab_history_icon_state.xml
deleted file mode 100644
index 504dd5804..000000000
--- a/mobile/android/base/resources/drawable/tab_history_icon_state.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_enabled="false">
- <shape>
- <solid android:color="@color/tab_history_favicon_background" />
- <stroke android:width="@dimen/tab_history_favicon_border_disabled"
- android:color="@color/tab_history_favicon_border" />
- </shape>
- </item>
-
- <item android:state_enabled="true">
- <shape>
- <solid android:color="@color/tab_history_favicon_background" />
- <stroke android:width="@dimen/tab_history_favicon_border_enabled"
- android:color="@color/tab_history_favicon_border" />
- </shape>
- </item>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/tab_item_close_button.xml b/mobile/android/base/resources/drawable/tab_item_close_button.xml
deleted file mode 100644
index 401234633..000000000
--- a/mobile/android/base/resources/drawable/tab_item_close_button.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <!-- pressed state -->
- <item android:state_pressed="true"
- android:drawable="@drawable/tab_close_active"/>
-
- <item android:state_checked="true"
- android:drawable="@drawable/tab_close_active"/>
-
- <!-- normal mode -->
- <item android:drawable="@drawable/tab_close"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/tab_panel_tab_background.xml b/mobile/android/base/resources/drawable/tab_panel_tab_background.xml
deleted file mode 100644
index c69bfbd81..000000000
--- a/mobile/android/base/resources/drawable/tab_panel_tab_background.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <item gecko:state_private="true">
- <layer-list>
- <item>
- <shape android:shape="rectangle">
- <solid android:color="@color/private_toolbar_grey"/>
- </shape>
- </item>
-
- <item>
- <bitmap android:src="@drawable/tab_preview_masq"
- android:gravity="center"/>
- </item>
- </layer-list>
- </item>
-
- <item>
- <layer-list>
- <item>
- <shape android:shape="rectangle">
- <solid android:color="@color/about_page_header_grey"/>
- </shape>
- </item>
-
- <item>
- <bitmap android:src="@drawable/globe_light"
- android:gravity="center"/>
- </item>
- </layer-list>
- </item>
-</selector>
diff --git a/mobile/android/base/resources/drawable/tab_queue_dismiss_button_foreground.xml b/mobile/android/base/resources/drawable/tab_queue_dismiss_button_foreground.xml
deleted file mode 100644
index 843ce5870..000000000
--- a/mobile/android/base/resources/drawable/tab_queue_dismiss_button_foreground.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_pressed="true"
- android:color="@color/tab_queue_dismiss_button_foreground_pressed" />
-
- <item android:color="@color/tab_queue_dismiss_button_foreground"/>
-
-</selector> \ No newline at end of file
diff --git a/mobile/android/base/resources/drawable/tab_row.xml b/mobile/android/base/resources/drawable/tab_row.xml
deleted file mode 100644
index cefd990f3..000000000
--- a/mobile/android/base/resources/drawable/tab_row.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_focused="true"
- android:drawable="@color/tab_row_pressed"/>
-
- <item android:state_pressed="true"
- android:drawable="@color/tab_row_pressed"/>
-
- <item android:drawable="@android:color/transparent"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/tab_strip_button.xml b/mobile/android/base/resources/drawable/tab_strip_button.xml
deleted file mode 100644
index 7daa9d5c4..000000000
--- a/mobile/android/base/resources/drawable/tab_strip_button.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <item android:state_pressed="true"
- android:state_enabled="true">
-
- <inset android:insetTop="@dimen/tablet_tab_strip_button_inset"
- android:insetBottom="@dimen/tablet_tab_strip_button_inset"
- android:insetLeft="@dimen/tablet_tab_strip_button_inset"
- android:insetRight="@dimen/tablet_tab_strip_button_inset">
- <shape android:shape="rectangle">
- <solid android:color="@color/highlight_dark"/>
- <corners android:radius="@dimen/standard_corner_radius"/>
- </shape>
- </inset>
-
- </item>
-
- <item android:state_focused="true"
- android:state_pressed="false">
-
- <inset android:insetTop="@dimen/tablet_tab_strip_button_inset"
- android:insetBottom="@dimen/tablet_tab_strip_button_inset"
- android:insetLeft="@dimen/tablet_tab_strip_button_inset"
- android:insetRight="@dimen/tablet_tab_strip_button_inset">
- <shape android:shape="rectangle">
- <solid android:color="@color/tablet_highlight_focused"/>
- <corners android:radius="@dimen/standard_corner_radius"/>
- </shape>
- </inset>
-
- </item>
-
- <item>
- <shape android:shape="rectangle">
- <solid android:color="@android:color/transparent"/>
- </shape>
- </item>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/tab_strip_divider.xml b/mobile/android/base/resources/drawable/tab_strip_divider.xml
deleted file mode 100644
index 4ff4ff31f..000000000
--- a/mobile/android/base/resources/drawable/tab_strip_divider.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
-
- <solid android:color="#555555"/>
-
- <size android:width="1dp"
- android:height="30dp"/>
-
- <!-- We draw this ourselves in TabStripView.draw() and to avoid implementing more
- than we have to, only bottom padding is taken into account. -->
- <padding android:bottom="6dp"/>
-
-</shape>
diff --git a/mobile/android/base/resources/drawable/tab_thumbnail.xml b/mobile/android/base/resources/drawable/tab_thumbnail.xml
deleted file mode 100644
index e51de927e..000000000
--- a/mobile/android/base/resources/drawable/tab_thumbnail.xml
+++ /dev/null
@@ -1,87 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <item android:state_focused="true">
-
- <shape android:shape="rectangle">
- <!-- @color/fennec_ui_orange with alpha -->
- <solid android:color="#B3FF9500"/>
- <corners android:radius="@dimen/standard_corner_radius"/>
- </shape>
-
- </item>
-
- <item android:state_focused="true"
- gecko:state_private="true">
-
- <shape android:shape="rectangle">
- <!-- @color/private_browsing_purple with alpha -->
- <solid android:color="#B3CF68FF"/>
- <corners android:radius="@dimen/standard_corner_radius"/>
- </shape>
-
- </item>
-
- <item android:state_pressed="true"
- gecko:state_private="true">
-
- <shape android:shape="rectangle">
- <!-- @color/private_browsing_purple with alpha -->
- <solid android:color="#B3CF68FF"/>
- <corners android:radius="@dimen/standard_corner_radius"/>
- </shape>
-
- </item>
-
- <item android:state_pressed="true">
-
- <shape android:shape="rectangle">
- <!-- @color/fennec_ui_orange with alpha -->
- <solid android:color="#B3FF9500"/>
- <corners android:radius="@dimen/standard_corner_radius"/>
- </shape>
-
- </item>
-
- <item gecko:state_recording="true">
-
- <shape android:shape="rectangle">
- <solid android:color="#FFFF0000"/>
- <corners android:radius="@dimen/standard_corner_radius"/>
- </shape>
-
- </item>
-
- <item android:state_focused="false"
- android:state_pressed="false"
- android:state_checked="true"
- gecko:state_recording="false"
- gecko:state_private="true">
-
- <shape android:shape="rectangle">
- <solid android:color="@color/private_browsing_purple"/>
- <corners android:radius="@dimen/standard_corner_radius"/>
- </shape>
-
- </item>
-
- <item android:state_focused="false"
- android:state_pressed="false"
- android:state_checked="true"
- gecko:state_recording="false">
-
- <shape android:shape="rectangle">
- <solid android:color="@color/fennec_ui_orange"/>
- <corners android:radius="@dimen/standard_corner_radius"/>
- </shape>
-
- </item>
-
- <item android:drawable="@android:color/transparent"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/tabs_panel_indicator.xml b/mobile/android/base/resources/drawable/tabs_panel_indicator.xml
deleted file mode 100644
index 4c1ab7655..000000000
--- a/mobile/android/base/resources/drawable/tabs_panel_indicator.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <item android:state_focused="false"
- android:state_selected="false"
- android:state_pressed="false"
- android:drawable="@android:color/transparent"/>
-
- <item gecko:state_private="true"
- android:state_focused="false"
- android:state_selected="true"
- android:state_pressed="false"
- android:drawable="@drawable/tabs_panel_indicator_selected_private"/>
-
- <item android:state_focused="false"
- android:state_selected="true"
- android:state_pressed="false"
- android:drawable="@drawable/tabs_panel_indicator_selected"/>
-
- <item android:state_focused="true"
- android:state_selected="false"
- android:state_pressed="false"
- android:drawable="@color/highlight_dark_focused"/>
-
- <item android:state_focused="true"
- android:state_selected="true"
- android:state_pressed="false"
- android:drawable="@drawable/tab_indicator_selected_focused"/>
-
- <item android:state_focused="false"
- android:state_selected="false"
- android:state_pressed="true"
- android:drawable="@color/highlight_dark"/>
-
- <item android:state_focused="false"
- android:state_selected="true"
- android:state_pressed="true"
- android:drawable="@color/highlight_dark"/>
-
- <item android:state_focused="true"
- android:state_selected="false"
- android:state_pressed="true"
- android:drawable="@color/highlight_dark"/>
-
- <item android:state_focused="true"
- android:state_selected="true"
- android:state_pressed="true"
- android:drawable="@color/highlight_dark"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/tabs_panel_indicator_selected.xml b/mobile/android/base/resources/drawable/tabs_panel_indicator_selected.xml
deleted file mode 100644
index c74b343e5..000000000
--- a/mobile/android/base/resources/drawable/tabs_panel_indicator_selected.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetTop="@dimen/tabs_panel_indicator_selected_padding_top"
- android:drawable="@color/fennec_ui_orange"
- />
diff --git a/mobile/android/base/resources/drawable/tabs_panel_indicator_selected_private.xml b/mobile/android/base/resources/drawable/tabs_panel_indicator_selected_private.xml
deleted file mode 100644
index 93dc04986..000000000
--- a/mobile/android/base/resources/drawable/tabs_panel_indicator_selected_private.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetTop="@dimen/tabs_panel_indicator_selected_padding_top"
- android:drawable="@color/private_browsing_purple"
- />
diff --git a/mobile/android/base/resources/drawable/tabs_strip_indicator.xml b/mobile/android/base/resources/drawable/tabs_strip_indicator.xml
deleted file mode 100644
index 32ca3115a..000000000
--- a/mobile/android/base/resources/drawable/tabs_strip_indicator.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_focused="false"
- android:state_selected="false"
- android:state_pressed="false"
- android:drawable="@android:color/transparent"/>
-
- <item android:state_focused="false"
- android:state_selected="true"
- android:state_pressed="false"
- android:drawable="@drawable/tab_indicator_selected"/>
-
- <item android:state_focused="true"
- android:state_selected="false"
- android:state_pressed="false"
- android:drawable="@color/highlight_dark_focused"/>
-
- <item android:state_focused="true"
- android:state_selected="true"
- android:state_pressed="false"
- android:drawable="@drawable/tab_indicator_selected_focused"/>
-
- <item android:state_focused="false"
- android:state_selected="false"
- android:state_pressed="true"
- android:drawable="@color/highlight_dark"/>
-
- <item android:state_focused="false"
- android:state_selected="true"
- android:state_pressed="true"
- android:drawable="@color/highlight_dark"/>
-
- <item android:state_focused="true"
- android:state_selected="false"
- android:state_pressed="true"
- android:drawable="@color/highlight_dark"/>
-
- <item android:state_focused="true"
- android:state_selected="true"
- android:state_pressed="true"
- android:drawable="@color/highlight_dark"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/toast_background.xml b/mobile/android/base/resources/drawable/toast_background.xml
deleted file mode 100644
index 55cd9d9b2..000000000
--- a/mobile/android/base/resources/drawable/toast_background.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <solid android:color="@color/toast_background" />
- <corners android:radius="@dimen/toast_button_corner_radius" />
-</shape>
diff --git a/mobile/android/base/resources/drawable/toast_button_background.xml b/mobile/android/base/resources/drawable/toast_button_background.xml
deleted file mode 100644
index 6570d9b45..000000000
--- a/mobile/android/base/resources/drawable/toast_button_background.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- On Android pre v12/3.0/Gingerbread, bottom left and bottom
- right are swapped. These values correct this bug; the resources
- that don't need correction are in res/drawable-v12. -->
- <item android:state_pressed="true">
- <shape android:shape="rectangle">
- <solid android:color="@color/toast_button_pressed" />
- <corners
- android:topRightRadius="@dimen/toast_button_corner_radius"
- android:bottomLeftRadius="@dimen/toast_button_corner_radius"
- android:topLeftRadius="0dp"
- android:bottomRightRadius="0dp" />
- </shape>
- </item>
- <item>
- <shape android:shape="rectangle">
- <solid android:color="@color/toast_button_background" />
- <corners
- android:topRightRadius="@dimen/toast_button_corner_radius"
- android:bottomLeftRadius="@dimen/toast_button_corner_radius"
- android:topLeftRadius="0dp"
- android:bottomRightRadius="0dp" />
- </shape>
- </item>
-</selector>
diff --git a/mobile/android/base/resources/drawable/toolbar_favicon_default.xml b/mobile/android/base/resources/drawable/toolbar_favicon_default.xml
deleted file mode 100644
index 92e294fb3..000000000
--- a/mobile/android/base/resources/drawable/toolbar_favicon_default.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
- android:src="@drawable/favicon_globe" />
diff --git a/mobile/android/base/resources/drawable/toolbar_grey_round.xml b/mobile/android/base/resources/drawable/toolbar_grey_round.xml
deleted file mode 100644
index ada0146dd..000000000
--- a/mobile/android/base/resources/drawable/toolbar_grey_round.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="@color/toolbar_grey"/>
- <corners android:radius="@dimen/standard_corner_radius"/>
-</shape>
-
diff --git a/mobile/android/base/resources/drawable/top_sites_thumbnail_bg.xml b/mobile/android/base/resources/drawable/top_sites_thumbnail_bg.xml
deleted file mode 100644
index 9d106441e..000000000
--- a/mobile/android/base/resources/drawable/top_sites_thumbnail_bg.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <size android:height="2dp"
- android:width="2dp"/>
- <solid android:color="#FFFFFFFF"/>
- <stroke android:width="1dp" android:color="#FFDDDDDD"/>
-</shape>
diff --git a/mobile/android/base/resources/drawable/url_bar_bg.xml b/mobile/android/base/resources/drawable/url_bar_bg.xml
deleted file mode 100644
index 52954c1ce..000000000
--- a/mobile/android/base/resources/drawable/url_bar_bg.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <!-- private browsing mode -->
- <item gecko:state_private="true" android:drawable="@color/tabs_tray_grey_pressed"/>
-
- <!-- normal mode -->
- <item android:drawable="@color/toolbar_grey"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/url_bar_entry.xml b/mobile/android/base/resources/drawable/url_bar_entry.xml
deleted file mode 100644
index 4090ceb8e..000000000
--- a/mobile/android/base/resources/drawable/url_bar_entry.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <!-- private browsing mode -->
- <item gecko:state_private="true"
- android:state_focused="true"
- android:drawable="@drawable/url_bar_entry_pressed_pb"/>
-
- <item gecko:state_private="true"
- android:state_pressed="true"
- android:drawable="@drawable/url_bar_entry_pressed_pb"/>
-
- <item gecko:state_private="true"
- android:state_selected="true"
- android:drawable="@drawable/url_bar_entry_pressed_pb"/>
-
- <item gecko:state_private="true"
- android:drawable="@drawable/url_bar_entry_default_pb"/>
-
- <!-- normal modes -->
- <item android:state_focused="true"
- android:drawable="@drawable/url_bar_entry_pressed"/>
-
- <item android:state_pressed="true"
- android:drawable="@drawable/url_bar_entry_pressed"/>
-
- <item android:state_selected="true"
- android:drawable="@drawable/url_bar_entry_pressed"/>
-
- <item android:drawable="@drawable/url_bar_entry_default"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/url_bar_nav_button.xml b/mobile/android/base/resources/drawable/url_bar_nav_button.xml
deleted file mode 100644
index 2afadaf5e..000000000
--- a/mobile/android/base/resources/drawable/url_bar_nav_button.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- This asset is properly available in large-* dirs so this null
- reference exists for build time on API 9 builds. -->
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
- android:src="@null"/>
diff --git a/mobile/android/base/resources/drawable/url_bar_translating_edge.xml b/mobile/android/base/resources/drawable/url_bar_translating_edge.xml
deleted file mode 100644
index 379499284..000000000
--- a/mobile/android/base/resources/drawable/url_bar_translating_edge.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<clip xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/url_bar_entry"
- android:clipOrientation="horizontal"
- android:gravity="right"/> \ No newline at end of file
diff --git a/mobile/android/base/resources/drawable/widget_button_left.xml b/mobile/android/base/resources/drawable/widget_button_left.xml
deleted file mode 100644
index 9891f8ada..000000000
--- a/mobile/android/base/resources/drawable/widget_button_left.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_pressed="true"
- android:drawable="@drawable/widget_button_left_pressed"/>
-
- <!-- The left button is gray in its off state -->
- <item android:drawable="@drawable/widget_button_left_default"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/widget_button_left_default.xml b/mobile/android/base/resources/drawable/widget_button_left_default.xml
deleted file mode 100644
index 7aff6bc1a..000000000
--- a/mobile/android/base/resources/drawable/widget_button_left_default.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- These drawables have to be wrapped in a layer-list in order to produce padding at
- the bottom of the drawable. That padding ensures the drawable doesn't block the
- orange strip in widget_bg.9.png -->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:bottom="@dimen/widget_bg_border_offset">
- <shape android:shape="rectangle">
- <corners android:topLeftRadius="@dimen/widget_drawable_corner_radius"
- android:topRightRadius="0dp"
- android:bottomLeftRadius="0dp"
- android:bottomRightRadius="0dp"/>
- <solid android:color="@color/toolbar_grey"/>
- </shape>
- </item>
-</layer-list>
diff --git a/mobile/android/base/resources/drawable/widget_button_left_pressed.xml b/mobile/android/base/resources/drawable/widget_button_left_pressed.xml
deleted file mode 100644
index d4ae5a715..000000000
--- a/mobile/android/base/resources/drawable/widget_button_left_pressed.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- These drawables have to be wrapped in a layer-list in order to produce padding at
- the bottom of the drawable. That padding ensures the drawable doesn't block the
- orange strip in widget_bg.9.png -->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:bottom="@dimen/widget_bg_border_offset">
- <shape android:shape="rectangle">
- <corners android:topLeftRadius="@dimen/widget_drawable_corner_radius"
- android:topRightRadius="0dp"
- android:bottomLeftRadius="0dp"
- android:bottomRightRadius="0dp"/>
- <solid android:color="@color/widget_button_pressed"/>
- </shape>
- </item>
-</layer-list>
diff --git a/mobile/android/base/resources/drawable/widget_button_middle.xml b/mobile/android/base/resources/drawable/widget_button_middle.xml
deleted file mode 100644
index e7d74b0cc..000000000
--- a/mobile/android/base/resources/drawable/widget_button_middle.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_pressed="true"
- android:drawable="@drawable/widget_button_middle_pressed"/>
-
- <item android:drawable="@android:color/transparent"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/widget_button_middle_pressed.xml b/mobile/android/base/resources/drawable/widget_button_middle_pressed.xml
deleted file mode 100644
index 19236d641..000000000
--- a/mobile/android/base/resources/drawable/widget_button_middle_pressed.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- These drawables have to be wrapped in a layer-list in order to produce padding at
- the bottom of the drawable. That padding ensures the drawable doesn't block the
- orange strip in widget_bg.9.png -->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:bottom="@dimen/widget_bg_border_offset">
- <shape android:shape="rectangle">
- <solid android:color="@color/widget_button_pressed"/>
- </shape>
- </item>
-</layer-list>
diff --git a/mobile/android/base/resources/drawable/widget_button_right.xml b/mobile/android/base/resources/drawable/widget_button_right.xml
deleted file mode 100644
index 54fae2018..000000000
--- a/mobile/android/base/resources/drawable/widget_button_right.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:state_pressed="true"
- android:drawable="@drawable/widget_button_right_pressed"/>
-
- <item android:drawable="@android:color/transparent"/>
-
-</selector>
diff --git a/mobile/android/base/resources/drawable/widget_button_right_pressed.xml b/mobile/android/base/resources/drawable/widget_button_right_pressed.xml
deleted file mode 100644
index 2ebf61489..000000000
--- a/mobile/android/base/resources/drawable/widget_button_right_pressed.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- These drawables have to be wrapped in a layer-list in order to produce padding at
- the bottom of the drawable. That padding ensures the drawable doesn't block the
- orange strip in widget_bg.9.png -->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:bottom="@dimen/widget_bg_border_offset">
- <shape android:shape="rectangle">
- <corners android:topLeftRadius="0dp"
- android:topRightRadius="@dimen/widget_drawable_corner_radius"
- android:bottomLeftRadius="0dp"
- android:bottomRightRadius="0dp"/>
- <solid android:color="@color/widget_button_pressed"/>
- </shape>
- </item>
-</layer-list>
diff --git a/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml b/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
deleted file mode 100644
index 77a968369..000000000
--- a/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
+++ /dev/null
@@ -1,153 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <ImageView android:id="@+id/url_bar_entry"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alignLeft="@+id/back"
- android:layout_toLeftOf="@id/menu_items"
- android:layout_marginLeft="@dimen/tablet_nav_button_width_half"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="10dp"
- android:duplicateParentState="true"
- android:clickable="false"
- android:focusable="false"
- android:background="@drawable/url_bar_entry"/>
-
- <!-- The attributes statically defined here are for the expanded
- forward button. We translate/hide the forward button in code -
- see BrowserToolbarTablet.animateForwardButton.
-
- (for alpha) We want the button hidden to start so alpha=0.
-
- (for layout_width) The visible area of the forward button is a
- nav_button_width and the non-visible area slides halfway
- under the back button. This non-visible area is used to
- ensure the forward button background fully covers the space
- to the right of the back button.
-
- (for layout_marginLeft) We left align with back,
- but only need to hide halfway underneath.
-
- (for paddingLeft) We use left padding to center the
- arrow in the visible area as opposed to the true width. -->
- <org.mozilla.gecko.toolbar.ForwardButton
- style="@style/UrlBar.ImageButton.BrowserToolbarColors"
- android:id="@+id/forward"
- android:layout_alignLeft="@id/back"
- android:contentDescription="@string/forward"
- android:layout_height="match_parent"
- android:paddingTop="0dp"
- android:paddingBottom="0dp"
- android:layout_marginTop="11.5dp"
- android:layout_marginBottom="11.5dp"
- android:layout_gravity="center_vertical"
- android:layout_centerVertical="true"
- android:src="@drawable/ic_menu_forward"
- android:background="@drawable/url_bar_nav_button"
- android:alpha="0"
- android:layout_width="@dimen/tablet_nav_button_width_plus_half"
- android:layout_marginLeft="@dimen/tablet_nav_button_width_half"
- android:paddingLeft="18dp"/>
-
- <org.mozilla.gecko.toolbar.BackButton android:id="@id/back"
- style="@style/UrlBar.ImageButton.BrowserToolbarColors"
- android:layout_width="@dimen/tablet_nav_button_width"
- android:layout_height="@dimen/tablet_nav_button_width"
- android:layout_centerVertical="true"
- android:layout_marginLeft="12dp"
- android:layout_alignParentLeft="true"
- android:src="@drawable/ic_menu_back"
- android:contentDescription="@string/back"
- android:background="@drawable/url_bar_nav_button"/>
-
- <org.mozilla.gecko.toolbar.ToolbarEditLayout android:id="@+id/edit_layout"
- style="@style/UrlBar.Button"
- android:paddingRight="12dp"
- android:visibility="gone"
- android:orientation="horizontal"
- android:layout_toRightOf="@id/back"
- android:layout_toLeftOf="@id/menu_items"/>
-
- <!-- Note: we set the padding on the site security icon to increase its tappable area. -->
- <org.mozilla.gecko.toolbar.ToolbarDisplayLayout android:id="@+id/display_layout"
- style="@style/UrlBar.Button.Container"
- android:layout_toRightOf="@id/back"
- android:layout_toLeftOf="@id/menu_items"
- android:paddingRight="4dip"/>
-
- <LinearLayout android:id="@+id/menu_items"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="center_vertical"
- android:layout_marginLeft="6dp"
- android:orientation="horizontal"
- android:layout_toLeftOf="@id/tabs"/>
-
- <org.mozilla.gecko.widget.themed.ThemedImageButton
- android:id="@+id/tabs"
- style="@style/UrlBar.ImageButton"
- android:layout_toLeftOf="@id/menu"
- android:layout_alignWithParentIfMissing="true"
- android:background="@drawable/browser_toolbar_action_bar_button"/>
-
- <!-- In a 56x60dp space, centering 24dp image will leave 16x18dp. -->
- <org.mozilla.gecko.toolbar.TabCounter android:id="@+id/tabs_counter"
- style="@style/UrlBar.ImageButton"
- android:layout_alignLeft="@id/tabs"
- android:layout_alignRight="@id/tabs"
- android:layout_alignTop="@id/tabs"
- android:layout_alignBottom="@id/tabs"
- android:layout_marginTop="18dp"
- android:layout_marginBottom="18dp"
- android:layout_marginLeft="16dp"
- android:layout_marginRight="16dp"
- android:background="@drawable/tabs_count"/>
-
- <!-- Bug 1144707. Use clickable View instead of menu button margin to prevent
- edit mode actiivation when user clicks on the edge of the screen. -->
- <View android:id="@id/menu_margin"
- android:layout_width="6dp"
- android:layout_height="match_parent"
- android:layout_alignParentRight="true"
- android:clickable="true"
- android:visibility="gone"/>
-
- <org.mozilla.gecko.widget.themed.ThemedFrameLayout
- android:id="@+id/menu"
- style="@style/UrlBar.ImageButton"
- android:layout_toLeftOf="@id/menu_margin"
- android:layout_alignWithParentIfMissing="true"
- android:contentDescription="@string/menu"
- android:background="@drawable/browser_toolbar_action_bar_button">
-
- <org.mozilla.gecko.widget.themed.ThemedImageView
- android:id="@+id/menu_icon"
- style="@style/UrlBar.ImageButton.BrowserToolbarColors"
- android:layout_height="@dimen/browser_toolbar_menu_icon_height"
- android:layout_width="wrap_content"
- android:scaleType="centerInside"
- android:src="@drawable/menu"
- android:layout_gravity="center"/>
-
- </org.mozilla.gecko.widget.themed.ThemedFrameLayout>
-
- <!-- We draw after the menu items so when they are hidden, the cancel button,
- which is thus drawn on top, may be pressed. -->
- <org.mozilla.gecko.widget.themed.ThemedImageView
- android:id="@+id/edit_cancel"
- style="@style/UrlBar.ImageButton"
- android:layout_width="@dimen/browser_toolbar_icon_width"
- android:layout_height="@dimen/browser_toolbar_height"
- android:layout_weight="0.0"
- android:layout_alignParentRight="true"
- android:src="@drawable/close_edit_mode_selector"
- android:contentDescription="@string/edit_mode_cancel"
- android:visibility="gone"/>
-
-</merge>
diff --git a/mobile/android/base/resources/layout-large-v11/tabs_counter.xml b/mobile/android/base/resources/layout-large-v11/tabs_counter.xml
deleted file mode 100644
index df771a231..000000000
--- a/mobile/android/base/resources/layout-large-v11/tabs_counter.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.widget.themed.ThemedTextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="24dip"
- android:layout_height="24dip"
- android:paddingTop="3dip"
- android:paddingLeft="4dip"
- android:background="@drawable/tabs_count_foreground"
- android:textAppearance="@style/TextAppearance.Micro"
- android:textColor="@color/tabs_counter_text_color"
- android:textStyle="bold"
- android:duplicateParentState="true"
- android:gravity="center"/>
diff --git a/mobile/android/base/resources/layout-xlarge-v11/font_size_preference.xml b/mobile/android/base/resources/layout-xlarge-v11/font_size_preference.xml
deleted file mode 100644
index a406b55a2..000000000
--- a/mobile/android/base/resources/layout-xlarge-v11/font_size_preference.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this file,
- - You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <ScrollView android:id="@+id/scrolling_container"
- android:layout_width="match_parent"
- android:layout_height="350dp"
- android:layout_margin="8dp"
- android:padding="8dp"
- android:scrollbars="vertical"
- android:scrollbarStyle="outsideOverlay"
- android:fadeScrollbars="true"
- android:requiresFadingEdge="vertical">
-
- <TextView android:id="@+id/preview"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/pref_font_size_preview_text"
- android:textColor="#ff000000"/>
-
- </ScrollView>
-
- <LinearLayout android:id="@+id/button_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginLeft="4dp"
- android:layout_marginRight="4dp"
- android:orientation="horizontal">
-
- <Button android:id="@+id/decrease_preview_font_button"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:text="@string/pref_font_size_adjust_char"
- android:textSize="8sp"/>
-
- <Button android:id="@+id/increase_preview_font_button"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:text="@string/pref_font_size_adjust_char"
- android:textSize="16sp"/>
-
- </LinearLayout>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/actionbar.xml b/mobile/android/base/resources/layout/actionbar.xml
deleted file mode 100644
index ecb5124de..000000000
--- a/mobile/android/base/resources/layout/actionbar.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
- <Button android:id="@+id/actionmode_title"
- android:layout_height="match_parent"
- android:layout_width="wrap_content"
- style="@style/GeckoActionBar.Title"/>
-
- <!-- Draw a separator to the left of the title -->
- <View android:layout_height="match_parent"
- android:layout_width="1dp"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="10dp"
- android:background="@color/text_color_secondary_inverse"/>
-
- <LinearLayout android:id="@+id/actionbar_buttons"
- android:layout_height="match_parent"
- android:layout_width="0dip"
- android:layout_weight="1"
- style="@style/GeckoActionBar.Buttons"/>
-
- <ImageButton android:id="@+id/actionbar_menu"
- android:layout_height="match_parent"
- android:layout_width="@dimen/browser_toolbar_icon_width"
- style="@style/GeckoActionBar.Button.MenuButton"/>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/activity_stream.xml b/mobile/android/base/resources/layout/activity_stream.xml
deleted file mode 100644
index b40c01cde..000000000
--- a/mobile/android/base/resources/layout/activity_stream.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<org.mozilla.gecko.home.activitystream.ActivityStreamHomeScreen xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#FAFAFA"/>
diff --git a/mobile/android/base/resources/layout/activity_stream_card_history_item.xml b/mobile/android/base/resources/layout/activity_stream_card_history_item.xml
deleted file mode 100644
index 7f411278d..000000000
--- a/mobile/android/base/resources/layout/activity_stream_card_history_item.xml
+++ /dev/null
@@ -1,123 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="10dp"
- android:layout_marginEnd="@dimen/activity_stream_base_margin"
- android:layout_marginLeft="@dimen/activity_stream_base_margin"
- android:layout_marginRight="@dimen/activity_stream_base_margin"
- android:layout_marginStart="@dimen/activity_stream_base_margin"
- android:layout_marginTop="0dp"
- android:orientation="vertical">
-
- <RelativeLayout
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:background="?android:attr/selectableItemBackground">
-
- <org.mozilla.gecko.widget.FaviconView
- android:id="@+id/icon"
- android:layout_width="@dimen/favicon_bg"
- android:layout_height="@dimen/favicon_bg"
- android:layout_marginLeft="@dimen/activity_stream_base_margin"
- android:layout_marginStart="@dimen/activity_stream_base_margin"
- android:layout_marginTop="@dimen/activity_stream_base_margin"
- android:layout_marginBottom="@dimen/activity_stream_base_margin"
- android:layout_gravity="center"
- gecko:enableRoundCorners="false"
- tools:background="@drawable/favicon_globe" />
-
- <ImageView
- android:id="@+id/menu"
- android:layout_width="wrap_content"
- android:layout_height="36dp"
- android:layout_margin="2dp"
- android:layout_alignParentEnd="true"
- android:layout_alignParentRight="true"
- android:layout_alignParentTop="true"
- android:layout_gravity="right|top"
- android:contentDescription="@string/menu"
- android:src="@drawable/menu"
- android:padding="@dimen/activity_stream_base_margin" />
-
- <TextView
- android:id="@+id/page"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- tools:text="twitter"
- android:textSize="12sp"
- android:textColor="@color/activity_stream_subtitle"
- android:layout_toRightOf="@id/icon"
- android:layout_toEndOf="@id/icon"
- android:layout_toLeftOf="@id/menu"
- android:layout_toStartOf="@id/menu"
- android:paddingTop="@dimen/activity_stream_base_margin"
- android:paddingLeft="@dimen/activity_stream_base_margin"
- android:paddingStart="@dimen/activity_stream_base_margin"/>
-
- <TextView
- android:id="@+id/card_history_label"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_toEndOf="@id/icon"
- android:layout_toRightOf="@id/icon"
- android:maxLines="3"
- android:ellipsize="end"
- android:paddingLeft="@dimen/activity_stream_base_margin"
- android:paddingStart="@dimen/activity_stream_base_margin"
- android:textSize="14sp"
- android:textStyle="bold"
- android:textColor="#ff000000"
- android:layout_below="@id/page"
- android:layout_toLeftOf="@id/menu"
- android:layout_toStartOf="@id/menu"
- tools:text="Descriptive title of a page that is veeeeeeery long - maybe even too long?" />
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_toRightOf="@id/icon"
- android:layout_toEndOf="@id/icon"
- android:layout_alignParentBottom="true"
- android:paddingLeft="@dimen/activity_stream_base_margin"
- android:paddingStart="@dimen/activity_stream_base_margin"
- android:paddingRight="@dimen/activity_stream_base_margin"
- android:paddingEnd="@dimen/activity_stream_base_margin"
- android:paddingTop="4dp"
- android:paddingBottom="@dimen/activity_stream_base_margin"
- android:gravity="center_vertical"
- android:layout_below="@id/card_history_label">
-
- <ImageView
- android:id="@+id/source_icon"
- android:layout_width="12dp"
- android:layout_height="12dp" />
-
- <TextView
- android:id="@+id/card_history_source"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginLeft="2dp"
- android:textSize="12sp"
- android:layout_weight="1"
- android:textColor="@color/activity_stream_subtitle"
- tools:text="Bookmarked" />
-
- <TextView
- android:id="@+id/card_history_time_since"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="12sp"
- android:textColor="@color/activity_stream_timestamp"
- tools:text="20m" />
-
- </LinearLayout>
- </RelativeLayout>
-</android.support.v7.widget.CardView>
diff --git a/mobile/android/base/resources/layout/activity_stream_contextmenu_bottomsheet.xml b/mobile/android/base/resources/layout/activity_stream_contextmenu_bottomsheet.xml
deleted file mode 100644
index 327587e6e..000000000
--- a/mobile/android/base/resources/layout/activity_stream_contextmenu_bottomsheet.xml
+++ /dev/null
@@ -1,78 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <RelativeLayout
- android:id="@+id/info_wrapper"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:padding="10dp">
-
- <org.mozilla.gecko.widget.FaviconView
- android:id="@+id/icon"
- android:layout_width="@dimen/favicon_bg"
- android:layout_height="@dimen/favicon_bg"
- android:layout_gravity="center"
- gecko:enableRoundCorners="false"
- tools:background="@drawable/favicon_globe"/>
-
- <TextView
- android:id="@+id/url"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_toEndOf="@id/icon"
- android:layout_toRightOf="@id/icon"
- android:paddingLeft="@dimen/activity_stream_base_margin"
- android:paddingStart="@dimen/activity_stream_base_margin"
- android:textColor="@color/activity_stream_subtitle"
- android:textSize="12sp"
- tools:text="twitter"/>
-
- <TextView
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/url"
- android:layout_toEndOf="@id/icon"
- android:layout_toRightOf="@id/icon"
- android:ellipsize="end"
- android:maxLines="3"
- android:paddingLeft="@dimen/activity_stream_base_margin"
- android:paddingStart="@dimen/activity_stream_base_margin"
- android:textColor="#ff000000"
- android:textSize="14sp"
- android:textStyle="bold"
- tools:text="Descriptive title of a page that is veeeeeeery long - maybe even too long?"/>
- </RelativeLayout>
-
- <View
- android:layout_width="match_parent"
- android:layout_height="0.5dp"
- android:layout_marginTop="4dp"
- android:background="@color/disabled_grey"
- android:padding="4dp"/>
-
- <android.support.v4.widget.NestedScrollView
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <android.support.design.widget.NavigationView
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/menu"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- app:itemTextAppearance="@style/ActivityStreamContextMenuText"
- android:theme="@style/ActivityStreamContextMenuStyle"
- app:menu="@menu/activitystream_contextmenu"/>
-
- </android.support.v4.widget.NestedScrollView>
-
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/activity_stream_contextmenu_popupmenu.xml b/mobile/android/base/resources/layout/activity_stream_contextmenu_popupmenu.xml
deleted file mode 100644
index a21d67528..000000000
--- a/mobile/android/base/resources/layout/activity_stream_contextmenu_popupmenu.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:layout_width="32dp"
- android:layout_height="200dp"
- app:cardElevation="5dp"
- app:cardUseCompatPadding="true">
-
- <!-- This is mostly a copy of the same menu in activity_stream_contextmenu_bottomsheet.xml,
- however for the popup menu we don't need to override the dividers, hence we omit the
- android:theme override -->
- <android.support.design.widget.NavigationView
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/menu"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- app:itemTextAppearance="@style/ActivityStreamContextMenuText"
- app:menu="@menu/activitystream_contextmenu"/>
-
-</android.support.v7.widget.CardView> \ No newline at end of file
diff --git a/mobile/android/base/resources/layout/activity_stream_main_highlightstitle.xml b/mobile/android/base/resources/layout/activity_stream_main_highlightstitle.xml
deleted file mode 100644
index 7338c8596..000000000
--- a/mobile/android/base/resources/layout/activity_stream_main_highlightstitle.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <View
- android:id="@+id/divider"
- android:layout_width="match_parent"
- android:layout_height="0.5dp"
- android:padding="4dp"
- android:background="#ffe0e0e0" />
-
- <TextView
- android:id="@+id/title_highlights"
- android:layout_marginLeft="@dimen/activity_stream_base_margin"
- android:layout_marginStart="@dimen/activity_stream_base_margin"
- android:layout_marginTop="@dimen/activity_stream_base_margin"
- android:layout_marginBottom="@dimen/activity_stream_base_margin"
- android:layout_marginRight="@dimen/activity_stream_base_margin"
- android:layout_marginEnd="@dimen/activity_stream_base_margin"
- android:text="@string/activity_stream_highlights"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textStyle="bold"
- android:textSize="16sp"
- android:textColor="#FF858585" />
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/activity_stream_main_toppanel.xml b/mobile/android/base/resources/layout/activity_stream_main_toppanel.xml
deleted file mode 100644
index 60c420063..000000000
--- a/mobile/android/base/resources/layout/activity_stream_main_toppanel.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <android.support.v4.view.ViewPager
- android:layout_marginTop="10dp"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/topsites_pager"
- android:contentDescription="@string/activity_stream_topsites" />
-
- <org.mozilla.gecko.home.activitystream.topsites.CirclePageIndicator
- android:id="@+id/topsites_indicator"
- android:padding="10dip"
- app:fillColor="#ff9d9d9d"
- app:pageColor="#FFFFFF"
- app:strokeWidth="1dp"
- app:radius="2dp"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/activity_stream_topsites_card.xml b/mobile/android/base/resources/layout/activity_stream_topsites_card.xml
deleted file mode 100644
index 8cb288b2f..000000000
--- a/mobile/android/base/resources/layout/activity_stream_topsites_card.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<org.mozilla.gecko.widget.FilledCardView xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
-
- <RelativeLayout
- android:id="@+id/content"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="?android:attr/selectableItemBackground">
-
- <org.mozilla.gecko.widget.FaviconView
- android:id="@+id/favicon"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_above="@+id/title"
- android:layout_alignParentTop="true"
- android:layout_centerHorizontal="true"
- android:layout_gravity="center"
- gecko:enableRoundCorners="false"
- tools:background="@drawable/favicon_globe" />
-
- <View
- android:layout_width="match_parent"
- android:layout_height="0.5dp"
- android:layout_below="@id/favicon"
- android:background="@color/activity_stream_divider" />
-
- <TextView
- android:id="@+id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- android:layout_alignParentEnd="true"
- android:layout_alignParentLeft="true"
- android:layout_alignParentRight="true"
- android:layout_alignParentStart="true"
- android:ellipsize="end"
- android:gravity="center"
- android:lines="1"
- android:padding="4dp"
- android:textSize="12sp"
- android:textColor="@android:color/black"
- tools:text="Lorem Ipsum here is a title" />
-
- <ImageView
- android:id="@+id/menu"
- android:layout_width="wrap_content"
- android:layout_height="28dp"
- android:layout_alignParentEnd="true"
- android:layout_alignParentRight="true"
- android:layout_alignParentTop="true"
- android:layout_gravity="right|top"
- android:padding="6dp"
- android:contentDescription="@string/menu"
- android:src="@drawable/menu" />
-
- </RelativeLayout>
-</org.mozilla.gecko.widget.FilledCardView> \ No newline at end of file
diff --git a/mobile/android/base/resources/layout/activity_stream_topsites_page.xml b/mobile/android/base/resources/layout/activity_stream_topsites_page.xml
deleted file mode 100644
index 78399f7fc..000000000
--- a/mobile/android/base/resources/layout/activity_stream_topsites_page.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<org.mozilla.gecko.home.activitystream.topsites.TopSitesPage xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:paddingLeft="10dp"/>
diff --git a/mobile/android/base/resources/layout/anchored_popup.xml b/mobile/android/base/resources/layout/anchored_popup.xml
deleted file mode 100644
index bc4b61493..000000000
--- a/mobile/android/base/resources/layout/anchored_popup.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@drawable/dropshadow"
- android:paddingLeft="3dp"
- android:paddingRight="3dp"
- android:paddingTop="3dp"
- android:paddingBottom="4dp">
-
- <ScrollView android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <org.mozilla.gecko.widget.RoundedCornerLayout android:id="@+id/content"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@color/toolbar_grey"
- android:orientation="vertical"/>
-
- </ScrollView>
-</FrameLayout>
diff --git a/mobile/android/base/resources/layout/as_content.xml b/mobile/android/base/resources/layout/as_content.xml
deleted file mode 100644
index 780d00beb..000000000
--- a/mobile/android/base/resources/layout/as_content.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
- <android.support.v7.widget.RecyclerView
- android:id="@+id/activity_stream_main_recyclerview"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
-</merge> \ No newline at end of file
diff --git a/mobile/android/base/resources/layout/autocomplete_list.xml b/mobile/android/base/resources/layout/autocomplete_list.xml
deleted file mode 100644
index 78487d42c..000000000
--- a/mobile/android/base/resources/layout/autocomplete_list.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<ListView xmlns:android="http://schemas.android.com/apk/res/android"
- style="@android:style/Widget.Holo.ListView"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@drawable/autocomplete_list_bg"
- android:cacheColorHint="#ffffff"/>
diff --git a/mobile/android/base/resources/layout/autocomplete_list_item.xml b/mobile/android/base/resources/layout/autocomplete_list_item.xml
deleted file mode 100644
index b8bbf6fd5..000000000
--- a/mobile/android/base/resources/layout/autocomplete_list_item.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="32dip"
- android:textAppearance="@style/TextAppearance.Medium"
- android:layout_gravity="center_vertical"
- android:paddingLeft="10dp"
- android:paddingRight="10dp"
- android:paddingTop="3dp"
- android:paddingBottom="3dp"/>
diff --git a/mobile/android/base/resources/layout/basic_color_picker_dialog.xml b/mobile/android/base/resources/layout/basic_color_picker_dialog.xml
deleted file mode 100644
index 4efb341d5..000000000
--- a/mobile/android/base/resources/layout/basic_color_picker_dialog.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:orientation="vertical">
-
- <org.mozilla.gecko.widget.BasicColorPicker android:id="@+id/colorpicker"
- android:layout_height="0dip"
- android:layout_weight="1"
- android:drawSelectorOnTop="true"
- android:choiceMode="singleChoice"
- android:divider="@android:color/transparent"
- android:dividerHeight="0dip"
- android:listSelector="#22FFFFFF"
- android:layout_width="match_parent"/>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/bookmark_edit.xml b/mobile/android/base/resources/layout/bookmark_edit.xml
deleted file mode 100644
index cf59096c8..000000000
--- a/mobile/android/base/resources/layout/bookmark_edit.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:orientation="vertical"
- android:layout_height="match_parent">
-
- <android.support.design.widget.TextInputLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <EditText
- android:id="@+id/edit_bookmark_name"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:hint="@string/bookmark_edit_name"
- />
- </android.support.design.widget.TextInputLayout>
-
- <android.support.design.widget.TextInputLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <EditText
- android:id="@+id/edit_bookmark_location"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:hint="@string/bookmark_edit_location"
- android:inputType="textNoSuggestions"/>
- </android.support.design.widget.TextInputLayout>
-
- <android.support.design.widget.TextInputLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <EditText
- android:id="@+id/edit_bookmark_keyword"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:hint="@string/bookmark_edit_keyword"/>
- </android.support.design.widget.TextInputLayout>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/bookmark_folder_row.xml b/mobile/android/base/resources/layout/bookmark_folder_row.xml
deleted file mode 100644
index dcf9620cd..000000000
--- a/mobile/android/base/resources/layout/bookmark_folder_row.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.home.BookmarkFolderView xmlns:android="http://schemas.android.com/apk/res/android"
- style="@style/Widget.FolderView"
- android:layout_width="match_parent"
- android:paddingLeft="0dp"
- android:paddingTop="0dp"
- android:paddingBottom="0dp"
- android:paddingRight="16dp"
- android:gravity="center_vertical"/>
diff --git a/mobile/android/base/resources/layout/bookmark_item_row.xml b/mobile/android/base/resources/layout/bookmark_item_row.xml
deleted file mode 100644
index 71d4f532f..000000000
--- a/mobile/android/base/resources/layout/bookmark_item_row.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.home.TwoLinePageRow xmlns:android="http://schemas.android.com/apk/res/android"
- style="@style/Widget.BookmarkItemView"
- android:layout_width="match_parent"
- android:layout_height="@dimen/page_row_height"
- android:minHeight="@dimen/page_row_height"/>
diff --git a/mobile/android/base/resources/layout/bookmark_screenshot_row.xml b/mobile/android/base/resources/layout/bookmark_screenshot_row.xml
deleted file mode 100644
index cdb3912c2..000000000
--- a/mobile/android/base/resources/layout/bookmark_screenshot_row.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.home.BookmarkScreenshotRow
- xmlns:android="http://schemas.android.com/apk/res/android" xmlns:gecko="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="@dimen/page_row_height"
- android:minHeight="@dimen/page_row_height"
- android:orientation="vertical"
- android:gravity="center_vertical"
- android:paddingLeft="16dp"
- android:paddingRight="16dp"
->
-
- <org.mozilla.gecko.widget.FadedSingleColorTextView
- android:id="@+id/title"
- style="@style/Widget.TwoLinePageRow.Title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- gecko:fadeWidth="30dp"
- />
-
- <org.mozilla.gecko.widget.FadedSingleColorTextView
- android:id="@+id/date"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.Widget.Home.ItemDescription"
- android:textColor="@color/link_blue"
- android:singleLine="true"
- gecko:fadeWidth="30dp"
- />
-
-</org.mozilla.gecko.home.BookmarkScreenshotRow>
diff --git a/mobile/android/base/resources/layout/browser_search.xml b/mobile/android/base/resources/layout/browser_search.xml
deleted file mode 100644
index 44ff340cf..000000000
--- a/mobile/android/base/resources/layout/browser_search.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <ViewStub android:id="@+id/suggestions_opt_in_prompt"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout="@layout/home_suggestion_prompt" />
-
- <view class="org.mozilla.gecko.home.BrowserSearch$HomeSearchListView"
- android:id="@+id/home_list_view"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1" />
-
- <!-- listSelector is too slow for showing pressed state
- so we set the pressed colors on the child. -->
- <org.mozilla.gecko.home.SearchEngineBar
- android:id="@+id/search_engine_bar"
- android:layout_width="match_parent"
- android:layout_height="48dp"
- android:paddingTop="1dp"
- android:orientation="horizontal"
- android:layout_gravity="center_horizontal"
- android:choiceMode="singleChoice"
- android:listSelector="@android:color/transparent"
- android:cacheColorHint="@android:color/transparent" />
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/browser_toolbar.xml b/mobile/android/base/resources/layout/browser_toolbar.xml
deleted file mode 100644
index 0413215f8..000000000
--- a/mobile/android/base/resources/layout/browser_toolbar.xml
+++ /dev/null
@@ -1,112 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
- <!-- Note: any layout parameters setting the right edge of
- this View should be matched in the url_bar_translating_edge. -->
- <ImageView android:id="@+id/url_bar_entry"
- style="@style/UrlBar.Button"
- android:layout_marginLeft="8dp"
- android:layout_marginRight="-6dp"
- android:layout_marginTop="8dp"
- android:layout_marginBottom="8dp"
- android:layout_toLeftOf="@+id/tabs"
- android:duplicateParentState="true"
- android:clickable="false"
- android:focusable="false"
- android:src="@drawable/url_bar_entry"
- android:scaleType="fitXY"/>
-
- <!-- A View that clips with url_bar_entry and translates
- around it to animate growing the url bar,
- which occurs in the display/editing mode transitions. -->
- <ImageView android:id="@+id/url_bar_translating_edge"
- style="@style/UrlBar.Button"
- android:layout_alignLeft="@id/url_bar_entry"
- android:layout_alignRight="@+id/url_bar_entry"
- android:layout_alignTop="@id/url_bar_entry"
- android:layout_alignBottom="@id/url_bar_entry"
- android:duplicateParentState="true"
- android:clickable="false"
- android:focusable="false"
- android:visibility="invisible"
- android:src="@drawable/url_bar_translating_edge"
- android:scaleType="fitXY"/>
-
- <org.mozilla.gecko.toolbar.ShapedButtonFrameLayout
- android:id="@+id/menu"
- style="@style/UrlBar.ImageButton"
- android:layout_alignParentRight="true"
- android:contentDescription="@string/menu"
- android:background="@drawable/shaped_button">
-
- <org.mozilla.gecko.widget.themed.ThemedImageView
- android:id="@+id/menu_icon"
- style="@style/UrlBar.ImageButton"
- android:layout_height="@dimen/browser_toolbar_menu_icon_height"
- android:layout_width="wrap_content"
- android:scaleType="centerInside"
- android:layout_gravity="center"
- android:src="@drawable/menu"
- android:tint="@color/tabs_tray_icon_grey"/>
-
- </org.mozilla.gecko.toolbar.ShapedButtonFrameLayout>
-
- <org.mozilla.gecko.toolbar.PhoneTabsButton android:id="@+id/tabs"
- style="@style/UrlBar.ImageButton"
- android:layout_width="64dip"
- android:layout_toLeftOf="@id/menu"
- android:layout_alignWithParentIfMissing="true"
- android:background="@drawable/shaped_button"/>
-
- <!-- The TextSwitcher should be shifted 24dp on the left, to avoid
- the curve. On a 48dp space, centering 24dp image will leave
- 12dp on all sides. However this image has a perception of
- 2 layers. Hence to center this, an additional 4dp is added to the left.
- The margins will be 40dp on left, 8dp on right, instead of ideal 30dp
- and 12dp. -->
- <org.mozilla.gecko.toolbar.TabCounter android:id="@+id/tabs_counter"
- style="@style/UrlBar.ImageButton"
- android:layout_width="24dip"
- android:layout_height="24dip"
- android:layout_centerVertical="true"
- android:layout_marginRight="8dip"
- android:layout_alignRight="@id/tabs"
- android:background="@drawable/tabs_count"
- android:gravity="center_horizontal"
- android:clipChildren="false"
- android:clipToPadding="false"/>
-
- <!-- Note that the edit components are invisible so that the views
- depending on their location can properly layout. -->
- <org.mozilla.gecko.widget.themed.ThemedImageView
- android:id="@+id/edit_cancel"
- style="@style/UrlBar.ImageButton"
- android:layout_alignParentRight="true"
- android:src="@drawable/close_edit_mode_selector"
- android:contentDescription="@string/edit_mode_cancel"
- android:background="@drawable/action_bar_button"
- android:visibility="invisible"/>
-
- <!-- The space to the left of the cancel button would be larger than the right because
- the url bar drawable contains some whitespace, so we compensate by removing
- some padding from the right (value determined through experimentation). -->
- <org.mozilla.gecko.toolbar.ToolbarEditLayout android:id="@+id/edit_layout"
- style="@style/UrlBar.Button"
- android:layout_alignLeft="@id/url_bar_entry"
- android:layout_toLeftOf="@id/edit_cancel"
- android:visibility="invisible"
- android:paddingLeft="8dp"
- android:paddingRight="8dp"/>
-
- <org.mozilla.gecko.toolbar.ToolbarDisplayLayout android:id="@+id/display_layout"
- style="@style/UrlBar.Button"
- android:layout_alignLeft="@id/url_bar_entry"
- android:layout_alignRight="@id/url_bar_entry"
- android:paddingLeft="1dip"
- android:paddingRight="4dip"/>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/button_toast.xml b/mobile/android/base/resources/layout/button_toast.xml
deleted file mode 100644
index 0ca590365..000000000
--- a/mobile/android/base/resources/layout/button_toast.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/toast"
- style="@style/Toast">
-
- <TextView android:id="@+id/toast_message"
- style="@style/ToastMessage" />
-
- <View android:id="@+id/toast_divider"
- style="@style/ToastDivider" />
-
- <Button android:id="@+id/toast_button"
- style="@style/ToastButton" />
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/color_picker_row.xml b/mobile/android/base/resources/layout/color_picker_row.xml
deleted file mode 100644
index 716383f50..000000000
--- a/mobile/android/base/resources/layout/color_picker_row.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.Widget.TextView"
- style="@style/Widget.ListItem"
- android:background="@drawable/color_picker_row_bg"
- android:checkMark="@android:color/transparent"/>
diff --git a/mobile/android/base/resources/layout/customtabs_activity.xml b/mobile/android/base/resources/layout/customtabs_activity.xml
deleted file mode 100644
index 7ba9c07f6..000000000
--- a/mobile/android/base/resources/layout/customtabs_activity.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<RelativeLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/root_layout"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <!--
- This layout is quite complex because GeckoApp accesses all view groups
- in this tree. In a perfect world this should just include a GeckoView.
- -->
-
- <android.support.v7.widget.Toolbar
- android:id="@+id/toolbar"
- android:layout_width="match_parent"
- android:layout_height="?attr/actionBarSize"
- android:elevation="4dp"
- android:background="@color/text_and_tabs_tray_grey"
- app:layout_scrollFlags="scroll|enterAlways"/>
-
- <view class="org.mozilla.gecko.GeckoApp$MainLayout"
- android:id="@+id/main_layout"
- android:layout_width="match_parent"
- android:layout_below="@+id/toolbar"
- android:layout_height="match_parent"
- android:background="@android:color/transparent">
-
- <RelativeLayout android:id="@+id/gecko_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_below="@+id/tablet_tab_strip"
- android:layout_above="@+id/find_in_page">
-
- <fragment class="org.mozilla.gecko.GeckoViewFragment"
- android:id="@+id/layer_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scrollbars="none"/>
-
- </RelativeLayout>
-
- </view>
-
-</RelativeLayout> \ No newline at end of file
diff --git a/mobile/android/base/resources/layout/datetime_picker.xml b/mobile/android/base/resources/layout/datetime_picker.xml
deleted file mode 100644
index 66826f589..000000000
--- a/mobile/android/base/resources/layout/datetime_picker.xml
+++ /dev/null
@@ -1,138 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- Layout of date picker-->
-
-<!-- Warning: everything within the "pickers" layout is removed and re-ordered
- depending on the date format selected by the user.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/datetime_picker"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:orientation="vertical"
- android:gravity="center">
-
- <LinearLayout android:id="@+id/date_spinners"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_marginLeft="1dip"
- android:layout_marginRight="1dip"
- android:gravity="center">
-
- <!-- Month -->
- <android.widget.NumberPicker
- android:id="@+id/month"
- android:layout_width="60dip"
- android:layout_height="wrap_content"
- android:layout_marginLeft="1dip"
- android:layout_marginRight="1dip"
- android:focusable="true"
- android:focusableInTouchMode="true"
- />
-
- <!-- Week -->
- <android.widget.NumberPicker
- android:id="@+id/week"
- android:layout_width="60dip"
- android:layout_height="wrap_content"
- android:layout_marginLeft="1dip"
- android:layout_marginRight="1dip"
- android:focusable="true"
- android:focusableInTouchMode="true"
- />
-
- <!-- Day -->
- <android.widget.NumberPicker
- android:id="@+id/day"
- android:layout_width="60dip"
- android:layout_height="wrap_content"
- android:layout_marginLeft="1dip"
- android:layout_marginRight="1dip"
- android:focusable="true"
- android:focusableInTouchMode="true"
- />
-
- <!-- Year -->
- <android.widget.NumberPicker
- android:id="@+id/year"
- android:layout_width="75dip"
- android:layout_height="wrap_content"
- android:layout_marginLeft="1dip"
- android:layout_marginRight="1dip"
- android:focusable="true"
- android:focusableInTouchMode="true"
- />
-
- </LinearLayout>
-
- <LinearLayout android:id="@+id/time_spinners"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_marginLeft="1dip"
- android:layout_marginRight="1dip"
- android:gravity="center">
-
- <!-- Hour -->
- <android.widget.NumberPicker
- android:id="@+id/hour"
- android:layout_width="60dip"
- android:layout_height="wrap_content"
- android:layout_marginLeft="1dip"
- android:layout_marginRight="1dip"
- android:focusable="true"
- android:focusableInTouchMode="true"
- />
-
- <TextView android:id="@+id/mincolon"
- android:text="@string/colon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginLeft="1dip"
- android:layout_marginRight="1dip"/>
-
- <!-- Minute -->
- <android.widget.NumberPicker
- android:id="@+id/minute"
- android:layout_width="60dip"
- android:layout_height="wrap_content"
- android:layout_marginLeft="1dip"
- android:layout_marginRight="1dip"
- android:focusable="true"
- android:focusableInTouchMode="true"
- />
-
- <!-- AMPM -->
- <android.widget.NumberPicker
- android:id="@+id/ampm"
- android:layout_width="60dip"
- android:layout_height="wrap_content"
- android:layout_marginLeft="1dip"
- android:layout_marginRight="1dip"
- android:focusable="true"
- android:focusableInTouchMode="true"
- />
-
- </LinearLayout>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/default_doorhanger.xml b/mobile/android/base/resources/layout/default_doorhanger.xml
deleted file mode 100644
index fd8bf09aa..000000000
--- a/mobile/android/base/resources/layout/default_doorhanger.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <TextView android:id="@+id/doorhanger_message"
- android:focusable="true"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.DoorHanger.Medium"/>
-
- <LinearLayout android:id="@+id/doorhanger_inputs"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_marginTop="@dimen/doorhanger_section_padding_medium"
- android:gravity="right"
- android:visibility="gone"/>
-
- <android.support.v7.widget.AppCompatCheckBox
- android:id="@+id/doorhanger_checkbox"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/doorhanger_section_padding_medium"
- android:checked="true"
- android:textColor="@color/placeholder_active_grey"
- android:visibility="gone"/>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/doorhanger.xml b/mobile/android/base/resources/layout/doorhanger.xml
deleted file mode 100644
index f3ab4e1fe..000000000
--- a/mobile/android/base/resources/layout/doorhanger.xml
+++ /dev/null
@@ -1,76 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
- <LinearLayout android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:paddingLeft="@dimen/doorhanger_section_padding_small"
- android:paddingRight="@dimen/doorhanger_section_padding_small"
- android:paddingBottom="@dimen/doorhanger_section_padding_medium"
- android:paddingTop="@dimen/doorhanger_section_padding_medium">
-
- <ImageView android:id="@+id/doorhanger_icon"
- android:layout_width="@dimen/doorhanger_icon_size"
- android:layout_height="@dimen/doorhanger_icon_size"
- android:layout_gravity="center_horizontal"
- android:padding="@dimen/doorhanger_section_padding_small"
- android:layout_marginRight="@dimen/doorhanger_section_padding_small"
- android:visibility="gone"/>
-
- <LinearLayout android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <TextView android:id="@+id/doorhanger_title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/doorhanger_section_padding_medium"
- android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Light"
- android:visibility="gone"/>
-
- <ViewStub android:id="@+id/content"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
-
- <TextView android:id="@+id/doorhanger_link"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
- android:textColor="@color/link_blue"
- android:layout_marginTop="@dimen/doorhanger_section_padding_large"
- android:layout_marginBottom="@dimen/doorhanger_section_padding_small"
- android:visibility="gone"/>
-
- </LinearLayout>
-
- </LinearLayout>
-
- <LinearLayout android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
-
- <Button android:id="@+id/doorhanger_button_negative"
- style="@style/Widget.Doorhanger.Button"
- android:textColor="@android:color/black"
- android:background="@drawable/action_bar_button_negative"
- android:visibility="gone"/>
-
- <Button android:id="@+id/doorhanger_button_positive"
- style="@style/Widget.Doorhanger.Button"
- android:textColor="@android:color/white"
- android:background="@drawable/action_bar_button_positive"
- android:visibility="gone"/>
-
- </LinearLayout>
-
- <View android:id="@+id/divider_doorhanger"
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="@color/toolbar_divider_grey"
- android:visibility="gone"/>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/doorhanger_security.xml b/mobile/android/base/resources/layout/doorhanger_security.xml
deleted file mode 100644
index 8f4ddc963..000000000
--- a/mobile/android/base/resources/layout/doorhanger_security.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <TextView android:id="@+id/security_title"
- android:focusable="true"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingBottom="@dimen/doorhanger_subsection_padding"
- android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
- android:visibility="gone"/>
-
- <TextView android:id="@+id/security_state"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingBottom="@dimen/doorhanger_section_padding_small"
- android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Bold"
- android:visibility="gone"/>
-
- <TextView android:id="@+id/security_message"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.DoorHanger.Medium"/>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/find_in_page_content.xml b/mobile/android/base/resources/layout/find_in_page_content.xml
deleted file mode 100644
index 5505f90bc..000000000
--- a/mobile/android/base/resources/layout/find_in_page_content.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
- <view class="org.mozilla.gecko.CustomEditText"
- android:id="@+id/find_text"
- android:layout_width="0dip"
- android:layout_height="wrap_content"
- android:layout_weight="1.0"
- android:layout_marginLeft="@dimen/find_in_page_text_margin_left"
- android:layout_marginRight="@dimen/find_in_page_text_margin_right"
- android:contentDescription="@string/find_text"
- android:background="@drawable/url_bar_entry"
- android:singleLine="true"
- android:textColor="#000000"
- android:textCursorDrawable="@null"
- android:inputType="text"
- android:paddingLeft="@dimen/find_in_page_text_padding_left"
- android:paddingRight="@dimen/find_in_page_text_padding_right"
- android:textColorHighlight="@color/fennec_ui_orange"
- android:imeOptions="actionSearch"
- android:selectAllOnFocus="true"
- android:gravity="center_vertical|left"/>
-
- <TextView android:id="@+id/find_status"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="@dimen/find_in_page_status_margin_right"
- android:textColor="@color/tabs_tray_icon_grey"
- android:visibility="gone"/>
-
- <ImageButton android:id="@+id/find_prev"
- style="@style/FindBar.ImageButton"
- android:contentDescription="@string/find_prev"
- android:layout_marginTop="@dimen/find_in_page_control_margin_top"
- android:src="@drawable/find_prev"/>
-
- <ImageButton android:id="@+id/find_next"
- style="@style/FindBar.ImageButton"
- android:contentDescription="@string/find_next"
- android:layout_marginTop="@dimen/find_in_page_control_margin_top"
- android:src="@drawable/find_next"/>
-
- <ImageButton android:id="@+id/find_close"
- style="@style/FindBar.ImageButton"
- android:contentDescription="@string/find_close"
- android:layout_marginTop="@dimen/find_in_page_control_margin_top"
- android:src="@drawable/find_close"/>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/firstrun_animation_container.xml b/mobile/android/base/resources/layout/firstrun_animation_container.xml
deleted file mode 100644
index 3e7225365..000000000
--- a/mobile/android/base/resources/layout/firstrun_animation_container.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.firstrun.FirstrunAnimationContainer xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:background="@color/dark_transparent_overlay">
-
- <org.mozilla.gecko.firstrun.FirstrunPager
- android:id="@+id/firstrun_pager"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@android:color/white">
-
- <org.mozilla.gecko.home.TabMenuStrip android:layout_width="match_parent"
- android:layout_height="@dimen/tabs_strip_height"
- android:background="@color/firstrun_pager_header"
- android:visibility="visible"
- android:layout_gravity="top"
- gecko:strip="@drawable/home_tab_menu_strip"
- gecko:activeTextColor="@color/placeholder_grey"
- gecko:inactiveTextColor="@color/tab_text_color"
- gecko:tabsMarginLeft="@dimen/firstrun_tab_strip_content_start" />
-
- </org.mozilla.gecko.firstrun.FirstrunPager>
-</org.mozilla.gecko.firstrun.FirstrunAnimationContainer>
diff --git a/mobile/android/base/resources/layout/firstrun_basepanel_checkable_fragment.xml b/mobile/android/base/resources/layout/firstrun_basepanel_checkable_fragment.xml
deleted file mode 100644
index feedab735..000000000
--- a/mobile/android/base/resources/layout/firstrun_basepanel_checkable_fragment.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:orientation="vertical"
- android:fillViewport="true">
-
- <LinearLayout android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="@dimen/firstrun_min_height"
- android:background="@color/about_page_header_grey"
- android:gravity="center_horizontal"
- android:orientation="vertical">
-
- <ImageView android:id="@+id/firstrun_image"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/firstrun_background_height"
- android:layout_marginTop="40dp"
- android:layout_marginBottom="40dp"
- android:scaleType="fitCenter"
- android:layout_gravity="center"
- android:adjustViewBounds="true"/>
-
- <TextView android:id="@+id/firstrun_text"
- android:layout_width="@dimen/firstrun_content_width"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:textAppearance="@style/TextAppearance.FirstrunLight.Main"/>
-
- <TextView android:id="@+id/firstrun_subtext"
- android:layout_width="@dimen/firstrun_content_width"
- android:layout_height="wrap_content"
- android:paddingTop="20dp"
- android:paddingBottom="30dp"
- android:gravity="center"
- android:textAppearance="@style/TextAppearance.FirstrunRegular.Body"/>
-
- <android.support.v7.widget.SwitchCompat
- android:id="@+id/firstrun_switch"
- android:layout_width="wrap_content"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:visibility="invisible"/>
-
- <TextView android:id="@+id/firstrun_link"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="30dp"
- android:gravity="center"
- android:textAppearance="@style/TextAppearance.FirstrunRegular.Link"
- android:text="@string/firstrun_button_next"/>
- </LinearLayout>
-</ScrollView>
diff --git a/mobile/android/base/resources/layout/firstrun_sync_fragment.xml b/mobile/android/base/resources/layout/firstrun_sync_fragment.xml
deleted file mode 100644
index 46fec6d5d..000000000
--- a/mobile/android/base/resources/layout/firstrun_sync_fragment.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:orientation="vertical"
- android:fillViewport="true">
-
- <LinearLayout android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="@dimen/firstrun_min_height"
- android:background="@color/about_page_header_grey"
- android:gravity="center_horizontal"
- android:orientation="vertical">
-
-
- <ImageView android:id="@+id/firstrun_image"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/firstrun_background_height"
- android:layout_marginTop="40dp"
- android:layout_marginBottom="40dp"
- android:scaleType="fitCenter"
- android:layout_gravity="center"
- android:adjustViewBounds="true"/>
-
- <TextView android:id="@+id/firstrun_text"
- android:layout_width="@dimen/firstrun_content_width"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:paddingBottom="40dp"
- android:textAppearance="@style/TextAppearance.FirstrunLight.Main"/>
-
- <Button android:id="@+id/welcome_account"
- style="@style/Widget.Firstrun.Button"
- android:background="@drawable/button_background_action_orange_round"
- android:layout_gravity="center"
- android:text="@string/firstrun_signin_button"/>
-
- <View android:layout_weight="1"
- android:layout_height="0dp"
- android:layout_width="match_parent"/>
-
- <TextView android:id="@+id/welcome_browse"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="30dp"
- android:gravity="center"
- android:textAppearance="@style/TextAppearance.FirstrunRegular.Link"/>
- </LinearLayout>
-</ScrollView>
diff --git a/mobile/android/base/resources/layout/font_size_preference.xml b/mobile/android/base/resources/layout/font_size_preference.xml
deleted file mode 100644
index b5af5e588..000000000
--- a/mobile/android/base/resources/layout/font_size_preference.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this file,
- - You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <LinearLayout android:id="@+id/button_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginLeft="4dp"
- android:layout_marginRight="4dp"
- android:layout_alignParentBottom="true"
- android:layout_alignParentLeft="true"
- android:orientation="horizontal">
-
- <Button android:id="@+id/decrease_preview_font_button"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:text="@string/pref_font_size_adjust_char"
- android:textSize="8sp"/>
-
- <Button android:id="@+id/increase_preview_font_button"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:text="@string/pref_font_size_adjust_char"
- android:textSize="16sp"/>
-
- </LinearLayout>
-
- <ScrollView android:id="@+id/scrolling_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_above="@id/button_container"
- android:layout_margin="8dp"
- android:padding="8dp"
- android:scrollbars="vertical"
- android:scrollbarStyle="outsideOverlay"
- android:fadeScrollbars="true"
- android:requiresFadingEdge="vertical">
-
- <TextView android:id="@+id/preview"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/pref_font_size_preview_text"
- android:textColor="#ff000000"/>
-
- </ScrollView>
-
-</RelativeLayout>
diff --git a/mobile/android/base/resources/layout/gecko_app.xml b/mobile/android/base/resources/layout/gecko_app.xml
deleted file mode 100644
index ec43b341f..000000000
--- a/mobile/android/base/resources/layout/gecko_app.xml
+++ /dev/null
@@ -1,177 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- android:id="@+id/root_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <ViewStub android:id="@+id/tabs_panel"
- android:layout="@layout/tabs_panel_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
-
- <view class="org.mozilla.gecko.GeckoApp$MainLayout"
- android:id="@+id/main_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@android:color/transparent">
-
- <RelativeLayout android:id="@+id/gecko_layout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_below="@+id/tablet_tab_strip"
- android:layout_above="@+id/find_in_page">
-
- <fragment class="org.mozilla.gecko.GeckoViewFragment"
- android:id="@+id/layer_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scrollbars="none"/>
-
- <AbsoluteLayout android:id="@+id/plugin_container"
- android:background="@android:color/transparent"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
-
- <org.mozilla.gecko.FormAssistPopup android:id="@+id/form_assist_popup"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"/>
-
- <view class="org.mozilla.gecko.media.VideoPlayer" android:id="@+id/video_player"
- android:layout_height="match_parent"
- android:layout_width="match_parent">
- </view>
-
- <ViewStub android:id="@+id/zoomed_view_stub"
- android:inflatedId="@+id/zoomed_view"
- android:layout="@layout/zoomed_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
-
- <FrameLayout android:id="@+id/home_screen_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone">
-
- <ViewStub android:id="@+id/home_pager_stub"
- android:layout="@layout/home_pager"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
-
- <ViewStub android:id="@+id/activity_stream_stub"
- android:layout="@layout/activity_stream"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
-
- <ViewStub android:id="@+id/home_banner_stub"
- android:layout="@layout/home_banner"
- android:layout_width="match_parent"
- android:layout_height="@dimen/home_banner_height"
- android:layout_gravity="bottom"/>
-
- <ViewStub android:id="@+id/firstrun_pager_stub"
- android:layout="@layout/firstrun_animation_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
-
- </FrameLayout>
-
- <View android:id="@+id/doorhanger_overlay"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@color/dark_transparent_overlay"
- android:alpha="0"
- android:layerType="hardware"/>
-
- </RelativeLayout>
-
- <org.mozilla.gecko.FindInPageBar android:id="@+id/find_in_page"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- style="@style/FindBar"
- android:visibility="gone"/>
-
- <org.mozilla.gecko.MediaCastingBar android:id="@+id/media_casting"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- style="@style/FindBar"
- android:visibility="gone"/>
-
- <FrameLayout android:id="@+id/search_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_below="@+id/browser_chrome"
- android:visibility="invisible"/>
-
- <!-- When focus is cleared from from BrowserToolbar's EditText to
- lower the virtual keyboard, focus will be returned to the root
- view. To make sure the EditText is not the first focusable view in
- the root view, BrowserToolbar should be specified as low in the
- view hierarchy as possible. -->
-
- <LinearLayout android:id="@id/browser_chrome"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <ViewStub android:id="@+id/tablet_tab_strip"
- android:inflatedId="@id/tablet_tab_strip"
- android:layout="@layout/tab_strip"
- android:layout_width="match_parent"
- android:layout_height="@dimen/tablet_tab_strip_height"
- android:visibility="gone"/>
-
- <ViewFlipper
- android:id="@+id/browser_actionbar"
- android:layout_width="match_parent"
- android:layout_height="@dimen/browser_toolbar_height_flipper"
- android:clickable="true"
- android:focusable="true">
-
- <org.mozilla.gecko.toolbar.BrowserToolbar
- android:id="@+id/browser_toolbar"
- style="@style/BrowserToolbar"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clickable="true"
- android:focusable="true"
- android:background="@drawable/url_bar_bg"/>
-
- <org.mozilla.gecko.ActionModeCompatView android:id="@+id/actionbar"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- style="@style/GeckoActionBar.ActionMode"/>
-
- </ViewFlipper>
-
- </LinearLayout>
-
- <org.mozilla.gecko.toolbar.ToolbarProgressView android:id="@+id/progress"
- android:layout_width="match_parent"
- android:layout_height="14dp"
- android:layout_marginTop="-8dp"
- android:layout_below="@id/browser_chrome"
- android:src="@drawable/progress"
- android:background="@null"
- android:visibility="gone" />
-
- </view>
-
- <FrameLayout android:id="@+id/tab_history_panel"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alignParentBottom="true"
- android:visibility="gone" />
-
- <ViewStub android:id="@+id/toast_stub"
- android:layout="@layout/button_toast"
- style="@style/Toast"/>
-
-</RelativeLayout>
diff --git a/mobile/android/base/resources/layout/history_sync_setup.xml b/mobile/android/base/resources/layout/history_sync_setup.xml
deleted file mode 100644
index d8ebfeebf..000000000
--- a/mobile/android/base/resources/layout/history_sync_setup.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/.
--->
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <LinearLayout
- style="@style/RemoteTabsPanelFrame">
-
- <TextView
- style="@style/RemoteTabsPanelItem.TextAppearance.Header"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/fxaccount_getting_started_welcome_to_sync" />
-
- <TextView
- style="@style/RemoteTabsPanelItem.TextAppearance"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/fxaccount_getting_started_description" />
-
- <Button
- android:id="@+id/sync_setup_button"
- style="@style/RemoteTabsPanelItem.Button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/fxaccount_getting_started_get_started" />
- </LinearLayout>
-
-</ScrollView> \ No newline at end of file
diff --git a/mobile/android/base/resources/layout/home_banner.xml b/mobile/android/base/resources/layout/home_banner.xml
deleted file mode 100644
index 46152c711..000000000
--- a/mobile/android/base/resources/layout/home_banner.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.home.HomeBanner xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/home_banner"
- style="@style/Widget.HomeBanner"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@drawable/home_banner"
- android:gravity="center_vertical"
- android:visibility="gone"
- android:clickable="true"
- android:focusable="true"/>
diff --git a/mobile/android/base/resources/layout/home_banner_content.xml b/mobile/android/base/resources/layout/home_banner_content.xml
deleted file mode 100644
index 5cbdc47e2..000000000
--- a/mobile/android/base/resources/layout/home_banner_content.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <ImageView android:id="@+id/icon"
- android:layout_width="@dimen/home_banner_icon_width"
- android:layout_height="@dimen/home_banner_icon_height"
- android:layout_marginLeft="10dp"
- android:scaleType="centerInside"/>
-
- <org.mozilla.gecko.widget.EllipsisTextView
- android:id="@+id/text"
- android:layout_width="0dip"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:layout_marginLeft="10dp"
- android:paddingTop="7dp"
- android:paddingBottom="7dp"
- android:textAppearance="@style/TextAppearance.Widget.HomeBanner"
- android:layout_gravity="bottom"
- android:gravity="center_vertical"
- gecko:ellipsizeAtLine="3"/>
-
- <ImageButton android:id="@+id/close"
- android:layout_width="@dimen/home_banner_close_width"
- android:layout_height="match_parent"
- android:background="@drawable/home_banner"
- android:scaleType="center"
- android:contentDescription="@string/close_tab"
- android:src="@drawable/tab_close"/>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/home_bookmarks_panel.xml b/mobile/android/base/resources/layout/home_bookmarks_panel.xml
deleted file mode 100644
index c4c08b93a..000000000
--- a/mobile/android/base/resources/layout/home_bookmarks_panel.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <ViewStub android:id="@+id/home_empty_view_stub"
- android:layout="@layout/home_empty_panel"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
-
- <org.mozilla.gecko.home.BookmarksListView
- android:id="@+id/bookmarks_list"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
-
-</FrameLayout>
diff --git a/mobile/android/base/resources/layout/home_combined_back_item.xml b/mobile/android/base/resources/layout/home_combined_back_item.xml
deleted file mode 100644
index e6d443708..000000000
--- a/mobile/android/base/resources/layout/home_combined_back_item.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- style="@style/Widget.FolderView"
- android:layout_width="match_parent"
- android:text="@string/home_history_back_to"
- android:paddingLeft="24dp"
- android:drawablePadding="24dp"
- android:drawableLeft="@drawable/arrow_up"
- android:gravity="center_vertical"/>
diff --git a/mobile/android/base/resources/layout/home_combined_history_panel.xml b/mobile/android/base/resources/layout/home_combined_history_panel.xml
deleted file mode 100644
index fe64b028e..000000000
--- a/mobile/android/base/resources/layout/home_combined_history_panel.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <android.support.v4.widget.SwipeRefreshLayout
- android:id="@+id/refresh_layout"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1.2">
-
- <org.mozilla.gecko.home.CombinedHistoryRecyclerView
- android:id="@+id/combined_recycler_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scrollbars="vertical"/>
-
- </android.support.v4.widget.SwipeRefreshLayout>
-
- <include android:id="@+id/home_history_empty_view"
- layout="@layout/home_empty_panel"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="3"
- android:visibility="gone"/>
-
- <include android:id="@+id/home_clients_empty_view"
- layout="@layout/history_sync_setup"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="3"
- android:visibility="gone"/>
-
- <include android:id="@+id/home_recent_tabs_empty_view"
- layout="@layout/home_empty_panel"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="3"
- android:visibility="gone"/>
-
- <Button android:id="@+id/history_panel_footer_button"
- style="@style/Widget.Home.ActionButton"
- android:layout_width="match_parent"
- android:layout_height="48dp"
- android:visibility="gone" />
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/home_empty_panel.xml b/mobile/android/base/resources/layout/home_empty_panel.xml
deleted file mode 100644
index e845e7765..000000000
--- a/mobile/android/base/resources/layout/home_empty_panel.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/home_empty_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:gravity="center"
- android:paddingLeft="50dp"
- android:paddingRight="50dp">
-
- <!-- Empty spacer view -->
- <View android:layout_width="match_parent"
- android:layout_height="0dip"
- android:layout_weight="1"/>
-
- <ImageView android:id="@+id/home_empty_image"
- android:layout_width="90dp"
- android:layout_height="90dp"
- android:layout_marginBottom="10dp"
- android:gravity="top|center"
- android:scaleType="fitCenter"/>
-
- <TextView android:id="@+id/home_empty_text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="top|center"
- android:textAppearance="@style/TextAppearance.EmptyMessage"/>
-
- <TextView android:id="@+id/home_empty_hint"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="gone"
- android:gravity="top|center"
- android:textAppearance="@style/TextAppearance.EmptyHint"
- android:textColorLink="#FFA62F" />
- <!-- Empty spacer view -->
- <View android:layout_width="match_parent"
- android:layout_height="0dip"
- android:layout_weight="2"/>
-
-</LinearLayout>
-
diff --git a/mobile/android/base/resources/layout/home_header_row.xml b/mobile/android/base/resources/layout/home_header_row.xml
deleted file mode 100644
index f3ca9322b..000000000
--- a/mobile/android/base/resources/layout/home_header_row.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- style="@style/Widget.Home.HeaderItem"/>
diff --git a/mobile/android/base/resources/layout/home_item_row.xml b/mobile/android/base/resources/layout/home_item_row.xml
deleted file mode 100644
index 86754225f..000000000
--- a/mobile/android/base/resources/layout/home_item_row.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.home.TwoLinePageRow xmlns:android="http://schemas.android.com/apk/res/android"
- style="@style/Widget.TwoLinePageRow"
- android:layout_width="match_parent"
- android:layout_height="@dimen/page_row_height"
- android:minHeight="@dimen/page_row_height"/>
diff --git a/mobile/android/base/resources/layout/home_pager.xml b/mobile/android/base/resources/layout/home_pager.xml
deleted file mode 100644
index c858cefbc..000000000
--- a/mobile/android/base/resources/layout/home_pager.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- This file is used to include the home pager in gecko app
- layout based on screen size -->
-
-<org.mozilla.gecko.home.HomePager xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- android:id="@+id/home_pager"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@android:color/white">
-
- <org.mozilla.gecko.home.TabMenuStrip android:layout_width="match_parent"
- android:layout_height="@dimen/tabs_strip_height"
- android:background="@color/about_page_header_grey"
- android:layout_gravity="top"
- gecko:strip="@drawable/home_tab_menu_strip"
- gecko:activeTextColor="@color/placeholder_grey"
- gecko:inactiveTextColor="@color/tab_text_color"
- gecko:tabsMarginLeft="@dimen/tab_strip_content_start" />
-
-</org.mozilla.gecko.home.HomePager>
diff --git a/mobile/android/base/resources/layout/home_remote_tabs_group.xml b/mobile/android/base/resources/layout/home_remote_tabs_group.xml
deleted file mode 100644
index 60e6597f7..000000000
--- a/mobile/android/base/resources/layout/home_remote_tabs_group.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- style="@style/Widget.RemoteTabsClientView"
- android:gravity="center_vertical"
- android:layout_width="match_parent"
- android:layout_height="@dimen/home_header_item_height"
- android:minHeight="@dimen/home_header_item_height">
-
- <ImageView
- android:id="@+id/device_type"
- android:layout_width="@dimen/favicon_bg"
- android:layout_height="@dimen/favicon_bg"
- android:layout_marginLeft="12dp"
- android:layout_marginRight="12dp"
- android:layout_gravity="center_vertical"
- android:scaleType="center"
- tools:src="@drawable/sync_mobile"/>
-
- <LinearLayout
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:orientation="vertical">
-
- <org.mozilla.gecko.widget.FadedSingleColorTextView
- android:id="@+id/client"
- style="@style/Widget.FolderTitle.TwoLine"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- gecko:fadeWidth="30dp"
- tools:text="Firefox on Nexus 5"/>
-
- <TextView
- android:id="@+id/last_synced"
- style="@style/Widget.TwoLinePageRow.Url"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="4dp"
- android:maxLength="1024"
- tools:text="Last synced: 5 minutes ago"/>
- </LinearLayout>
-
- <ImageView
- android:id="@+id/device_expanded"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_marginLeft="15dip"
- android:layout_marginRight="15dip"
- android:scaleType="center"
- tools:src="@drawable/arrow_down"/>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/home_remote_tabs_hidden_devices.xml b/mobile/android/base/resources/layout/home_remote_tabs_hidden_devices.xml
deleted file mode 100644
index 9397cfedc..000000000
--- a/mobile/android/base/resources/layout/home_remote_tabs_hidden_devices.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/.
--->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/hidden_devices"
- style="@style/Widget.Home.ActionItem"
- android:background="@drawable/action_bar_button"
- android:layout_width="match_parent"
- android:layout_height="@dimen/home_remote_tabs_hidden_footer_height"
- android:gravity="center"
- android:maxLength="1024"
- android:textSize="12dp"
- android:textColor="@color/disabled_grey" />
diff --git a/mobile/android/base/resources/layout/home_search_item_row.xml b/mobile/android/base/resources/layout/home_search_item_row.xml
deleted file mode 100644
index d12239550..000000000
--- a/mobile/android/base/resources/layout/home_search_item_row.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.home.SearchEngineRow xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="@dimen/search_row_height"
- android:duplicateParentState="true"
- android:paddingTop="5dp"
- android:paddingBottom="5dp"/>
diff --git a/mobile/android/base/resources/layout/home_smartfolder.xml b/mobile/android/base/resources/layout/home_smartfolder.xml
deleted file mode 100644
index 484c30eab..000000000
--- a/mobile/android/base/resources/layout/home_smartfolder.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- style="@style/Widget.RemoteTabsClientView"
- android:gravity="center_vertical"
- android:layout_width="match_parent"
- android:layout_height="@dimen/home_header_item_height"
- android:minHeight="@dimen/home_header_item_height">
-
- <ImageView
- android:id="@+id/device_type"
- android:layout_width="26dp"
- android:layout_height="18dp"
- android:layout_margin="20dp"
- android:scaleType="fitCenter"
- android:layout_gravity="center_vertical"
- tools:src="@drawable/cloud"/>
-
- <LinearLayout
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:orientation="vertical">
-
- <org.mozilla.gecko.widget.FadedSingleColorTextView
- android:id="@+id/title"
- style="@style/Widget.FolderTitle.TwoLine"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- gecko:fadeWidth="30dp"
- tools:text="Firefox on Nexus 5"/>
-
- <TextView
- android:id="@+id/subtext"
- style="@style/Widget.TwoLinePageRow.Url"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="4dp"
- android:maxLength="1024"
- tools:text="Last synced: 5 minutes ago"/>
- </LinearLayout>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/home_suggestion_prompt.xml b/mobile/android/base/resources/layout/home_suggestion_prompt.xml
deleted file mode 100644
index 818671e1c..000000000
--- a/mobile/android/base/resources/layout/home_suggestion_prompt.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@color/about_page_header_grey">
-
- <LinearLayout android:id="@+id/prompt"
- android:focusable="true"
- android:layout_width="match_parent"
- android:minHeight="48dp"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:gravity="center_vertical"
- android:paddingLeft="15dp"
- android:paddingRight="15dp"
- android:textSize="12sp">
-
- <TextView android:id="@+id/suggestions_prompt_title"
- android:layout_height="wrap_content"
- android:layout_width="0dp"
- android:layout_weight="1"
- android:fontFamily="sans-serif"
- android:textColor="@color/text_and_tabs_tray_grey"
- android:layout_marginRight="10dp"/>
-
- <TextView android:id="@+id/suggestions_prompt_no"
- android:layout_height="32dp"
- android:minWidth="72dp"
- android:layout_width="wrap_content"
- android:layout_marginRight="10dp"
- android:textColor="@color/text_and_tabs_tray_grey"
- android:gravity="center"
- android:background="@drawable/search_suggestion_prompt_no"
- android:nextFocusRight="@+id/suggestions_prompt_yes"
- android:focusable="true"
- android:text="@string/button_no"/>
-
- <TextView android:id="@+id/suggestions_prompt_yes"
- android:layout_height="32dp"
- android:minWidth="72dp"
- android:layout_width="wrap_content"
- android:gravity="center"
- android:textColor="@android:color/white"
- android:background="@drawable/search_suggestion_prompt_yes"
- android:focusable="true"
- android:text="@string/button_yes"/>
-
-
-
- </LinearLayout>
-
-</FrameLayout>
diff --git a/mobile/android/base/resources/layout/home_top_sites_panel.xml b/mobile/android/base/resources/layout/home_top_sites_panel.xml
deleted file mode 100644
index a08e680e6..000000000
--- a/mobile/android/base/resources/layout/home_top_sites_panel.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.home.HomeListView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/list"
- style="@style/Widget.TopSitesListView"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
diff --git a/mobile/android/base/resources/layout/icon_grid.xml b/mobile/android/base/resources/layout/icon_grid.xml
deleted file mode 100644
index 33c9aee31..000000000
--- a/mobile/android/base/resources/layout/icon_grid.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<GridView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:choiceMode="singleChoice"
- android:padding="@dimen/icongrid_padding"/>
diff --git a/mobile/android/base/resources/layout/icon_grid_item.xml b/mobile/android/base/resources/layout/icon_grid_item.xml
deleted file mode 100644
index 5c1a4bc9b..000000000
--- a/mobile/android/base/resources/layout/icon_grid_item.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- //device/apps/common/res/any/layout/resolve_list_item.xml
- Copyright 2006, The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:gravity="center"
- android:orientation="vertical"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:background="@drawable/icon_grid_item_bg"
- android:padding="16dp">
-
- <!-- Extended activity info to distinguish between duplicate activity names -->
- <TextView android:id="@android:id/text2"
- android:textAppearance="?android:attr/textAppearance"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:minLines="2"
- android:maxLines="2"
- android:paddingTop="4dip"
- android:paddingBottom="4dip" />
-
- <!-- Activity icon when presenting dialog
- Size will be filled in by ResolverActivity -->
- <ImageView android:id="@+id/icon"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:scaleType="fitCenter" />
-
- <!-- Activity name -->
- <TextView android:id="@android:id/text1"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:minLines="2"
- android:maxLines="2"
- android:paddingTop="4dip"
- android:paddingBottom="4dip" />
-</LinearLayout>
-
diff --git a/mobile/android/base/resources/layout/list_item_header.xml b/mobile/android/base/resources/layout/list_item_header.xml
deleted file mode 100644
index 2d91f4e02..000000000
--- a/mobile/android/base/resources/layout/list_item_header.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@android:id/text1"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="2dip"
- android:paddingBottom="2dip"
- android:paddingLeft="5dip"
- style="?android:attr/listSeparatorTextViewStyle" />
diff --git a/mobile/android/base/resources/layout/login_doorhanger.xml b/mobile/android/base/resources/layout/login_doorhanger.xml
deleted file mode 100644
index 8e8c005af..000000000
--- a/mobile/android/base/resources/layout/login_doorhanger.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <TextView android:id="@+id/doorhanger_message"
- android:focusable="true"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.DoorHanger.Medium"/>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/login_edit_dialog.xml b/mobile/android/base/resources/layout/login_edit_dialog.xml
deleted file mode 100644
index 8d42d5d95..000000000
--- a/mobile/android/base/resources/layout/login_edit_dialog.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:padding="@dimen/doorhanger_section_padding_medium"
- android:orientation="vertical">
-
- <EditText android:id="@+id/username_edit"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:inputType="textNoSuggestions"
- android:hint="@string/doorhanger_login_edit_username_hint"/>
-
- <EditText android:id="@+id/password_edit"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:inputType="textPassword"
- android:hint="@string/doorhanger_login_edit_password_hint"/>
-
- <android.support.v7.widget.AppCompatCheckBox
- android:id="@+id/checkbox_toggle_password"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/doorhanger_login_edit_toggle"
- android:layout_marginTop="@dimen/doorhanger_subsection_padding"/>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/media_casting.xml b/mobile/android/base/resources/layout/media_casting.xml
deleted file mode 100644
index 008cf36a0..000000000
--- a/mobile/android/base/resources/layout/media_casting.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
- <RelativeLayout android:id="@+id/media_controls"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerInParent="true">
-
- <ImageButton android:id="@+id/media_play"
- style="@style/FindBar.ImageButton"
- android:contentDescription="@string/media_play"
- android:src="@drawable/media_bar_play"
- android:visibility="gone"/>
-
- <ImageButton android:id="@+id/media_pause"
- style="@style/FindBar.ImageButton"
- android:contentDescription="@string/media_pause"
- android:src="@drawable/media_bar_pause"/>
-
- </RelativeLayout>
-
- <TextView android:id="@+id/media_sending_to"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginLeft="5dip"
- android:layout_marginRight="5dip"
- android:layout_alignParentLeft="true"
- android:layout_toLeftOf="@id/media_controls"
- android:layout_centerVertical="true"
- android:singleLine="true"
- android:ellipsize="end"
- android:textColor="#FFFFFFFF"
- android:contentDescription="@string/media_sending_to"/>
-
- <ImageButton android:id="@+id/media_stop"
- style="@style/FindBar.ImageButton"
- android:contentDescription="@string/media_stop"
- android:layout_alignParentRight="true"
- android:src="@drawable/media_bar_stop"/>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/menu_action_bar.xml b/mobile/android/base/resources/layout/menu_action_bar.xml
deleted file mode 100644
index 0b9476f20..000000000
--- a/mobile/android/base/resources/layout/menu_action_bar.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!--
- Note: This layout is intended to be used only above 11+.
- android:showDividers are available only 11+
--->
-<view xmlns:android="http://schemas.android.com/apk/res/android"
- class="org.mozilla.gecko.menu.GeckoMenu$DefaultActionItemBar"
- android:layout_width="@dimen/menu_item_row_width"
- android:layout_height="@dimen/browser_toolbar_height"
- android:orientation="horizontal"
- android:background="@color/toolbar_menu_dark_grey"/>
diff --git a/mobile/android/base/resources/layout/menu_item_switcher_layout.xml b/mobile/android/base/resources/layout/menu_item_switcher_layout.xml
deleted file mode 100644
index 04b70bde8..000000000
--- a/mobile/android/base/resources/layout/menu_item_switcher_layout.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
- <!-- Application icons will be added dynamically -->
-
- <FrameLayout android:layout_width="0dip"
- android:layout_height="@dimen/menu_item_row_height"
- android:layout_weight="1.0">
-
- <org.mozilla.gecko.menu.MenuItemDefault
- android:id="@+id/menu_item"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@drawable/menu_item_action_bar_bg"
- android:clickable="true"
- android:focusable="true"/>
-
- <org.mozilla.gecko.menu.MenuItemActionBar
- style="@style/Widget.MenuItemSecondaryActionBar"
- android:id="@+id/menu_item_button"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center_vertical"
- android:visibility="gone"/>
-
- </FrameLayout>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/menu_popup.xml b/mobile/android/base/resources/layout/menu_popup.xml
deleted file mode 100644
index 494b727e4..000000000
--- a/mobile/android/base/resources/layout/menu_popup.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.widget.FilledCardView xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/menu_panel"
- android:layout_width="@dimen/menu_popup_width"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:minWidth="@dimen/menu_popup_width"
- app:cardBackgroundColor="@color/toolbar_grey"
- app:cardUseCompatPadding="true">
-
- <!-- MenuPanel will be added here dynamically -->
-
-</org.mozilla.gecko.widget.FilledCardView> \ No newline at end of file
diff --git a/mobile/android/base/resources/layout/menu_secondary_action_bar.xml b/mobile/android/base/resources/layout/menu_secondary_action_bar.xml
deleted file mode 100644
index c5be6f51e..000000000
--- a/mobile/android/base/resources/layout/menu_secondary_action_bar.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!--
- Note: This layout is intended to be used only above 11+.
- android:showDividers are available only 11+
--->
-<view xmlns:android="http://schemas.android.com/apk/res/android"
- class="org.mozilla.gecko.menu.GeckoMenu$DefaultActionItemBar"
- android:layout_width="@dimen/menu_item_row_width"
- android:layout_height="@dimen/browser_toolbar_height"
- android:orientation="horizontal"/>
diff --git a/mobile/android/base/resources/layout/overlay_share_button.xml b/mobile/android/base/resources/layout/overlay_share_button.xml
deleted file mode 100644
index d40303a93..000000000
--- a/mobile/android/base/resources/layout/overlay_share_button.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <ImageView
- android:layout_width="60dp"
- android:layout_height="match_parent"
- android:id="@+id/overlaybtn_icon"
- android:padding="30dp"
- android:scaleType="center"/>
-
- <TextView
- android:textAppearance="@style/TextAppearance.ShareOverlay"
- android:id="@+id/overlaybtn_label"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:clickable="false"
- android:enabled="false"
- android:maxLines="1"
- android:textSize="14sp"
- android:textColor="@color/primary_text_selector"/>
-</merge>
diff --git a/mobile/android/base/resources/layout/overlay_share_dialog.xml b/mobile/android/base/resources/layout/overlay_share_dialog.xml
deleted file mode 100644
index b7fcd8747..000000000
--- a/mobile/android/base/resources/layout/overlay_share_dialog.xml
+++ /dev/null
@@ -1,83 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- Serves to position the content on the screen (bottom, centered) and provide the drop-shadow -->
-
-<merge
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
- <LinearLayout
- android:id="@+id/sharedialog"
- android:layout_width="300dp"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|center"
- android:layout_marginLeft="15dp"
- android:layout_marginRight="15dp"
- android:paddingTop="8dp"
- android:orientation="vertical">
-
- <!-- Title -->
- <TextView
- android:id="@+id/title"
- style="@style/ShareOverlayTitle"
- android:textAppearance="@style/TextAppearance.ShareOverlay.Header"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="8dp"
- android:maxLines="2"
- android:textSize="20sp"
- android:ellipsize="end"/>
-
- <!-- Subtitle (url) -->
- <TextView
- android:id="@+id/subtitle"
- style="@style/ShareOverlayTitle"
- android:textAppearance="@style/TextAppearance.ShareOverlay.Header"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="20dp"
- android:textSize="12sp"
- android:scrollHorizontally="true"/>
-
- <!-- TODO: Add back drop shadow (bug 1146488)? -->
- <!-- Buttons -->
- <!-- "Send to Firefox Sync" -->
- <org.mozilla.gecko.overlays.ui.SendTabList
- android:id="@+id/overlay_send_tab_btn"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:divider="@null"/>
-
- <!-- "Add bookmark" -->
- <org.mozilla.gecko.overlays.ui.OverlayDialogButton
- style="@style/ShareOverlayRow"
- android:id="@+id/overlay_share_bookmark_btn"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:enabled="false"
- gecko:drawable="@drawable/overlay_share_bookmark_button"
- gecko:enabledText="@string/overlay_share_bookmark_btn_label"
- gecko:disabledText="@string/overlay_share_bookmark_btn_label_already"/>
-
- </LinearLayout>
-
- <ImageView
- android:id="@+id/check"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="center"
- android:src="@drawable/overlay_check"
- android:visibility="invisible"/>
-
- <!-- This transparent View is used to overlay the
- entire Activity with an onClickListener. -->
- <View
- android:id="@+id/fullscreen_click_target"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:visibility="gone"/>
-</merge>
diff --git a/mobile/android/base/resources/layout/overlay_share_send_tab_item.xml b/mobile/android/base/resources/layout/overlay_share_send_tab_item.xml
deleted file mode 100644
index 99d723866..000000000
--- a/mobile/android/base/resources/layout/overlay_share_send_tab_item.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- The first item's background is unique and these views are reused
- so the background is set dynamically. -->
-<org.mozilla.gecko.overlays.ui.OverlayDialogButton
- xmlns:android="http://schemas.android.com/apk/res/android"
- style="@style/ShareOverlayRow"
- android:id="@+id/overlay_send_tab_item"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
diff --git a/mobile/android/base/resources/layout/panel_article_item.xml b/mobile/android/base/resources/layout/panel_article_item.xml
deleted file mode 100644
index a4234e018..000000000
--- a/mobile/android/base/resources/layout/panel_article_item.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
- <ImageView android:id="@+id/image"
- android:layout_width="@dimen/panel_article_item_height"
- android:layout_height="@dimen/panel_article_item_height"
- android:scaleType="centerCrop"/>
-
- <LinearLayout android:id="@+id/title_desc_container"
- android:layout_width="match_parent"
- android:layout_height="@dimen/panel_article_item_height"
- android:paddingLeft="15dip"
- android:paddingRight="15dip"
- android:gravity="center_vertical"
- android:orientation="vertical">
-
- <TextView android:id="@+id/title"
- style="@style/Widget.PanelItemView.Title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
-
- <TextView android:id="@+id/description"
- style="@style/Widget.PanelItemView.Description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="1dp"
- android:maxLength="1024"/>
-
- </LinearLayout>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/panel_auth_layout.xml b/mobile/android/base/resources/layout/panel_auth_layout.xml
deleted file mode 100644
index 549dc4191..000000000
--- a/mobile/android/base/resources/layout/panel_auth_layout.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
- <!-- Empty spacer view -->
- <View android:layout_width="match_parent"
- android:layout_height="0dip"
- android:layout_weight="1"/>
-
- <ImageView android:id="@+id/image"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="top|center"
- android:scaleType="center"
- android:paddingBottom="10dp"/>
-
- <TextView android:id="@+id/message"
- android:layout_width="match_parent"
- android:layout_height="0dip"
- android:gravity="top|center"
- android:textAppearance="@style/TextAppearance.EmptyMessage"
- android:paddingLeft="50dp"
- android:paddingRight="50dp"
- android:layout_weight="3"/>
-
- <Button android:id="@+id/button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginLeft="25dp"
- android:layout_marginRight="25dp"
- android:layout_marginBottom="25dp"
- android:gravity="bottom|center"
- android:textAppearance="@style/TextAppearance.EmptyMessage"
- android:background="@drawable/panel_auth_button"
- android:padding="20dp"/>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/panel_back_item.xml b/mobile/android/base/resources/layout/panel_back_item.xml
deleted file mode 100644
index d7f32fff3..000000000
--- a/mobile/android/base/resources/layout/panel_back_item.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
- <ImageView android:id="@+id/image"
- android:layout_width="54dp"
- android:layout_height="44dp"
- android:layout_marginTop="10dip"
- android:layout_marginLeft="10dip"
- android:scaleType="centerCrop"/>
-
- <TextView android:id="@+id/title"
- style="@style/Widget.PanelItemView.Title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="5dip"
- android:paddingBottom="5dip"
- android:paddingLeft="10dip"
- android:paddingRight="10dip"
- android:minHeight="@dimen/page_row_height"
- android:gravity="center_vertical"/>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/panel_icon_item.xml b/mobile/android/base/resources/layout/panel_icon_item.xml
deleted file mode 100644
index 7f3e554b3..000000000
--- a/mobile/android/base/resources/layout/panel_icon_item.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.widget.SquaredRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <ImageView
- android:id="@+id/background"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="centerCrop" />
-
- <ImageView
- android:id="@+id/image"
- android:layout_width="50dp"
- android:layout_height="50dp"
- android:scaleType="centerInside"
- android:layout_centerInParent="true" />
-
- <TextView
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:ellipsize="end"
- android:textColor="@android:color/white"
- android:layout_alignParentBottom="true"
- android:textSize="12sp"
- android:padding="8dp"
- android:background="@color/panel_icon_item_title_background"
- android:layout_gravity="center_horizontal"/>
-
-</org.mozilla.gecko.widget.SquaredRelativeLayout>
diff --git a/mobile/android/base/resources/layout/panel_image_item.xml b/mobile/android/base/resources/layout/panel_image_item.xml
deleted file mode 100644
index 15131dfaf..000000000
--- a/mobile/android/base/resources/layout/panel_image_item.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
- <org.mozilla.gecko.widget.SquaredImageView
- android:id="@+id/image"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:scaleType="centerCrop"
- android:adjustViewBounds="true"
- android:background="@color/panel_image_item_background"/>
-
- <LinearLayout android:id="@+id/title_desc_container"
- android:layout_width="match_parent"
- android:layout_height="@dimen/page_row_height"
- android:paddingTop="7dip"
- android:paddingBottom="7dip"
- android:paddingLeft="5dip"
- android:paddingRight="5dip"
- android:orientation="vertical">
-
- <TextView android:id="@+id/title"
- style="@style/Widget.PanelItemView.Title"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:gravity="center_vertical"
- android:singleLine="true"/>
-
- <TextView android:id="@+id/description"
- style="@style/Widget.PanelItemView.Description"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:gravity="center_vertical"
- android:singleLine="true"
- android:maxLength="1024"/>
-
- </LinearLayout>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/panel_item_container.xml b/mobile/android/base/resources/layout/panel_item_container.xml
deleted file mode 100644
index 2d767c64d..000000000
--- a/mobile/android/base/resources/layout/panel_item_container.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clickable="true"
- android:foreground="@color/recyclerview_selector">
-
-</FrameLayout>
diff --git a/mobile/android/base/resources/layout/pin_site_dialog.xml b/mobile/android/base/resources/layout/pin_site_dialog.xml
deleted file mode 100644
index 07eb152d7..000000000
--- a/mobile/android/base/resources/layout/pin_site_dialog.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <LinearLayout android:layout_width="match_parent"
- android:layout_height="@dimen/browser_toolbar_height"
- android:orientation="vertical"
- android:background="@color/toolbar_grey"
- android:padding="4dip">
-
- <EditText android:id="@+id/search"
- style="@style/UrlBar.Button"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:padding="6dip"
- android:hint="@string/pin_site_dialog_hint"
- android:background="@drawable/url_bar_entry"
- android:textColor="@color/url_bar_title"
- android:textColorHint="@color/url_bar_title_hint"
- android:textColorHighlight="@color/fennec_ui_orange"
- android:textSelectHandle="@drawable/handle_middle"
- android:textSelectHandleLeft="@drawable/handle_start"
- android:textSelectHandleRight="@drawable/handle_end"
- android:textCursorDrawable="@null"
- android:inputType="textUri|textNoSuggestions"
- android:imeOptions="actionGo|flagNoExtractUi|flagNoFullscreen"
- android:singleLine="true"
- android:gravity="center_vertical|left"/>
-
- </LinearLayout>
-
- <org.mozilla.gecko.home.HomeListView android:id="@+id/list"
- android:layout_width="match_parent"
- android:layout_height="0dip"
- android:layout_weight="1.0"/>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/preference_checkbox.xml b/mobile/android/base/resources/layout/preference_checkbox.xml
deleted file mode 100644
index e16f5b623..000000000
--- a/mobile/android/base/resources/layout/preference_checkbox.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2006 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<!-- Layout used by CheckBoxPreference for the checkbox style. This is inflated
- inside android.R.layout.preference. -->
-<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/checkbox"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:focusable="false"
- android:clickable="false" />
diff --git a/mobile/android/base/resources/layout/preference_panels.xml b/mobile/android/base/resources/layout/preference_panels.xml
deleted file mode 100644
index baeb99ce8..000000000
--- a/mobile/android/base/resources/layout/preference_panels.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeight"
- android:gravity="center_vertical"
- android:orientation="vertical"
- android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingRight="?android:attr/scrollbarSize">
-
- <TextView android:id="@+android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal" />
-
- <TextView android:id="@+android:id/summary"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:maxLines="2" />
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/preference_rightalign_icon.xml b/mobile/android/base/resources/layout/preference_rightalign_icon.xml
deleted file mode 100644
index 413faf2aa..000000000
--- a/mobile/android/base/resources/layout/preference_rightalign_icon.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- This custom layout matches the Android layout for preferences in
- padding and margins, so it is visually consistent. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:paddingRight="?android:attr/scrollbarSize">
-
- <RelativeLayout
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:gravity="right"
- android:layout_marginLeft="15dip"
- android:layout_marginRight="6dip"
- android:layout_marginTop="6dip"
- android:layout_marginBottom="6dip"
- android:paddingRight="6dip"
- android:layout_weight="1">
-
- <TextView android:id="@+android:id/title"
- android:layout_gravity="right"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal" />
-
- </RelativeLayout>
-
- <ImageView android:src="@drawable/menu_item_more"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:paddingRight="16dp" />
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/preference_search_engine.xml b/mobile/android/base/resources/layout/preference_search_engine.xml
deleted file mode 100644
index 97ce95a3f..000000000
--- a/mobile/android/base/resources/layout/preference_search_engine.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minHeight="?android:attr/listPreferredItemHeight"
- android:gravity="center_vertical"
- android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingRight="?android:attr/scrollbarSize">
-
- <org.mozilla.gecko.widget.FaviconView
- android:id="@+id/search_engine_icon"
- android:layout_width="@dimen/favicon_bg"
- android:layout_height="@dimen/favicon_bg"
- android:layout_gravity="center"
- android:minWidth="@dimen/favicon_bg"
- android:minHeight="@dimen/favicon_bg" />
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_marginLeft="15dip"
- android:layout_marginRight="6dip"
- android:layout_marginTop="6dip"
- android:layout_marginBottom="6dip">
-
- <TextView android:id="@+android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal" />
-
- <TextView android:id="@+android:id/summary"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:maxLines="2" />
-
- </LinearLayout>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/preference_search_tip.xml b/mobile/android/base/resources/layout/preference_search_tip.xml
deleted file mode 100644
index e03b171b4..000000000
--- a/mobile/android/base/resources/layout/preference_search_tip.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- Ignore UseCompoundDrawables because they caused a regression in bug 1208790. -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- tools:ignore="UseCompoundDrawables"
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingRight="?android:attr/scrollbarSize">
-
- <TextView android:id="@+id/label_search_hint"
- android:layout_height="wrap_content"
- android:layout_width="0dp"
- android:text="@string/pref_search_hint"
- android:layout_marginTop="5dip"
- android:layout_marginBottom="6dip"
- android:layout_marginLeft="15dip"
- android:layout_marginRight="6dip"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:paddingRight="6dip"
- android:layout_weight="1"/>
-
- <ImageView android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:paddingRight="8dp"
- android:paddingTop="12dip"
- android:src="@drawable/tip_addsearch"/>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/preference_set_homepage.xml b/mobile/android/base/resources/layout/preference_set_homepage.xml
deleted file mode 100644
index d30221a23..000000000
--- a/mobile/android/base/resources/layout/preference_set_homepage.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/homepage_layout"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingLeft="20dp"
- android:paddingRight="20dp"
- android:orientation="vertical">
-
- <RadioButton android:id="@+id/radio_default"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:text="@string/home_homepage_radio_default"
- android:textColor="@color/text_and_tabs_tray_grey"/>
-
- <RadioButton android:id="@+id/radio_user_address"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:text="@string/home_homepage_radio_user_address"
- android:textColor="@color/text_and_tabs_tray_grey"/>
-
- <!-- RadioGroup is a LinearLayout under the hood, so including this View is fine.
- The visibility changes with RadioButton state so we hide it to start. -->
- <EditText android:id="@+id/edittext_user_address"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:inputType="textUri"
- android:hint="@string/home_homepage_hint_user_address"
- android:textColorHint="@color/disabled_grey"
- android:visibility="gone"/>
-
-</RadioGroup>
diff --git a/mobile/android/base/resources/layout/private_tabs_panel.xml b/mobile/android/base/resources/layout/private_tabs_panel.xml
deleted file mode 100644
index 2c553abd7..000000000
--- a/mobile/android/base/resources/layout/private_tabs_panel.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <ImageView android:id="@+id/private_tabs_empty"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@drawable/private_masq"
- android:layout_gravity="center"/>
-
- <!-- Note: for an unknown reason, scrolling in the TabsLayout
- does not work unless it is laid out after the empty view. -->
- <view class="org.mozilla.gecko.tabs.TabsPanel$TabsLayout"
- android:id="@+id/private_tabs_layout"
- style="@style/TabsLayout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:choiceMode="singleChoice"
- gecko:tabs="tabs_private"/>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/restricted_firstrun_welcome_fragment.xml b/mobile/android/base/resources/layout/restricted_firstrun_welcome_fragment.xml
deleted file mode 100644
index 94ee43b7b..000000000
--- a/mobile/android/base/resources/layout/restricted_firstrun_welcome_fragment.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:fillViewport="true"
- android:orientation="vertical">
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@color/restricted_profile_background_gold"
- android:minHeight="@dimen/firstrun_min_height"
- android:orientation="vertical"
- android:weightSum="50">
-
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="21"
- android:padding="30dp">
-
- <TextView
- android:layout_width="320dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:background="@color/restricted_profile_background_green"
- android:gravity="center"
- android:paddingBottom="40dp"
- android:paddingLeft="30dp"
- android:paddingRight="30dp"
- android:paddingTop="40dp"
- android:text="@string/firstrun_welcome_restricted"
- android:textColor="#ffffff"
- android:textSize="22sp" />
-
- </FrameLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="29"
- android:background="@color/android:white"
- android:gravity="center_horizontal"
- android:orientation="vertical"
- android:padding="40dp">
-
- <Button
- android:id="@+id/welcome_browse"
- style="@style/Widget.Firstrun.Button"
- android:layout_gravity="center"
- android:layout_marginBottom="20dp"
- android:background="@drawable/button_background_action_blue_round"
- android:text="@string/firstrun_welcome_button_browser" />
-
- <TextView
- android:id="@+id/learn_more_link"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:padding="20dp"
- android:text="@string/pref_learn_more"
- android:textAppearance="@style/TextAppearance.FirstrunRegular.Link" />
-
- </LinearLayout>
- </LinearLayout>
-
-</ScrollView>
diff --git a/mobile/android/base/resources/layout/search_activity_main.xml b/mobile/android/base/resources/layout/search_activity_main.xml
deleted file mode 100644
index 93da2e083..000000000
--- a/mobile/android/base/resources/layout/search_activity_main.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- tools:context=".SearchActivity">
-
- <org.mozilla.search.autocomplete.SearchBar
- android:id="@+id/search_bar"
- android:layout_width="match_parent"
- android:layout_height="@dimen/search_bar_height"
- android:paddingTop="@dimen/search_bar_padding_y"
- android:paddingBottom="@dimen/search_bar_padding_y"
- android:paddingLeft="@dimen/search_row_padding"
- android:paddingRight="@dimen/search_row_padding"
- android:layout_gravity="top"/>
-
- <fragment
- android:id="@+id/postsearch"
- android:name="org.mozilla.search.PostSearchFragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="@dimen/search_bar_height"
- android:layout_gravity="top"
- android:visibility="invisible"/>
-
- <fragment
- android:id="@+id/presearch"
- android:name="org.mozilla.search.PreSearchFragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="@dimen/search_bar_height"
- android:layout_gravity="top"/>
-
- <fragment
- android:id="@+id/suggestions"
- android:name="org.mozilla.search.autocomplete.SuggestionsFragment"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="@dimen/search_bar_height"
- android:layout_gravity="top"/>
-
-
- <ImageButton
- android:id="@+id/settings_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="@android:color/transparent"
- android:padding="15dp"
- android:src="@drawable/ic_action_settings"
- android:layout_gravity="bottom|right"
- android:contentDescription="@string/search_pref_button_content_description"/>
-
- <View
- android:id="@+id/animation_card"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginTop="@dimen/search_bar_height"
- android:background="@color/row_background"
- android:visibility="invisible"
- android:layout_gravity="top"/>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/search_bar.xml b/mobile/android/base/resources/layout/search_bar.xml
deleted file mode 100644
index 69232ff07..000000000
--- a/mobile/android/base/resources/layout/search_bar.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
- <org.mozilla.search.ui.BackCaptureEditText
- android:id="@+id/edit_text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:imeOptions="actionSearch"
- android:inputType="textNoSuggestions"
- android:drawableLeft="@drawable/search_icon_inactive"
- android:drawablePadding="5dp"
- android:textSize="@dimen/query_text_size"
- android:focusable="false"
- android:focusableInTouchMode="false"
- android:textColorHighlight="@color/fennec_ui_orange"
- android:textSelectHandle="@drawable/handle_middle"
- android:textSelectHandleLeft="@drawable/handle_start"
- android:textSelectHandleRight="@drawable/handle_end" />
-
- <ImageButton
- android:id="@+id/clear_button"
- android:layout_width="30dp"
- android:layout_height="30dp"
- android:paddingLeft="10dp"
- android:layout_gravity="right|center_vertical"
- android:background="@android:color/transparent"
- android:src="@drawable/search_clear"
- android:scaleType="centerInside"
- android:visibility="gone"/>
-
- <ImageView
- android:id="@+id/engine_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_gravity="right|center_vertical"
- android:background="@android:color/transparent"
- android:visibility="gone"/>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/search_empty.xml b/mobile/android/base/resources/layout/search_empty.xml
deleted file mode 100644
index 9177a9dde..000000000
--- a/mobile/android/base/resources/layout/search_empty.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:gravity="center"
- android:paddingLeft="50dp"
- android:paddingRight="50dp">
-
- <!-- Empty spacer view -->
- <View
- android:layout_width="match_parent"
- android:layout_height="0dip"
- android:layout_weight="1"/>
-
- <ImageView
- android:id="@+id/empty_image"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="15dp"
- android:gravity="top|center"
- android:scaleType="fitCenter"/>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="0dip"
- android:orientation="vertical"
- android:layout_weight="3">
-
- <TextView
- android:id="@+id/empty_title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="15dp"
- style="@style/TextAppearance.EmptyView.Title"
- android:gravity="center"/>
-
- <TextView
- android:id="@+id/empty_message"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.EmptyView.Message"
- android:gravity="center"/>
-
- </LinearLayout>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/search_engine_bar_item.xml b/mobile/android/base/resources/layout/search_engine_bar_item.xml
deleted file mode 100644
index f8c546d93..000000000
--- a/mobile/android/base/resources/layout/search_engine_bar_item.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- TwoWayView doesn't let us set the margin around items (except as
- gecko:itemMargin, but that doesn't increase the hit area) so we
- have to surround the main View by a ViewGroup to create a pressable margin.
-
- Note: the layout_height values are shared with the parent
- View (browser_search at the time of this writing).
-
- The actual width of the FrameLayout is calculated at runtime by the
- SearchEngineBar class to spread the icons across the device's width. -->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/search_engine_icon_container"
- android:layout_width="72dp"
- android:layout_height="match_parent"
- android:clickable="true"
- android:background="@color/pressed_about_page_header_grey">
-
- <!-- Width & height are set to make the Favicons as sharp as possible
- based on asset size. -->
- <ImageView
- android:id="@+id/search_engine_icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_gravity="center"
- android:scaleType="fitCenter"/>
-
-</FrameLayout>
diff --git a/mobile/android/base/resources/layout/search_engine_bar_label.xml b/mobile/android/base/resources/layout/search_engine_bar_label.xml
deleted file mode 100644
index 057e9bf31..000000000
--- a/mobile/android/base/resources/layout/search_engine_bar_label.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- TwoWayView doesn't let us set the margin around items (except as
- gecko:itemMargin, but that doesn't increase the hit area) so we
- have to surround the main View by a ViewGroup to create a pressable margin. -->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="48dp"
- android:layout_height="match_parent"
- android:clickable="true"
- android:background="@color/pressed_about_page_header_grey">
-
- <ImageView
- android:id="@+id/search_engine_label"
- android:layout_width="16dp"
- android:layout_height="16dp"
- android:layout_gravity="center"
- android:scaleType="fitXY"/>
-
-</FrameLayout>
diff --git a/mobile/android/base/resources/layout/search_engine_row.xml b/mobile/android/base/resources/layout/search_engine_row.xml
deleted file mode 100644
index 5c2f2dfb9..000000000
--- a/mobile/android/base/resources/layout/search_engine_row.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
- <org.mozilla.gecko.widget.FaviconView android:id="@+id/suggestion_icon"
- android:layout_width="@dimen/favicon_bg"
- android:layout_height="@dimen/favicon_bg"
- android:layout_marginLeft="16dip"
- android:layout_marginRight="16dip"
- android:layout_centerVertical="true"
- android:minWidth="@dimen/favicon_bg"
- android:minHeight="@dimen/favicon_bg"/>
-
- <org.mozilla.gecko.widget.FlowLayout android:id="@+id/suggestion_layout"
- android:layout_toRightOf="@id/suggestion_icon"
- android:layout_centerVertical="true"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="15dp"
- android:duplicateParentState="true">
-
- <include layout="@layout/suggestion_item"
- android:id="@+id/suggestion_user_entered"/>
-
- </org.mozilla.gecko.widget.FlowLayout>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/search_fragment_post_search.xml b/mobile/android/base/resources/layout/search_fragment_post_search.xml
deleted file mode 100644
index 4bbfd36b3..000000000
--- a/mobile/android/base/resources/layout/search_fragment_post_search.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <ProgressBar
- android:id="@+id/progress_bar"
- style="@android:style/Widget.ProgressBar.Horizontal"
- android:layout_width="match_parent"
- android:layout_height="@dimen/progress_bar_height"
- android:progressDrawable="@drawable/progressbar"/>
-
- <WebView
- android:id="@+id/webview"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
-
- <ViewStub
- android:id="@+id/error_view_stub"
- android:layout="@layout/search_empty"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"/>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/search_fragment_pre_search.xml b/mobile/android/base/resources/layout/search_fragment_pre_search.xml
deleted file mode 100644
index 82ce15b1b..000000000
--- a/mobile/android/base/resources/layout/search_fragment_pre_search.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="@color/toolbar_grey">
-
- <ViewStub android:id="@+id/empty_view_stub"
- android:layout="@layout/search_empty"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"/>
-
- <ListView
- android:id="@+id/list_view"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:scrollbarStyle="outsideOverlay"
- android:paddingLeft="@dimen/search_row_padding"
- android:paddingRight="@dimen/search_row_padding"/>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/search_history_row.xml b/mobile/android/base/resources/layout/search_history_row.xml
deleted file mode 100644
index d47185bcc..000000000
--- a/mobile/android/base/resources/layout/search_history_row.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<TextView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/site_name"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@drawable/search_row_background"
- android:drawableLeft="@drawable/search_history"
- android:drawablePadding="@dimen/search_history_drawable_padding"
- android:padding="@dimen/search_row_padding"
- android:textSize="@dimen/query_text_size"/>
diff --git a/mobile/android/base/resources/layout/search_sugestions.xml b/mobile/android/base/resources/layout/search_sugestions.xml
deleted file mode 100644
index 1fb989fe8..000000000
--- a/mobile/android/base/resources/layout/search_sugestions.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<ListView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@color/toolbar_grey"
- android:paddingLeft="@dimen/search_row_padding"
- android:paddingRight="@dimen/search_row_padding"
- android:visibility="invisible"/>
diff --git a/mobile/android/base/resources/layout/search_suggestions_row.xml b/mobile/android/base/resources/layout/search_suggestions_row.xml
deleted file mode 100644
index 8c3f3e7a1..000000000
--- a/mobile/android/base/resources/layout/search_suggestions_row.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@drawable/search_row_background"
- android:padding="@dimen/search_row_padding"
- android:descendantFocusability="blocksDescendants"
- android:orientation="horizontal">
-
- <TextView
- android:id="@+id/auto_complete_row_text"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:textSize="@dimen/query_text_size"/>
-
- <ImageButton
- android:id="@+id/auto_complete_row_jump_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="right|center_vertical"
- android:paddingLeft="@dimen/search_row_padding"
- android:src="@drawable/search_plus"
- android:contentDescription="@string/search_plus_content_description"
- android:background="@android:color/transparent" />
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/search_widget.xml b/mobile/android/base/resources/layout/search_widget.xml
deleted file mode 100644
index e5db2d774..000000000
--- a/mobile/android/base/resources/layout/search_widget.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- A homescreen widget for launching Fennec or the search activity. We can't use styles in here
- so make sure any changes you make are also made to launch_widget.xml which doesn't have
- the search widget button. -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/widget_header_height"
- android:orientation="horizontal"
- android:background="@drawable/widget_bg">
-
- <ImageView android:id="@+id/logo_button"
- android:layout_width="0dp"
- android:layout_weight="1"
- android:padding="@dimen/widget_padding"
- android:background="@drawable/widget_button_left"
- android:layout_height="match_parent"
- android:src="@drawable/widget_icon"/>
-
- <LinearLayout android:id="@+id/search_button"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:gravity="center"
- android:contentDescription="@string/search_widget_button_label"
- android:orientation="horizontal"
- android:background="@drawable/widget_button_middle">
-
- <TextView android:id="@+id/search_button_label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:drawableLeft="@drawable/ic_widget_search"
- android:drawablePadding="@dimen/widget_padding"
- android:text="@string/search_widget_button_label"
- android:gravity="center"
- android:fontFamily="sans-serif"
- android:textSize="@dimen/widget_text_size"
- android:textColor="@color/toolbar_icon_grey"/>
-
- </LinearLayout>
-
- <LinearLayout android:id="@+id/new_tab_button"
- android:layout_width="0dp"
- android:layout_weight="1"
- android:layout_height="match_parent"
- android:contentDescription="@string/new_tab"
- android:gravity="center"
- android:orientation="horizontal"
- android:background="@drawable/widget_button_right">
-
- <TextView android:id="@+id/new_tab_button_label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:drawableLeft="@drawable/ic_widget_new_tab"
- android:drawablePadding="@dimen/widget_padding"
- android:gravity="center"
- android:text="@string/new_tab"
- android:fontFamily="sans-serif"
- android:textSize="@dimen/widget_text_size"
- android:textColor="@color/toolbar_icon_grey"/>
-
- </LinearLayout>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/select_dialog_list.xml b/mobile/android/base/resources/layout/select_dialog_list.xml
deleted file mode 100644
index ed6a7ecea..000000000
--- a/mobile/android/base/resources/layout/select_dialog_list.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- This is used for select lists for multiple selection enabled -->
-<ListView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/select_list"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:cacheColorHint="@null"
- android:scrollbars="vertical"
- android:overScrollMode="ifContentScrolls" />
diff --git a/mobile/android/base/resources/layout/select_dialog_multichoice.xml b/mobile/android/base/resources/layout/select_dialog_multichoice.xml
deleted file mode 100644
index 0fc9f9494..000000000
--- a/mobile/android/base/resources/layout/select_dialog_multichoice.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@android:id/text1"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.Widget.TextView"
- style="@style/Widget.ListItem"/>
diff --git a/mobile/android/base/resources/layout/select_dialog_singlechoice.xml b/mobile/android/base/resources/layout/select_dialog_singlechoice.xml
deleted file mode 100644
index 0bffa830d..000000000
--- a/mobile/android/base/resources/layout/select_dialog_singlechoice.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@android:id/text1"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.Widget.TextView"
- style="@style/Widget.ListItem"
- android:checkMark="?android:attr/listChoiceIndicatorSingle"
- android:ellipsize="marquee"/>
diff --git a/mobile/android/base/resources/layout/site_identity.xml b/mobile/android/base/resources/layout/site_identity.xml
deleted file mode 100644
index b74c97c83..000000000
--- a/mobile/android/base/resources/layout/site_identity.xml
+++ /dev/null
@@ -1,101 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <LinearLayout android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:paddingLeft="@dimen/doorhanger_section_padding_small"
- android:paddingRight="@dimen/doorhanger_section_padding_small"
- android:paddingBottom="@dimen/doorhanger_section_padding_large"
- android:paddingTop="@dimen/doorhanger_section_padding_medium">
-
- <ImageView android:id="@+id/site_identity_icon"
- android:layout_width="@dimen/doorhanger_icon_size"
- android:layout_height="@dimen/doorhanger_icon_size"
- android:gravity="center_horizontal"
- android:padding="@dimen/doorhanger_section_padding_small"
- android:layout_marginRight="@dimen/doorhanger_section_padding_small"/>
-
- <LinearLayout android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_weight="1.0">
-
- <TextView android:id="@+id/site_identity_title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Light"/>
-
- <TextView android:id="@+id/site_identity_state"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/doorhanger_subsection_padding"
- android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Bold"/>
-
- <TextView android:id="@+id/mixed_content_activity"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/doorhanger_section_padding_medium"
- android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
- android:visibility="gone"/>
-
- <LinearLayout android:id="@+id/site_identity_known_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="gone"
- android:orientation="vertical">
-
- <TextView android:id="@+id/owner"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/doorhanger_section_padding_small"
- android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
- android:textStyle="bold"/>
-
- <TextView android:id="@+id/owner_supplemental"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.DoorHanger.Medium"/>
-
- <TextView android:id="@+id/verifier"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/doorhanger_section_padding_medium"
- android:textAppearance="@style/TextAppearance.DoorHanger.Medium.Light"/>
-
- </LinearLayout>
-
- <TextView android:id="@+id/site_identity_link"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
- android:textColor="@color/link_blue"
- android:layout_marginTop="@dimen/doorhanger_section_padding_large"
- android:text="@string/learn_more"
- android:visibility="gone"/>
-
- <TextView android:id="@+id/site_settings_link"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
- android:textColor="@color/link_blue"
- android:layout_marginTop="@dimen/doorhanger_section_padding_large"
- android:text="@string/contextmenu_site_settings"
- android:visibility="gone"/>
- </LinearLayout>
- </LinearLayout>
-
- <View android:id="@+id/divider_doorhanger"
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="@color/toolbar_divider_grey"
- android:visibility="gone"/>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/site_setting_item.xml b/mobile/android/base/resources/layout/site_setting_item.xml
deleted file mode 100644
index a8feba7e3..000000000
--- a/mobile/android/base/resources/layout/site_setting_item.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<org.mozilla.gecko.widget.CheckableLinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:gravity="center_vertical"
- android:paddingLeft="16dip"
- android:paddingRight="12dip"
- android:minHeight="?android:attr/listPreferredItemHeight"
- android:focusable="false">
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:orientation="vertical"
- android:gravity="center_vertical"
- android:focusable="false">
-
- <TextView
- android:id="@+id/setting"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- style="Widget.ListItem"
- android:gravity="center_vertical|left"
- android:singleLine="true"
- android:ellipsize="marquee"/>
-
- <TextView
- android:id="@+id/value"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmall"
- style="Widget.ListItem"
- android:gravity="center_vertical|left"
- android:singleLine="true"
- android:ellipsize="marquee"/>
-
- </LinearLayout>
-
- <android.support.v7.widget.AppCompatCheckBox
- android:id="@+id/checkbox"
- android:layout_width="35dip"
- android:layout_height="wrap_content"
- android:paddingRight="12dip"
- android:gravity="center_vertical"
- android:focusable="false"
- android:clickable="false"/>
-
-</org.mozilla.gecko.widget.CheckableLinearLayout>
diff --git a/mobile/android/base/resources/layout/suggestion_item.xml b/mobile/android/base/resources/layout/suggestion_item.xml
deleted file mode 100644
index 8dc0dbe6c..000000000
--- a/mobile/android/base/resources/layout/suggestion_item.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:minHeight="32dp"
- android:orientation="horizontal"
- android:background="@drawable/search_suggestion_button"
- android:gravity="center_vertical"
- android:clickable="true"
- android:padding="5dp">
-
- <ImageView android:id="@+id/suggestion_item_icon"
- android:src="@drawable/icon_most_recent_empty"
- android:layout_marginRight="3dip"
- android:layout_width="18dip"
- android:layout_height="18dip"
- android:visibility="gone"/>
-
- <TextView android:id="@+id/suggestion_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textColor="@color/text_and_tabs_tray_grey"
- android:textSize="14sp"
- android:gravity="center_vertical"
- android:layout_gravity="center_vertical"/>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/tab_history_item_row.xml b/mobile/android/base/resources/layout/tab_history_item_row.xml
deleted file mode 100644
index c9de09506..000000000
--- a/mobile/android/base/resources/layout/tab_history_item_row.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko= "http://schemas.android.com/apk/res-auto"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:background="@color/state_pressed_toolbar_grey_pressed" >
-
- <LinearLayout android:id="@+id/tab_history_timeline_combo"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_marginLeft="@dimen/tab_history_combo_margin_left"
- android:layout_marginRight="@dimen/tab_history_combo_margin_right" >
-
- <ImageView android:id="@+id/tab_history_timeline_top"
- android:layout_width="@dimen/tab_history_timeline_width"
- android:layout_height="@dimen/tab_history_timeline_height"
- android:layout_gravity="center_horizontal"
- android:background="@color/tab_history_timeline_separator" />
-
- <org.mozilla.gecko.widget.FaviconView android:id="@+id/tab_history_icon"
- android:layout_width="@dimen/tab_history_favicon_bg"
- android:layout_height="@dimen/tab_history_favicon_bg"
- android:background="@drawable/tab_history_icon_state"
- android:padding="@dimen/tab_history_favicon_padding"
- android:scaleType="centerInside"
- gecko:overrideScaleType="false"
- gecko:dominantBorderEnabled="false" />
-
- <ImageView android:id="@+id/tab_history_timeline_bottom"
- android:layout_width="@dimen/tab_history_timeline_width"
- android:layout_height="@dimen/tab_history_timeline_height"
- android:layout_gravity="center_horizontal"
- android:background="@color/tab_history_timeline_separator" />
-
- </LinearLayout>
-
- <!-- HACK: Widget.TwoLinePageRow overrides the background attr but really shouldn't
- (bug 1271797). So we override the override and set the background equal to null. -->
- <org.mozilla.gecko.widget.FadedSingleColorTextView
- android:id="@+id/tab_history_title"
- style="@style/Widget.TwoLinePageRow.Title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:background="@null"
- android:paddingRight="@dimen/tab_history_title_margin_right"
- android:text="@+id/tab_history_title"
- android:textSize="@dimen/tab_history_title_text_size"
- android:textColor="@color/text_and_tabs_tray_grey"
- gecko:fadeWidth="@dimen/tab_history_title_fading_width"/>
-
-</LinearLayout>
diff --git a/mobile/android/base/resources/layout/tab_history_layout.xml b/mobile/android/base/resources/layout/tab_history_layout.xml
deleted file mode 100644
index 010ab6c50..000000000
--- a/mobile/android/base/resources/layout/tab_history_layout.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<RelativeLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <ListView android:id="@+id/tab_history_list"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- android:background="@drawable/tab_history_bg"
- android:divider="@null" />
-</RelativeLayout>
diff --git a/mobile/android/base/resources/layout/tab_menu_strip.xml b/mobile/android/base/resources/layout/tab_menu_strip.xml
deleted file mode 100644
index b3d3952b8..000000000
--- a/mobile/android/base/resources/layout/tab_menu_strip.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:minWidth="@dimen/tabs_strip_button_width"
- android:background="@drawable/tabs_strip_indicator"
- android:paddingLeft="@dimen/tabs_strip_button_padding"
- android:paddingRight="@dimen/tabs_strip_button_padding"
- android:gravity="center"
- android:focusable="true"
- style="@style/TextAppearance.Widget.HomePagerTabMenuStrip"/>
diff --git a/mobile/android/base/resources/layout/tab_prompt_input.xml b/mobile/android/base/resources/layout/tab_prompt_input.xml
deleted file mode 100644
index 72a73b702..000000000
--- a/mobile/android/base/resources/layout/tab_prompt_input.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@android:id/tabhost"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:minHeight="100dp">
-
- <TabWidget
- android:id="@android:id/tabs"
- style="@style/TabInput.TabWidget"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" >
- </TabWidget>
-
- <FrameLayout
- android:id="@android:id/tabcontent"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
- </FrameLayout>
-
- </LinearLayout>
-
-</TabHost>
diff --git a/mobile/android/base/resources/layout/tab_queue_prompt.xml b/mobile/android/base/resources/layout/tab_queue_prompt.xml
deleted file mode 100644
index d37b431d3..000000000
--- a/mobile/android/base/resources/layout/tab_queue_prompt.xml
+++ /dev/null
@@ -1,142 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge
- xmlns: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:clipChildren="false"
- android:clipToPadding="false">
-
- <LinearLayout
- android:id="@+id/tab_queue_container"
- android:layout_width="@dimen/overlay_prompt_container_width"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|center"
- android:background="@android:color/white"
- android:orientation="vertical">
-
- <TextView
- android:id="@+id/title"
- android:layout_width="@dimen/overlay_prompt_content_width"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:fontFamily="sans-serif-light"
- android:gravity="center_horizontal"
- android:paddingTop="40dp"
- android:text="@string/tab_queue_prompt_title"
- android:textColor="@color/text_and_tabs_tray_grey"
- android:textSize="20sp"
-
- tools:text="Opening multiple links?" />
-
- <TextView
- android:id="@+id/text"
- android:layout_width="@dimen/overlay_prompt_content_width"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:gravity="center"
- android:lineSpacingMultiplier="1.25"
- android:paddingTop="20dp"
- android:text="@string/tab_queue_prompt_text"
- android:textColor="@color/placeholder_grey"
- android:textSize="16sp"
- tools:text="Save them until the next time you open Firefox." />
-
- <TextView
- android:id="@+id/tip_text"
- android:layout_width="@dimen/overlay_prompt_content_width"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:gravity="center"
- android:paddingBottom="30dp"
- android:paddingTop="20dp"
- android:text="@string/tab_queue_prompt_tip_text"
- android:textColor="@color/action_orange"
- android:textSize="14sp"
- android:textStyle="italic"
- tools:text="you can change this later in Settings" />
-
- <TextView
- android:id="@+id/settings_permit_text"
- android:layout_width="@dimen/overlay_prompt_content_width"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:gravity="center"
- android:paddingBottom="30dp"
- android:paddingTop="20dp"
- android:text="@string/tab_queue_prompt_permit_drawing_over_apps"
- android:textColor="@color/action_orange"
- android:textSize="14sp"
- android:textStyle="italic"
- tools:text="Turn on Permit drawing over other apps" />
-
- <FrameLayout
- android:id="@+id/bottom_container"
- android:layout_width="match_parent"
- android:layout_height="52dp"
- android:layout_gravity="center"
- android:layout_marginBottom="40dp">
-
- <ImageView
- android:id="@+id/enabled_confirmation"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:src="@drawable/img_check"
- android:visibility="gone" />
-
- <LinearLayout
- android:id="@+id/button_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center_horizontal"
- android:orientation="horizontal">
-
- <TextView
- android:id="@+id/cancel_button"
- style="@style/Widget.BaseButton"
- android:layout_width="@dimen/overlay_prompt_button_width"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:background="@color/android:white"
- android:text="@string/tab_queue_prompt_negative_action_button"
- android:textColor="@drawable/tab_queue_dismiss_button_foreground"
- android:textSize="16sp"
-
- tools:text="Not now" />
-
- <Button
- android:id="@+id/ok_button"
- style="@style/Widget.BaseButton"
- android:layout_width="@dimen/overlay_prompt_button_width"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:background="@drawable/button_background_action_orange_round"
- android:text="@string/tab_queue_prompt_positive_action_button"
- android:textColor="@android:color/white"
- android:textSize="16sp"
- tools:text="Enable" />
-
- <Button
- android:id="@+id/settings_button"
- style="@style/Widget.BaseButton"
- android:layout_width="@dimen/overlay_prompt_button_width"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:background="@drawable/button_background_action_orange_round"
- android:text="@string/tab_queue_prompt_settings_button"
- android:textColor="@android:color/white"
- android:textSize="16sp"
- tools:text="Go to settings" />
-
- </LinearLayout>
-
- </FrameLayout>
-
- </LinearLayout>
-</merge>
diff --git a/mobile/android/base/resources/layout/tab_queue_toast.xml b/mobile/android/base/resources/layout/tab_queue_toast.xml
deleted file mode 100644
index 9aaf60f94..000000000
--- a/mobile/android/base/resources/layout/tab_queue_toast.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- (lint: UselessParent) The second-outermost layout doesn't have a parent to position itself
- against and would take up the whole screen without the outermost layout. -->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
-
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- tools:ignore="UselessParent">
-
- <LinearLayout
- android:id="@+id/toast"
- style="@style/Toast">
-
- <TextView
- android:id="@+id/toast_message"
- style="@style/ToastMessage"
-
- tools:text="Tab queued in firefox" />
-
- <View
- android:id="@+id/toast_divider"
- style="@style/ToastDivider" />
-
- <Button
- android:id="@+id/toast_button"
- style="@style/ToastButton"
- android:drawableLeft="@drawable/switch_button_icon"
-
- tools:text="Open" />
-
- </LinearLayout>
-</FrameLayout> \ No newline at end of file
diff --git a/mobile/android/base/resources/layout/tab_strip.xml b/mobile/android/base/resources/layout/tab_strip.xml
deleted file mode 100644
index fd9d88555..000000000
--- a/mobile/android/base/resources/layout/tab_strip.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.tabs.TabStrip
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@color/text_and_tabs_tray_grey"/>
diff --git a/mobile/android/base/resources/layout/tab_strip_inner.xml b/mobile/android/base/resources/layout/tab_strip_inner.xml
deleted file mode 100644
index 8e7824818..000000000
--- a/mobile/android/base/resources/layout/tab_strip_inner.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <org.mozilla.gecko.tabs.TabStripView
- android:id="@+id/tab_strip"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:paddingTop="8dp"/>
-
- <!-- The right margin creates a "dead area" on the right side of the button
- which we compensate for with a touch delegate. See TabStrip -->
- <org.mozilla.gecko.widget.themed.ThemedImageButton
- android:id="@+id/add_tab"
- style="@style/UrlBar.ImageButton"
- android:layout_width="@dimen/tablet_tab_strip_height"
- android:src="@drawable/tab_new"
- gecko:drawableTintList="@color/tab_new_tab_strip_colors"
- android:contentDescription="@string/new_tab"
- android:layout_marginRight="9dp"
- android:background="@drawable/tab_strip_button"/>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/tab_strip_item.xml b/mobile/android/base/resources/layout/tab_strip_item.xml
deleted file mode 100644
index 73412458d..000000000
--- a/mobile/android/base/resources/layout/tab_strip_item.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- The paddings are asymmetric here to compensate the padding around the
- the close button within the TabStripItemView -->
-<org.mozilla.gecko.tabs.TabStripItemView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/tablet_tab_strip_item_width"
- android:layout_height="match_parent"
- android:paddingLeft="28dp"
- android:paddingRight="12dp"/>
diff --git a/mobile/android/base/resources/layout/tab_strip_item_view.xml b/mobile/android/base/resources/layout/tab_strip_item_view.xml
deleted file mode 100644
index 2f56b7702..000000000
--- a/mobile/android/base/resources/layout/tab_strip_item_view.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <ImageView
- android:id="@+id/favicon"
- android:layout_width="@dimen/browser_toolbar_favicon_size"
- android:layout_height="match_parent"
- android:layout_marginRight="8dp"
- android:scaleType="centerInside"
- android:duplicateParentState="true"/>
-
- <org.mozilla.gecko.widget.FadedSingleColorTextView
- android:id="@+id/title"
- android:layout_width="0dip"
- android:layout_height="match_parent"
- android:layout_weight="1.0"
- android:layout_marginRight="-5dp"
- android:drawablePadding="6dp"
- android:gravity="center_vertical"
- android:textSize="14sp"
- android:ellipsize="end"
- android:textColor="@color/tab_strip_item_title"
- android:maxLines="1"
- gecko:fadeWidth="30dip"
- android:duplicateParentState="true"/>
-
- <org.mozilla.gecko.widget.themed.ThemedImageButton
- android:id="@+id/close"
- android:layout_width="40dip"
- android:layout_height="match_parent"
- android:background="@android:color/transparent"
- android:scaleType="center"
- android:contentDescription="@string/close_tab"
- android:src="@drawable/tab_close"
- android:duplicateParentState="true"/>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/tabs_counter.xml b/mobile/android/base/resources/layout/tabs_counter.xml
deleted file mode 100644
index e41081251..000000000
--- a/mobile/android/base/resources/layout/tabs_counter.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.widget.themed.ThemedTextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="24dip"
- android:layout_height="24dip"
- android:layout_margin="12dip"
- android:paddingTop="2dip"
- android:paddingLeft="4dip"
- android:background="@drawable/tabs_count_foreground"
- android:textAppearance="@style/TextAppearance.Micro"
- android:textColor="@color/text_and_tabs_tray_grey"
- android:textStyle="bold"
- android:duplicateParentState="true"
- android:gravity="center"/>
diff --git a/mobile/android/base/resources/layout/tabs_layout_item_view.xml b/mobile/android/base/resources/layout/tabs_layout_item_view.xml
deleted file mode 100644
index 75f35e467..000000000
--- a/mobile/android/base/resources/layout/tabs_layout_item_view.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.tabs.TabsLayoutItemView xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- style="@style/TabsItem"
- android:id="@+id/info"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:orientation="vertical">
-
- <LinearLayout android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:duplicateParentState="true"
- android:paddingLeft="@dimen/tab_highlight_stroke_width"
- android:paddingRight="@dimen/tab_highlight_stroke_width"
- android:paddingBottom="@dimen/tab_highlight_stroke_width">
-
- <org.mozilla.gecko.widget.FadedSingleColorTextView
- android:id="@+id/title"
- android:layout_width="0dip"
- android:layout_height="wrap_content"
- android:layout_weight="1.0"
- style="@style/TabLayoutItemTextAppearance"
- android:textSize="14sp"
- android:textColor="@color/tab_item_title"
- android:singleLine="true"
- android:duplicateParentState="true"
- gecko:fadeWidth="15dp"
- android:paddingRight="5dp"
- android:drawablePadding="6dp"/>
-
- <!-- Use of baselineAlignBottom only supported from API 11+ - if this needs to work on lower API versions
- we'll need to override getBaseLine() and return image height, but we assume this won't happen -->
- <ImageView android:id="@+id/close"
- style="@style/TabsItemClose"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:scaleType="center"
- android:baselineAlignBottom="true"
- android:background="@android:color/transparent"
- android:contentDescription="@string/close_tab"
- android:src="@drawable/tab_item_close_button"
- android:duplicateParentState="true"/>
-
- </LinearLayout>
-
- <!-- We set state_private on this View dynamically in TabsGridLayout. -->
- <org.mozilla.gecko.widget.TabThumbnailWrapper
- android:id="@+id/wrapper"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="@dimen/tab_highlight_stroke_width"
- android:background="@drawable/tab_thumbnail"
- android:duplicateParentState="true"
- android:clipToPadding="false">
-
- <org.mozilla.gecko.tabs.TabsPanelThumbnailView android:id="@+id/thumbnail"
- android:layout_width="@dimen/tab_thumbnail_width"
- android:layout_height="@dimen/tab_thumbnail_height"
- android:elevation="2dp"
- android:outlineProvider="bounds"
- />
-
- </org.mozilla.gecko.widget.TabThumbnailWrapper>
-
-</org.mozilla.gecko.tabs.TabsLayoutItemView>
diff --git a/mobile/android/base/resources/layout/tabs_list_item_view.xml b/mobile/android/base/resources/layout/tabs_list_item_view.xml
deleted file mode 100644
index 8c81e8095..000000000
--- a/mobile/android/base/resources/layout/tabs_list_item_view.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.tabs.TabsLayoutItemView xmlns:android="http://schemas.android.com/apk/res/android"
- style="@style/TabsItem"
- android:focusable="true"
- android:id="@+id/info"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingLeft="12dip"
- android:paddingTop="6dip"
- android:paddingBottom="6dip"
- android:background="@drawable/tab_row">
-
- <!-- We set state_private on this View dynamically in TabsListLayout. -->
- <org.mozilla.gecko.widget.TabThumbnailWrapper
- android:id="@+id/wrapper"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="4dip"
- android:background="@drawable/tab_thumbnail"
- android:duplicateParentState="true"
- android:clipToPadding="false">
-
- <org.mozilla.gecko.tabs.TabsPanelThumbnailView android:id="@+id/thumbnail"
- android:layout_width="@dimen/tab_thumbnail_width"
- android:layout_height="@dimen/tab_thumbnail_height"
- android:elevation="2dp"
- android:outlineProvider="bounds"/>
-
- </org.mozilla.gecko.widget.TabThumbnailWrapper>
-
- <LinearLayout android:layout_width="0dip"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:layout_weight="1.0"
- android:paddingTop="4dip"
- android:paddingLeft="8dip"
- android:paddingRight="4dip"
- android:duplicateParentState="true">
-
- <TextView android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="0dip"
- android:layout_weight="1.0"
- style="@style/TabLayoutItemTextAppearance"
- android:textColor="@color/tab_item_title"
- android:textSize="14sp"
- android:gravity="center_vertical"
- android:singleLine="false"
- android:maxLines="4"
- android:drawablePadding="6dp"
- android:duplicateParentState="true"/>
-
- </LinearLayout>
-
- <ImageView android:id="@+id/close"
- style="@style/TabsItemClose"
- android:layout_width="34dip"
- android:layout_height="match_parent"
- android:scaleType="center"
- android:contentDescription="@string/close_tab"
- android:src="@drawable/tab_item_close_button"
- android:duplicateParentState="true"/>
-
-</org.mozilla.gecko.tabs.TabsLayoutItemView> \ No newline at end of file
diff --git a/mobile/android/base/resources/layout/tabs_panel_default.xml b/mobile/android/base/resources/layout/tabs_panel_default.xml
deleted file mode 100644
index a5ef871e8..000000000
--- a/mobile/android/base/resources/layout/tabs_panel_default.xml
+++ /dev/null
@@ -1,100 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <!-- The layout_height value is used in TabsPanel.getTabsLayoutContainerHeight
- and as an offset in PrivateTabsPanel: if you change it here,
- change it there! -->
- <RelativeLayout android:id="@+id/tabs_panel_header"
- android:layout_width="match_parent"
- android:layout_height="@dimen/browser_toolbar_height">
-
- <view class="org.mozilla.gecko.tabs.TabsPanel$TabsPanelToolbar"
- android:layout_width="match_parent"
- android:layout_height="@dimen/browser_toolbar_height"
- android:background="@color/text_and_tabs_tray_grey">
-
- <org.mozilla.gecko.tabs.TabPanelBackButton
- android:id="@+id/nav_back"
- android:layout_width="@dimen/tabs_panel_button_width"
- android:layout_height="match_parent"
- android:minWidth="@dimen/tabs_panel_button_width"
- android:src="@drawable/tabs_panel_nav_back"
- android:contentDescription="@string/back"
- android:background="@drawable/action_bar_button_inverse"
- gecko:dividerVerticalPadding="@dimen/tab_panel_divider_vertical_padding"
- gecko:rightDivider="@drawable/tab_indicator_divider"/>
-
- <org.mozilla.gecko.widget.IconTabWidget android:id="@+id/tab_widget"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:tabStripEnabled="false"
- android:divider="@drawable/tab_indicator_divider"
- android:dividerPadding="@dimen/tab_panel_divider_vertical_padding"
- android:layout="@layout/tabs_panel_indicator"/>
-
- <View android:layout_width="0dip"
- android:layout_height="match_parent"
- android:layout_weight="1.0"/>
-
- <ImageButton android:id="@+id/add_tab"
- style="@style/UrlBar.ImageButton"
- android:layout_width="@dimen/tabs_panel_button_width"
- android:padding="@dimen/browser_toolbar_button_padding"
- android:src="@drawable/tab_new"
- android:contentDescription="@string/new_tab"
- android:background="@drawable/action_bar_button_inverse"/>
-
- <FrameLayout android:id="@+id/menu"
- style="@style/UrlBar.ImageButton"
- android:layout_width="@dimen/tabs_panel_button_width"
- android:background="@drawable/action_bar_button_inverse"
- android:contentDescription="@string/menu">
-
- <ImageView
- style="@style/UrlBar.ImageButton"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/browser_toolbar_menu_icon_height"
- android:layout_gravity="center"
- android:scaleType="centerInside"
- android:src="@drawable/menu"
- android:tint="@color/tabs_tray_icon_grey"/>
-
- </FrameLayout>
-
- </view>
-
- <View android:layout_width="match_parent"
- android:layout_height="2dp"
- android:layout_alignParentBottom="true"
- android:background="#1A000000"/>
-
- </RelativeLayout>
-
- <FrameLayout
- android:id="@+id/tabs_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <view class="org.mozilla.gecko.tabs.TabsPanel$TabsLayout"
- android:id="@+id/normal_tabs"
- style="@style/TabsLayout"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:choiceMode="singleChoice"
- android:visibility="gone"
- gecko:tabs="tabs_normal"/>
-
- <org.mozilla.gecko.tabs.PrivateTabsPanel
- android:id="@+id/private_tabs_panel"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"/>
-
- </FrameLayout>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/tabs_panel_indicator.xml b/mobile/android/base/resources/layout/tabs_panel_indicator.xml
deleted file mode 100644
index 64c9d4afc..000000000
--- a/mobile/android/base/resources/layout/tabs_panel_indicator.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.widget.themed.ThemedImageButton
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/tabs_panel_indicator_width"
- android:layout_height="@dimen/browser_toolbar_height"
- android:minWidth="@dimen/tabs_panel_indicator_width"
- android:paddingTop="@dimen/browser_toolbar_button_padding"
- android:paddingBottom="@dimen/browser_toolbar_button_padding"
- android:background="@drawable/tabs_panel_indicator"/>
diff --git a/mobile/android/base/resources/layout/tabs_panel_view.xml b/mobile/android/base/resources/layout/tabs_panel_view.xml
deleted file mode 100644
index 1f70b28f5..000000000
--- a/mobile/android/base/resources/layout/tabs_panel_view.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- The tabs panel is initial hidden on startup. It will be made
- visible when the user activates it. See TabsPanel.show() -->
-<org.mozilla.gecko.tabs.TabsPanel xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/tabs_panel"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="@color/text_and_tabs_tray_grey"
- android:orientation="vertical"
- android:visibility="invisible"/>
diff --git a/mobile/android/base/resources/layout/toolbar_display_layout.xml b/mobile/android/base/resources/layout/toolbar_display_layout.xml
deleted file mode 100644
index 4caacb379..000000000
--- a/mobile/android/base/resources/layout/toolbar_display_layout.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <!-- The site security icon is misaligned with the page title so
- we add a bottom margin to align their bottoms.
- Site security icon must have exact position and size as search icon in
- edit layout -->
- <ImageButton android:id="@+id/site_security"
- style="@style/UrlBar.ImageButton"
- android:layout_width="@dimen/browser_toolbar_site_security_width"
- android:layout_height="@dimen/browser_toolbar_site_security_height"
- android:scaleType="fitCenter"
- android:layout_marginRight="@dimen/browser_toolbar_site_security_margin_right"
- android:layout_marginBottom="@dimen/browser_toolbar_site_security_margin_bottom"
- android:paddingLeft="@dimen/browser_toolbar_site_security_padding_horizontal"
- android:paddingRight="@dimen/browser_toolbar_site_security_padding_horizontal"
- android:paddingTop="@dimen/browser_toolbar_site_security_padding_vertical"
- android:paddingBottom="@dimen/browser_toolbar_site_security_padding_vertical"
- android:src="@drawable/site_security_level"
- android:contentDescription="@string/site_security"
- android:layout_gravity="center_vertical" />
-
- <org.mozilla.gecko.widget.FadedMultiColorTextView
- android:id="@+id/url_bar_title"
- style="@style/UrlBar.Title"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1.0"
- gecko:fadeWidth="40dip"
- gecko:fadeBackgroundColor="@color/toolbar_display_layout_bg"
- gecko:autoUpdateTheme="false"/>
-
- <org.mozilla.gecko.toolbar.PageActionLayout android:id="@+id/page_action_layout"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:visibility="gone"
- android:orientation="horizontal"/>
-
- <ImageButton android:id="@+id/stop"
- android:layout_width="@dimen/page_action_button_width"
- android:layout_height="match_parent"
- android:src="@drawable/urlbar_stop"
- android:contentDescription="@string/stop"
- android:background="#00ffffff"
- android:visibility="gone"/>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/toolbar_edit_layout.xml b/mobile/android/base/resources/layout/toolbar_edit_layout.xml
deleted file mode 100644
index c3d27fadf..000000000
--- a/mobile/android/base/resources/layout/toolbar_edit_layout.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <!-- Search icon must have exact position and size as site security in
- display layout -->
- <ImageView android:id="@+id/search_icon"
- android:layout_width="@dimen/browser_toolbar_site_security_width"
- android:layout_height="@dimen/browser_toolbar_site_security_height"
- android:layout_marginBottom="@dimen/browser_toolbar_site_security_margin_bottom"
- android:layout_marginRight="@dimen/browser_toolbar_site_security_margin_right"
- android:paddingBottom="@dimen/browser_toolbar_site_security_padding_vertical"
- android:paddingLeft="@dimen/browser_toolbar_site_security_padding_horizontal"
- android:paddingRight="@dimen/browser_toolbar_site_security_padding_horizontal"
- android:paddingTop="@dimen/browser_toolbar_site_security_padding_vertical"
- android:scaleType="fitCenter"
- android:src="@drawable/search_icon_inactive"
- android:visibility="gone"/>
-
- <org.mozilla.gecko.toolbar.ToolbarEditText
- android:id="@+id/url_edit_text"
- style="@style/UrlBar.Title"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1.0"
- android:inputType="textUri|textNoSuggestions"
- android:imeOptions="actionGo|flagNoExtractUi|flagNoFullscreen"
- android:selectAllOnFocus="true"
- android:contentDescription="@string/url_bar_default_text"
- android:paddingRight="8dp"
- gecko:autoUpdateTheme="false"/>
-
- <ImageButton android:id="@+id/qrcode"
- android:layout_width="@dimen/page_action_button_width"
- android:layout_height="match_parent"
- android:src="@drawable/ab_qrcode"
- android:background="@android:color/transparent"/>
-
- <ImageButton android:id="@+id/mic"
- android:layout_width="@dimen/page_action_button_width"
- android:layout_height="match_parent"
- android:src="@drawable/ab_mic"
- android:background="@android:color/transparent"/>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/top_sites_grid_item_view.xml b/mobile/android/base/resources/layout/top_sites_grid_item_view.xml
deleted file mode 100644
index 6a76514a3..000000000
--- a/mobile/android/base/resources/layout/top_sites_grid_item_view.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
-
- <org.mozilla.gecko.home.TopSitesThumbnailView
- android:id="@+id/thumbnail"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"/>
-
- <org.mozilla.gecko.widget.FadedSingleColorTextView
- android:id="@+id/title"
- style="@style/Widget.TopSitesGridItemTitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/thumbnail"
- android:duplicateParentState="true"
- android:drawablePadding="4dip"
- gecko:fadeWidth="20dip"/>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/tracking_protection_prompt.xml b/mobile/android/base/resources/layout/tracking_protection_prompt.xml
deleted file mode 100644
index 5fc993b09..000000000
--- a/mobile/android/base/resources/layout/tracking_protection_prompt.xml
+++ /dev/null
@@ -1,106 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/tracking_protection_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false">
-
- <LinearLayout
- android:id="@+id/tracking_protection_inner_container"
- android:layout_width="@dimen/overlay_prompt_container_width"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|center"
- android:background="@android:color/white"
- android:orientation="vertical"
- android:paddingBottom="40dp">
-
- <ScrollView android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:fillViewport="true"
- android:fadeScrollbars="false">
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <ImageView android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/tracking_protection_toolbar_illustration"
- android:layout_gravity="center"
- android:layout_marginTop="40dp"
- android:layout_marginBottom="20dp"/>
-
- <TextView
- android:id="@+id/title"
- android:layout_width="@dimen/overlay_prompt_content_width"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:fontFamily="sans-serif-light"
- android:gravity="center_horizontal"
- android:text="@string/tracking_protection_prompt_title"
- android:textColor="@color/text_and_tabs_tray_grey"
- android:textSize="20sp"
-
- tools:text="Now with Tracking Protection"/>
-
- <TextView
- android:id="@+id/text"
- android:layout_width="@dimen/overlay_prompt_content_width"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:gravity="center"
- android:lineSpacingMultiplier="1.25"
- android:paddingTop="20dp"
- android:text="@string/tracking_protection_prompt_text"
- android:textColor="@color/placeholder_grey"
- android:textSize="16sp"
-
- tools:text="Actively block tracking elements so you don't have to worry."/>
-
- <TextView
- android:id="@+id/link_text"
- android:layout_width="@dimen/overlay_prompt_content_width"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:gravity="center"
- android:paddingBottom="30dp"
- android:paddingTop="20dp"
- android:text="@string/tracking_protection_prompt_tip_text"
- android:textColor="@color/link_blue"
- android:textSize="14sp"
-
- tools:text="Visit Privacy settings to learn more"/>
-
- </LinearLayout>
-
- </ScrollView>
-
- <Button
- android:id="@+id/ok_button"
- style="@style/Widget.BaseButton"
- android:layout_width="match_parent"
-
- android:layout_height="52dp"
- android:layout_gravity="center"
- android:background="@drawable/button_background_action_orange_round"
- android:text="@string/tracking_protection_prompt_action_button"
- android:textColor="@android:color/white"
- android:textSize="16sp"
-
- android:layout_marginLeft="32dp"
- android:layout_marginRight="32dp"
- tools:text="Got it"/>
-
- </LinearLayout>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/two_line_folder_row.xml b/mobile/android/base/resources/layout/two_line_folder_row.xml
deleted file mode 100644
index 3ca52749f..000000000
--- a/mobile/android/base/resources/layout/two_line_folder_row.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- tools:context=".BrowserApp">
-
- <ImageView android:id="@+id/icon"
- android:src="@drawable/folder_closed"
- android:layout_width="24dp"
- android:layout_height="18dp"
- android:scaleType="fitXY"
- android:layout_margin="20dp"/>
-
- <LinearLayout android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_gravity="center_vertical"
- android:paddingRight="10dp"
- android:orientation="vertical">
-
- <org.mozilla.gecko.widget.FadedSingleColorTextView
- android:id="@+id/title"
- style="@style/Widget.TwoLinePageRow.Title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- gecko:fadeWidth="90dp"
- tools:text="This is a long test title"/>
-
- <org.mozilla.gecko.widget.FadedSingleColorTextView android:id="@+id/subtitle"
- style="@style/Widget.TwoLinePageRow.Url"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:visibility="gone"
- gecko:fadeWidth="90dp"
- tools:text="1 items"/>
-
- </LinearLayout>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/two_line_page_row.xml b/mobile/android/base/resources/layout/two_line_page_row.xml
deleted file mode 100644
index 344f717d6..000000000
--- a/mobile/android/base/resources/layout/two_line_page_row.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- tools:context=".BrowserApp">
-
- <org.mozilla.gecko.widget.FaviconView android:id="@+id/icon"
- android:layout_width="@dimen/favicon_bg"
- android:layout_height="@dimen/favicon_bg"
- android:layout_margin="16dp"
- tools:background="@drawable/favicon_globe"/>
-
- <LinearLayout android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_gravity="center_vertical"
- android:paddingRight="10dp"
- android:orientation="vertical">
-
- <org.mozilla.gecko.widget.FadedSingleColorTextView
- android:id="@+id/title"
- style="@style/Widget.TwoLinePageRow.Title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- gecko:fadeWidth="90dp"
- tools:text="This is a long test title"/>
-
- <org.mozilla.gecko.widget.FadedSingleColorTextView android:id="@+id/url"
- style="@style/Widget.TwoLinePageRow.Url"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:drawablePadding="5dp"
- android:maxLength="1024"
- gecko:fadeWidth="90dp"
- tools:text="http://test.com/test"
- tools:drawableLeft="@drawable/ic_url_bar_tab"/>
-
- </LinearLayout>
-
- <ImageView android:id="@+id/status_icon_bookmark"
- android:layout_width="20dp"
- android:layout_height="20dp"
- android:layout_gravity="center"
- android:visibility="gone"
- android:src="@drawable/star_blue"/>
-
-</merge>
diff --git a/mobile/android/base/resources/layout/validation_message.xml b/mobile/android/base/resources/layout/validation_message.xml
deleted file mode 100644
index abcc2ef1e..000000000
--- a/mobile/android/base/resources/layout/validation_message.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="50dip">
-
- <TextView android:id="@+id/validation_message_text"
- android:layout_width="wrap_content"
- android:layout_height="40dip"
- android:layout_marginTop="6dip"
- android:paddingLeft="20dp"
- android:paddingRight="20dp"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="@color/validation_message_text"
- android:background="@drawable/validation_bg"
- android:gravity="center"
- android:singleLine="true"
- android:scrollHorizontally="true"
- android:ellipsize="marquee"/>
-
- <ImageView android:id="@+id/validation_message_arrow"
- android:layout_width="24dip"
- android:layout_height="10dip"
- android:layout_centerHorizontal="true"
- android:layout_alignParentTop="true"
- android:src="@drawable/validation_arrow"
- android:scaleType="fitXY"/>
-
- <ImageView android:id="@+id/validation_message_arrow_inverted"
- android:layout_width="24dip"
- android:layout_height="10dip"
- android:layout_centerHorizontal="true"
- android:layout_marginTop="35dip"
- android:layout_alignParentTop="true"
- android:src="@drawable/validation_arrow_inverted"
- android:scaleType="fitXY"
- android:visibility="gone"/>
-
-</RelativeLayout>
diff --git a/mobile/android/base/resources/layout/zoomed_view.xml b/mobile/android/base/resources/layout/zoomed_view.xml
deleted file mode 100644
index fd5684303..000000000
--- a/mobile/android/base/resources/layout/zoomed_view.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/.
--->
-
-<org.mozilla.gecko.ZoomedView xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- android:id="@+id/zoomed_view_container"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="@drawable/dropshadow"
- android:padding="@dimen/drawable_dropshadow_size"
- android:visibility="gone">
-
- <RelativeLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_alignParentTop="true"
- android:background="@drawable/toolbar_grey_round">
- <!--
- Zoom factor button is invisible by default. Set ui.zoomedview.simplified to false
- in order to display the button in the zoomed view tool bar
- -->
- <TextView
- android:id="@+id/change_zoom_factor"
- android:layout_width="wrap_content"
- android:layout_height="@dimen/zoomed_view_toolbar_height"
- android:background="@android:color/transparent"
- android:padding="12dip"
- android:layout_alignLeft="@+id/zoomed_image_view"
- android:textSize="16sp"
- android:textColor="@color/text_and_tabs_tray_grey"
- android:visibility="invisible"/>
- <ImageView
- android:id="@+id/dialog_close"
- android:scaleType="center"
- android:layout_width="@dimen/zoomed_view_toolbar_height"
- android:layout_height="@dimen/zoomed_view_toolbar_height"
- android:layout_alignRight="@id/zoomed_image_view"
- android:src="@drawable/close_edit_mode_selector"/>
- <ImageView
- android:id="@id/zoomed_image_view"
- android:layout_below="@id/change_zoom_factor"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- </RelativeLayout>
-
-</org.mozilla.gecko.ZoomedView> \ No newline at end of file
diff --git a/mobile/android/base/resources/menu-large/browser_app_menu.xml b/mobile/android/base/resources/menu-large/browser_app_menu.xml
deleted file mode 100644
index a4a8dbd4a..000000000
--- a/mobile/android/base/resources/menu-large/browser_app_menu.xml
+++ /dev/null
@@ -1,116 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- We disable AlwaysShowAction because we interpret the menu
- attributes ourselves and thus the warning isn't relevant to us. -->
-<menu xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- tools:ignore="AlwaysShowAction">
-
- <item android:id="@+id/reload"
- android:icon="@drawable/ic_menu_reload"
- android:title="@string/reload"
- android:showAsAction="always"/>
-
- <item android:id="@+id/back"
- android:icon="@drawable/ic_menu_back"
- android:title="@string/back"
- android:visible="false"/>
-
- <item android:id="@+id/forward"
- android:icon="@drawable/ic_menu_forward"
- android:title="@string/forward"
- android:visible="false"/>
-
- <item android:id="@+id/bookmark"
- android:icon="@drawable/ic_menu_bookmark_add"
- android:title="@string/bookmark"
- android:showAsAction="ifRoom"/>
-
- <item android:id="@+id/share"
- android:icon="@drawable/ic_menu_share"
- android:title="@string/share"
- android:showAsAction="ifRoom"/>
-
- <item android:id="@+id/new_tab"
- android:title="@string/new_tab"/>
-
- <item android:id="@+id/new_private_tab"
- android:title="@string/new_private_tab"/>
-
- <item android:id="@+id/bookmarks_list"
- android:title="@string/bookmarks_title"/>
-
- <item android:id="@+id/history_list"
- android:title="@string/history_title"/>
-
- <item android:id="@+id/find_in_page"
- android:title="@string/find_in_page" />
-
- <item android:id="@+id/desktop_mode"
- android:title="@string/desktop_mode"
- android:checkable="true" />
-
- <item android:id="@+id/page"
- android:title="@string/page">
-
- <menu>
-
- <item android:id="@+id/subscribe"
- android:title="@string/contextmenu_subscribe"/>
-
- <item android:id="@+id/save_as_pdf"
- android:title="@string/save_as_pdf"/>
-
- <item android:id="@+id/print"
- android:title="@string/print"/>
-
- <item android:id="@+id/add_search_engine"
- android:title="@string/contextmenu_add_search_engine"/>
-
- <item android:id="@+id/add_to_launcher"
- android:title="@string/contextmenu_add_to_launcher"/>
-
- </menu>
-
- </item>
-
- <item android:id="@+id/tools"
- android:title="@string/tools">
-
- <menu>
-
- <item android:id="@+id/downloads"
- android:title="@string/downloads"/>
-
- <item android:id="@+id/addons"
- android:title="@string/addons"/>
-
- <item android:id="@+id/logins"
- android:title="@string/logins"/>
-
- <item android:id="@+id/new_guest_session"
- android:visible="false"
- android:title="@string/new_guest_session"/>
-
- <item android:id="@+id/exit_guest_session"
- android:visible="false"
- android:title="@string/exit_guest_session"/>
-
- </menu>
-
- </item>
-
- <item android:id="@+id/char_encoding"
- android:visible="false"
- android:title="@string/char_encoding"/>
-
- <item android:id="@+id/settings"
- android:title="@string/settings" />
-
- <item android:id="@+id/help"
- android:title="@string/help_menu" />
-
-</menu>
diff --git a/mobile/android/base/resources/menu-v11/preferences_search_menu.xml b/mobile/android/base/resources/menu-v11/preferences_search_menu.xml
deleted file mode 100644
index 11fe2cadd..000000000
--- a/mobile/android/base/resources/menu-v11/preferences_search_menu.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@+id/restore_defaults"
- android:drawable="@drawable/menu"
- android:showAsAction="never"
- android:title="@string/pref_search_restore_defaults" />
-</menu>
diff --git a/mobile/android/base/resources/menu-v11/tabs_menu.xml b/mobile/android/base/resources/menu-v11/tabs_menu.xml
deleted file mode 100644
index bd974e25b..000000000
--- a/mobile/android/base/resources/menu-v11/tabs_menu.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:id="@+id/new_tab"
- android:title="@string/new_tab"/>
-
- <item android:id="@+id/new_private_tab"
- android:title="@string/new_private_tab"/>
-
- <item android:id="@+id/close_all_tabs"
- android:title="@string/close_all_tabs"/>
-
- <item android:id="@+id/close_private_tabs"
- android:title="@string/close_private_tabs"/>
-
-</menu>
diff --git a/mobile/android/base/resources/menu-v11/titlebar_contextmenu.xml b/mobile/android/base/resources/menu-v11/titlebar_contextmenu.xml
deleted file mode 100644
index 0027f0542..000000000
--- a/mobile/android/base/resources/menu-v11/titlebar_contextmenu.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:id="@+id/pasteandgo"
- android:title="@string/contextmenu_pasteandgo"/>
-
- <item android:id="@+id/paste"
- android:title="@string/contextmenu_paste"/>
-
- <item android:id="@+id/copyurl"
- android:title="@string/contextmenu_copyurl"/>
-
- <item android:id="@+id/add_to_launcher"
- android:title="@string/contextmenu_add_to_launcher"/>
-
-</menu>
diff --git a/mobile/android/base/resources/menu-xlarge/browser_app_menu.xml b/mobile/android/base/resources/menu-xlarge/browser_app_menu.xml
deleted file mode 100644
index 817a8690a..000000000
--- a/mobile/android/base/resources/menu-xlarge/browser_app_menu.xml
+++ /dev/null
@@ -1,117 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- We disable AlwaysShowAction because we interpret the menu
- attributes ourselves and thus the warning isn't relevant to us. -->
-<menu xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- tools:ignore="AlwaysShowAction">
-
- <item android:id="@+id/reload"
- android:icon="@drawable/ic_menu_reload"
- android:title="@string/reload"
- android:showAsAction="always"/>
-
- <item android:id="@+id/back"
- android:icon="@drawable/ic_menu_back"
- android:title="@string/back"
- android:visible="false"/>
-
- <item android:id="@+id/forward"
- android:icon="@drawable/ic_menu_forward"
- android:title="@string/forward"
- android:visible="false"/>
-
- <item android:id="@+id/bookmark"
- android:icon="@drawable/ic_menu_bookmark_add"
- android:title="@string/bookmark"
- android:showAsAction="always"/>
-
- <item android:id="@+id/share"
- android:icon="@drawable/ic_menu_share"
- android:title="@string/share"
- android:showAsAction="ifRoom"/>
-
- <item android:id="@+id/new_tab"
- android:title="@string/new_tab"/>
-
- <item android:id="@+id/new_private_tab"
- android:title="@string/new_private_tab"/>
-
- <item android:id="@+id/bookmarks_list"
- android:title="@string/bookmarks_title"/>
-
- <item android:id="@+id/history_list"
- android:title="@string/history_title"/>
-
- <item android:id="@+id/find_in_page"
- android:title="@string/find_in_page" />
-
- <item android:id="@+id/desktop_mode"
- android:title="@string/desktop_mode"
- android:checkable="true" />
-
-
- <item android:id="@+id/page"
- android:title="@string/page">
-
- <menu>
-
- <item android:id="@+id/subscribe"
- android:title="@string/contextmenu_subscribe"/>
-
- <item android:id="@+id/save_as_pdf"
- android:title="@string/save_as_pdf"/>
-
- <item android:id="@+id/print"
- android:title="@string/print"/>
-
- <item android:id="@+id/add_search_engine"
- android:title="@string/contextmenu_add_search_engine"/>
-
- <item android:id="@+id/add_to_launcher"
- android:title="@string/contextmenu_add_to_launcher"/>
-
- </menu>
-
- </item>
-
- <item android:id="@+id/tools"
- android:title="@string/tools">
-
- <menu>
-
- <item android:id="@+id/downloads"
- android:title="@string/downloads"/>
-
- <item android:id="@+id/addons"
- android:title="@string/addons"/>
-
- <item android:id="@+id/logins"
- android:title="@string/logins"/>
-
- <item android:id="@+id/new_guest_session"
- android:visible="false"
- android:title="@string/new_guest_session"/>
-
- <item android:id="@+id/exit_guest_session"
- android:visible="false"
- android:title="@string/exit_guest_session"/>
-
- </menu>
-
- </item>
-
- <item android:id="@+id/char_encoding"
- android:visible="false"
- android:title="@string/char_encoding"/>
-
- <item android:id="@+id/settings"
- android:title="@string/settings" />
-
- <item android:id="@+id/help"
- android:title="@string/help_menu" />
-
-</menu>
diff --git a/mobile/android/base/resources/menu/activitystream_contextmenu.xml b/mobile/android/base/resources/menu/activitystream_contextmenu.xml
deleted file mode 100644
index ce22f14dc..000000000
--- a/mobile/android/base/resources/menu/activitystream_contextmenu.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- Group ID's are required, otherwise NavigationView won't show any dividers. The ID's are unused, but still required. -->
- <group android:id="@+id/group0">
- <item
- android:id="@+id/bookmark"
- android:icon="@drawable/as_bookmark"
- android:title="@string/bookmark"/>
- <item
- android:id="@+id/share"
- android:icon="@drawable/as_share"
- android:title="@string/share"/>
- <item
- android:id="@+id/copy_url"
- android:icon="@drawable/as_copy"
- android:title="@string/contextmenu_copyurl"/>
- <item
- android:id="@+id/add_homescreen"
- android:icon="@drawable/as_home"
- android:title="@string/contextmenu_add_to_launcher"/>
- </group>
-
- <group android:id="@+id/group1">
- <item
- android:id="@+id/open_new_tab"
- android:icon="@drawable/as_tab"
- android:title="@string/contextmenu_open_new_tab"/>
- <item
- android:id="@+id/open_new_private_tab"
- android:icon="@drawable/as_private"
- android:title="@string/contextmenu_open_private_tab"/>
- </group>
-
-
- <group android:id="@+id/group2">
- <item
- android:id="@+id/dismiss"
- android:icon="@drawable/as_dimiss"
- android:title="@string/activity_stream_dismiss"/>
-
- <item
- android:id="@+id/delete"
- android:icon="@drawable/as_bin"
- android:visible="false"
- android:title="@string/activity_stream_delete_history"/>
- </group>
-</menu> \ No newline at end of file
diff --git a/mobile/android/base/resources/menu/browser_app_menu.xml b/mobile/android/base/resources/menu/browser_app_menu.xml
deleted file mode 100644
index f831ee783..000000000
--- a/mobile/android/base/resources/menu/browser_app_menu.xml
+++ /dev/null
@@ -1,116 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- We disable AlwaysShowAction because we interpret the menu
- attributes ourselves and thus the warning isn't relevant to us. -->
-<menu xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- tools:ignore="AlwaysShowAction">
-
- <item android:id="@+id/back"
- android:icon="@drawable/ic_menu_back"
- android:title="@string/back"
- android:showAsAction="always"/>
-
- <item android:id="@+id/forward"
- android:icon="@drawable/ic_menu_forward"
- android:title="@string/forward"
- android:showAsAction="always"/>
-
- <item android:id="@+id/bookmark"
- android:icon="@drawable/ic_menu_bookmark_add"
- android:title="@string/bookmark"
- android:showAsAction="always"/>
-
- <item android:id="@+id/reload"
- android:icon="@drawable/ic_menu_reload"
- android:title="@string/reload"
- android:showAsAction="always"/>
-
- <item android:id="@+id/share"
- android:icon="@drawable/ic_menu_share"
- android:title="@string/share"
- android:showAsAction="ifRoom"/>
-
- <item android:id="@+id/new_tab"
- android:title="@string/new_tab"/>
-
- <item android:id="@+id/new_private_tab"
- android:title="@string/new_private_tab"/>
-
- <item android:id="@+id/bookmarks_list"
- android:title="@string/bookmarks_title"/>
-
- <item android:id="@+id/history_list"
- android:title="@string/history_title"/>
-
- <item android:id="@+id/find_in_page"
- android:title="@string/find_in_page" />
-
- <item android:id="@+id/desktop_mode"
- android:title="@string/desktop_mode"
- android:checkable="true" />
-
- <item android:id="@+id/page"
- android:title="@string/page">
-
- <menu>
-
- <item android:id="@+id/subscribe"
- android:title="@string/contextmenu_subscribe"/>
-
- <item android:id="@+id/save_as_pdf"
- android:title="@string/save_as_pdf"/>
-
- <item android:id="@+id/print"
- android:title="@string/print"/>
-
- <item android:id="@+id/add_search_engine"
- android:title="@string/contextmenu_add_search_engine"/>
-
- <item android:id="@+id/add_to_launcher"
- android:title="@string/contextmenu_add_to_launcher"/>
-
- </menu>
-
- </item>
-
- <item android:id="@+id/tools"
- android:title="@string/tools">
-
- <menu>
-
- <item android:id="@+id/downloads"
- android:title="@string/downloads"/>
-
- <item android:id="@+id/addons"
- android:title="@string/addons"/>
-
- <item android:id="@+id/logins"
- android:title="@string/logins"/>
-
- <item android:id="@+id/new_guest_session"
- android:visible="false"
- android:title="@string/new_guest_session"/>
-
- <item android:id="@+id/exit_guest_session"
- android:visible="false"
- android:title="@string/exit_guest_session"/>
-
- </menu>
-
- </item>
-
- <item android:id="@+id/char_encoding"
- android:visible="false"
- android:title="@string/char_encoding"/>
-
- <item android:id="@+id/settings"
- android:title="@string/settings" />
-
- <item android:id="@+id/help"
- android:title="@string/help_menu" />
-
-</menu>
diff --git a/mobile/android/base/resources/menu/browsersearch_contextmenu.xml b/mobile/android/base/resources/menu/browsersearch_contextmenu.xml
deleted file mode 100644
index 335490825..000000000
--- a/mobile/android/base/resources/menu/browsersearch_contextmenu.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:id="@+id/browsersearch_remove"
- android:title="@string/contextmenu_remove"/>
-
-</menu>
diff --git a/mobile/android/base/resources/menu/gecko_app_menu.xml b/mobile/android/base/resources/menu/gecko_app_menu.xml
deleted file mode 100644
index a55804fcb..000000000
--- a/mobile/android/base/resources/menu/gecko_app_menu.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@+id/quit"
- android:title="@string/quit"
- android:orderInCategory="10" />
-</menu>
diff --git a/mobile/android/base/resources/menu/home_contextmenu.xml b/mobile/android/base/resources/menu/home_contextmenu.xml
deleted file mode 100644
index 294b8aee5..000000000
--- a/mobile/android/base/resources/menu/home_contextmenu.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:id="@+id/home_open_new_tab"
- android:title="@string/contextmenu_open_new_tab"/>
-
- <item android:id="@+id/home_open_private_tab"
- android:title="@string/contextmenu_open_private_tab"/>
-
- <item android:id="@+id/home_copyurl"
- android:title="@string/contextmenu_copyurl"/>
-
- <item android:id="@+id/home_share"
- android:title="@string/contextmenu_share"/>
-
- <item android:id="@+id/top_sites_edit"
- android:title="@string/contextmenu_top_sites_edit"/>
-
- <item android:id="@+id/top_sites_pin"
- android:title="@string/contextmenu_top_sites_pin"/>
-
- <item android:id="@+id/top_sites_unpin"
- android:title="@string/contextmenu_top_sites_unpin"/>
-
- <item android:id="@+id/home_edit_bookmark"
- android:title="@string/contextmenu_edit_bookmark"/>
-
- <item android:id="@+id/home_remove"
- android:title="@string/contextmenu_remove"/>
-
- <item android:id="@+id/home_add_to_launcher"
- android:title="@string/contextmenu_add_to_launcher"/>
-
-</menu>
diff --git a/mobile/android/base/resources/menu/home_remote_tabs_client_contextmenu.xml b/mobile/android/base/resources/menu/home_remote_tabs_client_contextmenu.xml
deleted file mode 100644
index cd6310bdb..000000000
--- a/mobile/android/base/resources/menu/home_remote_tabs_client_contextmenu.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:id="@+id/home_remote_tabs_hide_client"
- android:title="@string/pref_panels_hide"/>
-
-</menu>
diff --git a/mobile/android/base/resources/menu/preferences_search_menu.xml b/mobile/android/base/resources/menu/preferences_search_menu.xml
deleted file mode 100644
index f3b2453d1..000000000
--- a/mobile/android/base/resources/menu/preferences_search_menu.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- Stub to preserve IDs. -->
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@+id/restore_defaults"
- android:title="" />
-</menu>
diff --git a/mobile/android/base/resources/menu/tabs_menu.xml b/mobile/android/base/resources/menu/tabs_menu.xml
deleted file mode 100644
index bd974e25b..000000000
--- a/mobile/android/base/resources/menu/tabs_menu.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:id="@+id/new_tab"
- android:title="@string/new_tab"/>
-
- <item android:id="@+id/new_private_tab"
- android:title="@string/new_private_tab"/>
-
- <item android:id="@+id/close_all_tabs"
- android:title="@string/close_all_tabs"/>
-
- <item android:id="@+id/close_private_tabs"
- android:title="@string/close_private_tabs"/>
-
-</menu>
diff --git a/mobile/android/base/resources/menu/titlebar_contextmenu.xml b/mobile/android/base/resources/menu/titlebar_contextmenu.xml
deleted file mode 100644
index 72c41cdc5..000000000
--- a/mobile/android/base/resources/menu/titlebar_contextmenu.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item android:id="@+id/pasteandgo"
- android:title="@string/contextmenu_pasteandgo"/>
-
- <item android:id="@+id/paste"
- android:title="@string/contextmenu_paste"/>
-
- <item android:id="@+id/subscribe"
- android:title="@string/contextmenu_subscribe"/>
-
- <item android:id="@+id/add_search_engine"
- android:title="@string/contextmenu_add_search_engine"/>
-
- <item android:id="@+id/copyurl"
- android:title="@string/contextmenu_copyurl"/>
-
- <item android:id="@+id/add_to_launcher"
- android:title="@string/contextmenu_add_to_launcher"/>
-
-</menu>
diff --git a/mobile/android/base/resources/raw/bookmarkdefaults_favicon_addons.png b/mobile/android/base/resources/raw/bookmarkdefaults_favicon_addons.png
deleted file mode 100644
index 2fbc630e6..000000000
--- a/mobile/android/base/resources/raw/bookmarkdefaults_favicon_addons.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/raw/bookmarkdefaults_favicon_restricted_support.png b/mobile/android/base/resources/raw/bookmarkdefaults_favicon_restricted_support.png
deleted file mode 100644
index ea7d6a3ae..000000000
--- a/mobile/android/base/resources/raw/bookmarkdefaults_favicon_restricted_support.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/raw/bookmarkdefaults_favicon_restricted_webmaker.png b/mobile/android/base/resources/raw/bookmarkdefaults_favicon_restricted_webmaker.png
deleted file mode 100644
index ad177aa86..000000000
--- a/mobile/android/base/resources/raw/bookmarkdefaults_favicon_restricted_webmaker.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/raw/bookmarkdefaults_favicon_support.png b/mobile/android/base/resources/raw/bookmarkdefaults_favicon_support.png
deleted file mode 100644
index 3255e4cfd..000000000
--- a/mobile/android/base/resources/raw/bookmarkdefaults_favicon_support.png
+++ /dev/null
Binary files differ
diff --git a/mobile/android/base/resources/raw/fake_home_items.json b/mobile/android/base/resources/raw/fake_home_items.json
deleted file mode 100644
index 59b4ab74e..000000000
--- a/mobile/android/base/resources/raw/fake_home_items.json
+++ /dev/null
@@ -1,15 +0,0 @@
-[{
- "id": 1,
- "dataset_id": "fake-dataset",
- "url": "http://example.com/first",
- "title": "First Example",
- "description": "This is an example",
- "image_url": "http://lorempixel.com/64/64?id=1"
-}, {
- "id": 2,
- "dataset_id": "fake-dataset",
- "url": "http://example.com/second",
- "title": "Second Example",
- "description": "This is a second example",
- "image_url": "http://lorempixel.com/64/64?id=2"
-}]
diff --git a/mobile/android/base/resources/raw/topdomains.txt b/mobile/android/base/resources/raw/topdomains.txt
deleted file mode 100644
index de1c19f4b..000000000
--- a/mobile/android/base/resources/raw/topdomains.txt
+++ /dev/null
@@ -1,455 +0,0 @@
-google.com
-facebook.com
-amazon.com
-youtube.com
-yahoo.com
-ebay.com
-wikipedia.org
-twitter.com
-reddit.com
-go.com
-craigslist.org
-live.com
-netflix.com
-pinterest.com
-bing.com
-linkedin.com
-imgur.com
-espn.go.com
-walmart.com
-tumblr.com
-target.com
-paypal.com
-cnn.com
-chase.com
-instagram.com
-bestbuy.com
-blogspot.com
-nytimes.com
-msn.com
-imdb.com
-apple.com
-bankofamerica.com
-diply.com
-huffingtonpost.com
-yelp.com
-wellsfargo.com
-etsy.com
-weather.com
-wordpress.com
-buzzfeed.com
-zillow.com
-kohls.com
-aol.com
-homedepot.com
-foxnews.com
-microsoft.com
-comcast.net
-wikia.com
-groupon.com
-macys.com
-washingtonpost.com
-outbrain.com
-microsoftonline.com
-xfinity.com
-usps.com
-hulu.com
-americanexpress.com
-slickdeals.net
-pandora.com
-office.com
-cnet.com
-indeed.com
-capitalone.com
-nfl.com
-ups.com
-ask.com
-verizonwireless.com
-newegg.com
-usatoday.com
-forbes.com
-dailymail.co.uk
-dropbox.com
-att.com
-costco.com
-gfycat.com
-lowes.com
-gap.com
-about.com
-tripadvisor.com
-fedex.com
-baidu.com
-vice.com
-nordstrom.com
-adobe.com
-bbc.com
-twitch.tv
-allrecipes.com
-retailmenot.com
-stackoverflow.com
-citi.com
-sears.com
-jcpenney.com
-webmd.com
-ijreview.com
-nih.gov
-answers.com
-foodnetwork.com
-discovercard.com
-cbssports.com
-overstock.com
-businessinsider.com
-office365.com
-theguardian.com
-staples.com
-bleacherreport.com
-toysrus.com
-verizon.com
-github.com
-wayfair.com
-salesforce.com
-zulily.com
-wsj.com
-flickr.com
-goodreads.com
-realtor.com
-nbcnews.com
-ebates.com
-ancestry.com
-wunderground.com
-instructure.com
-people.com
-stackexchange.com
-drudgereport.com
-fidelity.com
-southwest.com
-deviantart.com
-thesaurus.com
-intuit.com
-woot.com
-pch.com
-soundcloud.com
-force.com
-samsclub.com
-ign.com
-qvc.com
-npr.org
-patch.com
-dell.com
-accuweather.com
-vimeo.com
-expedia.com
-trulia.com
-ca.gov
-swagbucks.com
-spotify.com
-bedbathandbeyond.com
-nypost.com
-aliexpress.com
-blackboard.com
-ticketmaster.com
-ikea.com
-feedly.com
-usaa.com
-tmz.com
-quora.com
-lifehacker.com
-kayak.com
-reference.com
-zappos.com
-gizmodo.com
-slate.com
-faithtap.com
-adp.com
-abcnews.go.com
-sephora.com
-cbs.com
-latimes.com
-shutterfly.com
-t-mobile.com
-littlethings.com
-glassdoor.com
-bloomberg.com
-cbsnews.com
-wikihow.com
-walgreens.com
-usbank.com
-blogger.com
-weebly.com
-gamestop.com
-food.com
-time.com
-kickstarter.com
-okcupid.com
-aa.com
-weather.gov
-nametests.com
-fandango.com
-engadget.com
-steamcommunity.com
-thekitchn.com
-nba.com
-mashable.com
-hp.com
-gamefaqs.com
-delta.com
-breitbart.com
-coupons.com
-eonline.com
-surveymonkey.com
-kmart.com
-barnesandnoble.com
-meetup.com
-bhphotovideo.com
-fanduel.com
-quizlet.com
-nydailynews.com
-sbnation.com
-nbcsports.com
-likes.com
-bbc.co.uk
-ew.com
-nike.com
-rottentomatoes.com
-steampowered.com
-reuters.com
-qq.com
-today.com
-mapquest.com
-audible.com
-priceline.com
-whitepages.com
-united.com
-myfitnesspal.com
-icloud.com
-forever21.com
-theatlantic.com
-microsoftstore.com
-theverge.com
-gawker.com
-houzz.com
-mayoclinic.org
-rei.com
-sfgate.com
-lifebuzz.com
-discover.com
-pnc.com
-pof.com
-iflscience.com
-popsugar.com
-creditkarma.com
-telegraph.co.uk
-airbnb.com
-buzzlie.com
-cnbc.com
-deadspin.com
-sina.com.cn
-legacy.com
-thedailybeast.com
-samsung.com
-nextdoor.com
-evite.com
-shopify.com
-yellowpages.com
-pcmag.com
-redfin.com
-emgn.com
-weibo.com
-alibaba.com
-cabelas.com
-battle.net
-foxsports.com
-taobao.com
-eventbrite.com
-victoriassecret.com
-theblaze.com
-dealnews.com
-cbslocal.com
-cvs.com
-dailymotion.com
-ecollege.com
-gofundme.com
-fitbit.com
-instructables.com
-godaddy.com
-babycenter.com
-squarespace.com
-llbean.com
-dickssportinggoods.com
-6pm.com
-myway.com
-hsn.com
-wired.com
-officedepot.com
-ozztube.com
-usmagazine.com
-match.com
-cracked.com
-evernote.com
-box.com
-starbucks.com
-kbb.com
-mlb.com
-marriott.com
-si.com
-jezebel.com
-pbs.org
-consumerreports.org
-roblox.com
-urbandictionary.com
-kotaku.com
-xbox.com
-marketwatch.com
-refinery29.com
-wikimedia.org
-tvguide.com
-politico.com
-barclaycardus.com
-abc.go.com
-mint.com
-topix.com
-theblackfriday.com
-aarp.org
-hotnewhiphop.com
-yourdailydish.com
-sprint.com
-vox.com
-cafemom.com
-nbc.com
-dailykos.com
-azlyrics.com
-autotrader.com
-hilton.com
-irs.gov
-monster.com
-fatwallet.com
-mailchimp.com
-webex.com
-landsend.com
-wix.com
-usnews.com
-jcrew.com
-jet.com
-capitalone360.com
-sharepoint.com
-schwab.com
-ulta.com
-vistaprint.com
-rollingstone.com
-biblegateway.com
-gamespot.com
-io9.com
-opentable.com
-hm.com
-duckduckgo.com
-chron.com
-photobucket.com
-shareasale.com
-directv.com
-avg.com
-oracle.com
-hotels.com
-timewarnercable.com
-chicagotribune.com
-ehow.com
-primewire.ag
-abs-cbnnews.com
-salon.com
-greatergood.com
-epicurious.com
-fool.com
-patheos.com
-custhelp.com
-purdue.edu
-tickld.com
-frys.com
-indiatimes.com
-amazon.co.uk
-zendesk.com
-tigerdirect.com
-stubhub.com
-healthcare.gov
-archive.org
-qualtrics.com
-ravelry.com
-cars.com
-redbox.com
-jalopnik.com
-speedtest.net
-harvard.edu
-slideshare.net
-kinja.com
-nesn.com
-michaels.com
-mit.edu
-bodybuilding.com
-edmunds.com
-nhl.com
-zergnet.com
-terraclicks.com
-techcrunch.com
-regnok.com
-pogo.com
-backpage.com
-mozilla.org
-naver.com
-giphy.com
-bankrate.com
-msnbc.com
-digitaltrends.com
-fanfiction.net
-skype.com
-disney.go.com
-norton.com
-androidcentral.com
-tomshardware.com
-thefreedictionary.com
-liveleak.com
-247sports.com
-merriam-webster.com
-wnd.com
-earthlink.net
-conservativetribune.com
-independent.co.uk
-drugs.com
-rotoworld.com
-nationalgeographic.com
-ae.com
-noaa.gov
-arstechnica.com
-thinkgeek.com
-stanford.edu
-bizjournals.com
-hootsuite.com
-genius.com
-goodhousekeeping.com
-vanguard.com
-ny.gov
-citibankonline.com
-booking.com
-mic.com
-orbitz.com
-dominos.com
-medium.com
-wow.com
-urbanoutfitters.com
-douban.com
-timeanddate.com
-draftkings.com
-livestrong.com
-livingsocial.com
-cox.net
-theonion.com
-marthastewart.com
-comenity.net
-worldlifestyle.com
-disney.com
-realsimple.com
-vrbo.com
-playstation.com
-potterybarn.com
-zazzle.com
-ksl.com
-tdbank.com
-sourceforge.net
-careerbuilder.com
diff --git a/mobile/android/base/resources/values-land/dimens.xml b/mobile/android/base/resources/values-land/dimens.xml
deleted file mode 100644
index 561b81b76..000000000
--- a/mobile/android/base/resources/values-land/dimens.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <!-- Remote Tabs static view top padding. Less in landscape on phones. -->
- <dimen name="home_remote_tabs_top_padding">16dp</dimen>
- <dimen name="tab_panel_grid_padding">48dp</dimen>
-</resources>
diff --git a/mobile/android/base/resources/values-land/integers.xml b/mobile/android/base/resources/values-land/integers.xml
deleted file mode 100644
index c70badd26..000000000
--- a/mobile/android/base/resources/values-land/integers.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <integer name="number_of_top_sites_cols">3</integer>
- <integer name="panel_icon_grid_view_columns">6</integer>
-
-</resources>
diff --git a/mobile/android/base/resources/values-land/styles.xml b/mobile/android/base/resources/values-land/styles.xml
deleted file mode 100644
index b9362223a..000000000
--- a/mobile/android/base/resources/values-land/styles.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <style name="TabsLayout" parent="TabsLayoutBase">
- <item name="android:orientation">horizontal</item>
- <item name="android:scrollbars">horizontal</item>
- </style>
-
- <style name="TabsItem">
- <item name="android:nextFocusDown">@+id/close</item>
- </style>
-
- <style name="TabsItemClose">
- <item name="android:nextFocusUp">@+id/info</item>
- </style>
-
- <!-- Tabs panel -->
- <style name="TabsPanelSection" parent="TabsPanelSectionBase">
- <item name="android:layout_weight">1</item>
- </style>
-
- <style name="TabsPanelItem">
- <item name="android:layout_marginBottom">20dp</item>
- <item name="android:layout_gravity">left</item>
- <item name="android:gravity">left</item>
- </style>
-
-</resources>
diff --git a/mobile/android/base/resources/values-large-land-v11/dimens.xml b/mobile/android/base/resources/values-large-land-v11/dimens.xml
deleted file mode 100644
index 4d14ba835..000000000
--- a/mobile/android/base/resources/values-large-land-v11/dimens.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <!-- Remote Tabs static view top padding. Less in landscape on phones. -->
- <dimen name="home_remote_tabs_top_padding">48dp</dimen>
- <dimen name="home_header_item_height">64dp</dimen>
-
-</resources>
diff --git a/mobile/android/base/resources/values-large-land-v11/styles.xml b/mobile/android/base/resources/values-large-land-v11/styles.xml
deleted file mode 100644
index 14fbdf342..000000000
--- a/mobile/android/base/resources/values-large-land-v11/styles.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <style name="TabsLayout" parent="TabsLayoutBase">
- <item name="android:scrollbars">vertical</item>
- </style>
-
- <style name="Widget.BookmarksListView" parent="Widget.HomeListView">
- <item name="android:scrollbarStyle">outsideOverlay</item>
- </style>
-
- <style name="Widget.TopSitesGridView" parent="Widget.GridView">
- <item name="android:paddingLeft">55dp</item>
- <item name="android:paddingRight">55dp</item>
- <item name="android:paddingBottom">30dp</item>
- <item name="android:horizontalSpacing">20dp</item>
- <item name="android:verticalSpacing">20dp</item>
- </style>
-
- <!-- Tabs panel -->
- <style name="TabsPanelSection" parent="TabsPanelSectionBase">
- <item name="android:layout_marginLeft">20dp</item>
- <item name="android:layout_marginRight">20dp</item>
- </style>
-
- <style name="TabsPanelItem" parent="TabsPanelItemBase">
- <!-- To override the values-land style. -->
- </style>
-
-</resources>
diff --git a/mobile/android/base/resources/values-large-v16/dimens.xml b/mobile/android/base/resources/values-large-v16/dimens.xml
deleted file mode 100644
index 80ce7118d..000000000
--- a/mobile/android/base/resources/values-large-v16/dimens.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
- <dimen name="doorhanger_offsetY">124dp</dimen>
-</resources>
diff --git a/mobile/android/base/resources/values-large-v16/styles.xml b/mobile/android/base/resources/values-large-v16/styles.xml
deleted file mode 100644
index 1c57a046e..000000000
--- a/mobile/android/base/resources/values-large-v16/styles.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="TextAppearance.UrlBar.Title" parent="TextAppearance.Small">
- <item name="android:textSize">16sp</item>
- <item name="android:fontFamily">sans-serif-light</item>
- </style>
-
-</resources>
diff --git a/mobile/android/base/resources/values-large/bool.xml b/mobile/android/base/resources/values-large/bool.xml
deleted file mode 100644
index 73fde40be..000000000
--- a/mobile/android/base/resources/values-large/bool.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ This Source Code Form is subject to the terms of the Mozilla Public
- ~ License, v. 2.0. If a copy of the MPL was not distributed with this
- ~ file, you can obtain one at http://mozilla.org/MPL/2.0/.
- -->
-
-<resources>
- <!-- See definition in values/ for explanation. -->
- <bool name="is_large_resource">true</bool>
-</resources>
diff --git a/mobile/android/base/resources/values-large/dimens.xml b/mobile/android/base/resources/values-large/dimens.xml
deleted file mode 100644
index a94ef6fb1..000000000
--- a/mobile/android/base/resources/values-large/dimens.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <dimen name="doorhanger_offsetY">100dp</dimen>
-
- <dimen name="browser_toolbar_height">56dp</dimen>
- <!-- This value is the height of the Tabs Panel header view
- (browser_toolbar_height) minus the height of the indicator
- (6dp). This value should change when the height of the view changes. -->
- <dimen name="tabs_panel_indicator_selected_padding_top">50dp</dimen>
-
- <dimen name="browser_toolbar_height_flipper">60dp</dimen>
- <dimen name="browser_toolbar_button_padding">16dp</dimen>
- <dimen name="browser_toolbar_favicon_size">16dp</dimen>
-
- <dimen name="browser_toolbar_site_security_height">60dp</dimen>
- <dimen name="browser_toolbar_site_security_width">34dp</dimen>
- <dimen name="browser_toolbar_site_security_margin_right">1dp</dimen>
- <!-- We primarily use padding (instead of margins) to increase the hit area. -->
- <dimen name="browser_toolbar_site_security_padding_vertical">21dp</dimen>
- <dimen name="browser_toolbar_site_security_padding_horizontal">8dp</dimen>
-
- <dimen name="firstrun_background_height">300dp</dimen>
-
- <dimen name="tabs_panel_indicator_width">72dp</dimen>
- <dimen name="tabs_panel_button_width">60dp</dimen>
- <dimen name="panel_grid_view_column_width">200dp</dimen>
-
- <dimen name="overlay_prompt_container_width">360dp</dimen>
-
- <item name="tab_strip_content_start" type="dimen">72dp</item>
-
-</resources>
diff --git a/mobile/android/base/resources/values-large/integers.xml b/mobile/android/base/resources/values-large/integers.xml
deleted file mode 100644
index 2688e5de5..000000000
--- a/mobile/android/base/resources/values-large/integers.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <integer name="number_of_inline_share_devices">3</integer>
- <integer name="max_search_suggestions">4</integer>
- <integer name="max_saved_suggestions">4</integer>
-
-</resources>
diff --git a/mobile/android/base/resources/values-large/styles.xml b/mobile/android/base/resources/values-large/styles.xml
deleted file mode 100644
index 79867a802..000000000
--- a/mobile/android/base/resources/values-large/styles.xml
+++ /dev/null
@@ -1,89 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <style name="UrlBar.ImageButton" parent="UrlBar.ImageButtonBase">
- <item name="android:layout_width">@dimen/tablet_browser_toolbar_menu_item_width</item>
- </style>
-
- <!-- If this style wasn't actually shared outside the
- url bar, this name could be improved (bug 1197424). -->
- <style name="UrlBar.ImageButton.BrowserToolbarColors">
- <item name="drawableTintList">@color/action_bar_menu_item_colors</item>
- </style>
-
- <style name="UrlBar.Button.Container">
- <item name="android:layout_marginTop">6dp</item>
- <item name="android:layout_marginBottom">6dp</item>
- <!-- Start with forward hidden -->
- <item name="android:orientation">horizontal</item>
- </style>
-
- <style name="TabsLayout" parent="TabsLayoutBase">
- <item name="android:scrollbars">vertical</item>
- </style>
-
- <style name="TabsItem">
- <item name="android:nextFocusDown">@+id/close</item>
- </style>
-
- <style name="TabsItemClose">
- <item name="android:nextFocusUp">@+id/info</item>
- </style>
-
- <style name="Toast" parent="ToastBase">
- <item name="android:layout_width">400dp</item>
-
- <!-- Same as pre-19 Toast style, but with no left and right margins.
- They're removed since large tablets are never going to be only 400dp wide. -->
- </style>
-
- <style name="Widget.MenuItemActionBar">
- <item name="android:layout_width">wrap_content</item>
- <item name="android:layout_height">wrap_content</item>
- <item name="android:background">@drawable/browser_toolbar_action_bar_button</item>
- <item name="drawableTintList">@color/action_bar_menu_item_colors</item>
- <item name="android:scaleType">center</item>
-
- <!-- layout_width/height doesn't work here, likely because it's only
- added programmatically, so we use padding for the width instead.
- layout_height is set to MATCH_PARENT programmatically in
- org.mozilla.gecko.toolbar.BrowserToolbarTabletBase.addActionItem(View) -->
-
- <item name="android:paddingLeft">@dimen/tablet_browser_toolbar_menu_item_padding_horizontal</item>
- <item name="android:paddingRight">@dimen/tablet_browser_toolbar_menu_item_padding_horizontal</item>
- </style>
-
- <style name="Widget.BookmarksListView" parent="Widget.HomeListView">
- <item name="android:scrollbarStyle">outsideOverlay</item>
- </style>
-
- <style name="Widget.TopSitesGridView" parent="Widget.GridView">
- <item name="android:paddingLeft">5dp</item>
- <item name="android:paddingRight">5dp</item>
- <item name="android:paddingBottom">30dp</item>
- <item name="android:horizontalSpacing">10dp</item>
- <item name="android:verticalSpacing">10dp</item>
- </style>
-
- <style name="Widget.TopSitesListView" parent="Widget.BookmarksListView">
- <item name="android:paddingTop">30dp</item>
- <item name="android:paddingLeft">32dp</item>
- <item name="android:paddingRight">32dp</item>
- <item name="android:clipToPadding">false</item>
- <item name="topDivider">false</item>
- </style>
-
- <style name="Widget.HomeBanner">
- <item name="android:paddingLeft">32dp</item>
- <item name="android:paddingRight">32dp</item>
- </style>
-
- <style name="TextAppearance.UrlBar.Title" parent="TextAppearance.Medium">
- <item name="android:textSize">16sp</item>
- </style>
-
-</resources>
diff --git a/mobile/android/base/resources/values-sw240dp/dimens.xml b/mobile/android/base/resources/values-sw240dp/dimens.xml
deleted file mode 100644
index ee2bf0e8d..000000000
--- a/mobile/android/base/resources/values-sw240dp/dimens.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
- <dimen name="tab_panel_column_width">143dip</dimen>
- <dimen name="tab_thumbnail_height">100dip</dimen>
- <dimen name="tab_thumbnail_width">135dip</dimen>
-</resources>
diff --git a/mobile/android/base/resources/values-sw360dp/dimens.xml b/mobile/android/base/resources/values-sw360dp/dimens.xml
deleted file mode 100644
index da14a185c..000000000
--- a/mobile/android/base/resources/values-sw360dp/dimens.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
- <dimen name="tab_panel_column_width">156dip</dimen>
- <dimen name="tab_thumbnail_height">110dip</dimen>
- <dimen name="tab_thumbnail_width">148dip</dimen>
-
- <dimen name="firstrun_background_height">180dp</dimen>
- <dimen name="firstrun_min_height">180dp</dimen>
-</resources>
diff --git a/mobile/android/base/resources/values-sw400dp/dimens.xml b/mobile/android/base/resources/values-sw400dp/dimens.xml
deleted file mode 100644
index 94567a220..000000000
--- a/mobile/android/base/resources/values-sw400dp/dimens.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
- <dimen name="tab_panel_column_width">176dip</dimen>
- <dimen name="tab_thumbnail_height">120dip</dimen>
- <dimen name="tab_thumbnail_width">168dip</dimen>
-</resources>
diff --git a/mobile/android/base/resources/values-v11/dimens.xml b/mobile/android/base/resources/values-v11/dimens.xml
deleted file mode 100644
index fbcb27bd1..000000000
--- a/mobile/android/base/resources/values-v11/dimens.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <!-- This is chosen to be close to Android's listPreferredItemHeightSmall.
- TODO: We should inherit these from the system.
- http://androidxref.com/4.2.2_r1/xref/frameworks/base/core/res/res/values/themes.xml#1287 -->
- <dimen name="menu_item_row_height">48dp</dimen>
-
-</resources>
diff --git a/mobile/android/base/resources/values-v11/styles.xml b/mobile/android/base/resources/values-v11/styles.xml
deleted file mode 100644
index 1550c18b8..000000000
--- a/mobile/android/base/resources/values-v11/styles.xml
+++ /dev/null
@@ -1,112 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <!--
- Only overriden styles for Honeycomb/Ice cream sandwich are specified here.
- Please refer to values/styles.xml for default styles.
- -->
-
- <!--
- Base application styles. This could be overridden in other res/values-XXX/themes.xml.
- -->
- <style name="Widget.BaseButton" parent="android:style/Widget.Holo.Light.Button"/>
-
- <style name="Widget.BaseDropDownItem" parent="android:style/Widget.Holo.Light.DropDownItem"/>
-
- <style name="Widget.BaseEditText" parent="android:style/Widget.Holo.Light.EditText"/>
-
- <style name="Widget.BaseListView" parent="android:style/Widget.Holo.ListView"/>
-
- <style name="Widget.BaseGridView" parent="android:style/Widget.Holo.GridView"/>
-
- <style name="Widget.BaseTextView" parent="android:style/Widget.Holo.Light.TextView"/>
-
- <style name="Widget.ProgressBar.Horizontal" parent="android:style/Widget.Holo.ProgressBar.Horizontal"/>
-
-
- <!--
- Application styles. All customizations that are not specific
- to a particular API level can go here.
- -->
- <style name="Widget.ListItem">
- <item name="android:textColor">@color/select_item_multichoice</item>
- <item name="android:minHeight">?android:attr/listPreferredItemHeight</item>
- <item name="android:textAppearance">?android:attr/textAppearanceLarge</item>
- <item name="android:gravity">center_vertical</item>
- <item name="android:paddingLeft">12dip</item>
- <item name="android:paddingRight">7dip</item>
- <item name="android:checkMark">?android:attr/listChoiceIndicatorMultiple</item>
- <item name="android:ellipsize">marquee</item>
- </style>
-
- <!-- ActionBar -->
- <style name="ActionBar" parent="android:style/Widget.Holo.ActionBar" />
-
- <!-- TabsLayout ActionBar -->
- <style name="ActionBar.TabsLayout">
- <item name="android:visibility">gone</item>
- </style>
-
- <!-- DropDown List View -->
- <style name="DropDownListView" parent="@android:style/Widget.Holo.ListView.DropDown">
- <item name="android:listSelector">@drawable/action_bar_button</item>
- <item name="android:divider">@color/toolbar_divider_grey</item>
- <item name="android:dividerHeight">@dimen/page_row_divider_height</item>
- </style>
-
- <!-- Spinner DropDown Item -->
- <style name="Widget.DropDownItem.Spinner" parent="@android:style/Widget.Holo.Light.DropDownItem.Spinner">
- <item name="android:textColor">#FF000000</item>
- </style>
-
- <style name="Widget.Spinner" parent="android:style/Widget.Holo.Light.Spinner">
- <item name="android:minWidth">@dimen/doorhanger_input_width</item>
- </style>
-
- <style name="Widget.TextView.SpinnerItem" parent="android:style/Widget.Holo.Light.TextView.SpinnerItem">
- <item name="android:textColor">#FF000000</item>
- </style>
-
- <style name="TextAppearance.Widget.ActionBar.Title" parent="@android:style/TextAppearance.Medium"/>
-
- <style name="GeckoActionBar.Title" parent="TextAppearance.Widget.ActionBar.Title">
- <item name="android:drawableLeft">@drawable/ab_done</item>
- <item name="android:background">@android:color/transparent</item>
- <item name="android:paddingLeft">15dp</item>
- <item name="android:paddingRight">15dp</item>
- <!-- gravity and minWidth are added here to more resemble our values/styles.xml
- counterpart. This is solely to correct bug 1233709 -->
- <item name="android:gravity">center_vertical</item>
- <item name="android:minWidth">0dp</item>
- </style>
-
- <style name="GeckoActionBar.Button" parent="android:style/Widget.Holo.Light.ActionButton">
- <item name="android:padding">8dip</item>
- <!-- The default implementation doesn't do any image scaling. Our custom menus mean we can't just use the same image
- in both menus and the actionbar without doing some scaling though. -->
- <item name="android:scaleType">centerInside</item>
- </style>
-
- <style name="GeckoActionBar.Button.MenuButton" parent="android:style/Widget.Holo.Light.ActionButton.Overflow">
- <item name="android:scaleType">centerInside</item>
- <item name="android:background">@android:color/transparent</item>
- <item name="android:src">@drawable/menu</item>
- <item name="android:tint">@color/toolbar_icon_grey</item>
- <item name="android:layout_marginTop">16dp</item>
- <item name="android:layout_marginBottom">16dp</item>
- </style>
-
- <style name="TabInput"></style>
-
- <style name="TabInput.TabWidget" parent="android:style/Widget.Holo.Light.TabWidget"/>
-
- <style name="TabInput.Tab" parent="android:style/Widget.Holo.Light.Tab">
- <item name="android:minHeight">@dimen/menu_item_row_height</item>
- <item name="android:textAllCaps">true</item>
- </style>
-
-</resources>
diff --git a/mobile/android/base/resources/values-v11/themes.xml b/mobile/android/base/resources/values-v11/themes.xml
deleted file mode 100644
index af8145226..000000000
--- a/mobile/android/base/resources/values-v11/themes.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <!--
- Base application theme. This could be overridden by GeckoBaseTheme
- in other res/values-XXX/themes.xml.
- -->
- <style name="GeckoBase" parent="Theme.AppCompat.Light.DarkActionBar">
- <item name="android:windowContentOverlay">@null</item>
- <item name="windowActionBar">false</item>
- <item name="windowNoTitle">true</item>
- </style>
-
- <style name="GeckoDialogBase" parent="@android:style/Theme.Holo.Light.Dialog">
- <item name="android:windowContentOverlay">@null</item>
- <item name="android:windowActionBar">false</item>
- <item name="android:windowNoTitle">true</item>
- <item name="android:windowBackground">@android:color/transparent</item>
- </style>
-
- <style name="GeckoTitleDialogBase" parent="@android:style/Theme.Holo.Light.Dialog" />
-
- <!--
- Activity based themes for API 11+. This theme completely replaces
- GeckoAppBase from res/values/themes.xml on API 11+ devices.
- -->
- <style name="GeckoAppBase" parent="Gecko">
- <item name="android:actionButtonStyle">@style/GeckoActionBar.Button</item>
- <item name="android:actionModeCopyDrawable">@drawable/ab_copy</item>
- <item name="android:actionModeCutDrawable">@drawable/ab_cut</item>
- <item name="android:actionModePasteDrawable">@drawable/ab_paste</item>
- <item name="android:listViewStyle">@style/Widget.ListView</item>
- <item name="android:spinnerDropDownItemStyle">@style/Widget.DropDownItem.Spinner</item>
- <item name="android:spinnerItemStyle">@style/Widget.TextView.SpinnerItem</item>
- <item name="menuItemSwitcherLayoutStyle">@style/Widget.MenuItemSwitcherLayout</item>
- <item name="menuItemDefaultStyle">@style/Widget.MenuItemDefault</item>
- <item name="menuItemSecondaryActionBarStyle">@style/Widget.MenuItemSecondaryActionBar</item>
- <item name="tabGridLayoutViewStyle">@style/Widget.TabsGridLayout</item>
- </style>
-
-</resources>
diff --git a/mobile/android/base/resources/values-v13/search_styles.xml b/mobile/android/base/resources/values-v13/search_styles.xml
deleted file mode 100644
index f493891e8..000000000
--- a/mobile/android/base/resources/values-v13/search_styles.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <!-- Base application theme. -->
- <style name="AppTheme" parent="@android:style/Theme.Holo.Light.NoActionBar">
- <item name="android:windowBackground">@color/toolbar_grey</item>
- <item name="android:colorBackground">@color/toolbar_grey</item>
-
- <!--This attribute is required so that we can create a facet button-->
- <!--pragmatically. The defStyle param used in the View constructor-->
- <!--must be an attr, see: https://code.google.com/p/android/issues/detail?id=12683-->
- <item name="facetButtonStyle">@style/FacetButtonStyle</item>
- </style>
-
- <style name="SettingsTheme" parent="@android:style/Theme.Holo.Light"/>
-
-</resources>
diff --git a/mobile/android/base/resources/values-v13/styles.xml b/mobile/android/base/resources/values-v13/styles.xml
deleted file mode 100644
index 307f57180..000000000
--- a/mobile/android/base/resources/values-v13/styles.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="GeckoDialogTitle">
- <!-- Override this to use a Holo theme on v13+ -->
- <item name="android:textAppearance">@android:style/TextAppearance.Holo.DialogWindowTitle</item>
- </style>
-
- <style name="TextAppearance.Widget.ActionBar.Title" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title"/>
-
-</resources>
diff --git a/mobile/android/base/resources/values-v14/themes.xml b/mobile/android/base/resources/values-v14/themes.xml
deleted file mode 100644
index a04cf54d8..000000000
--- a/mobile/android/base/resources/values-v14/themes.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <!--
- Activity based themes for API 14+. This theme completely replaces
- GeckoAppBase from res/values/themes.xml on API 14+ devices.
- -->
- <style name="GeckoAppBase" parent="Gecko">
- <item name="android:actionButtonStyle">@style/GeckoActionBar.Button</item>
- <item name="android:actionModeCopyDrawable">@drawable/ab_copy</item>
- <item name="android:actionModeCutDrawable">@drawable/ab_cut</item>
- <item name="android:actionModePasteDrawable">@drawable/ab_paste</item>
- <item name="android:actionModeSelectAllDrawable">@drawable/ab_select_all</item>
- <item name="android:actionModeStyle">@style/GeckoActionBar</item>
- <item name="android:listViewStyle">@style/Widget.ListView</item>
- <item name="android:spinnerDropDownItemStyle">@style/Widget.DropDownItem.Spinner</item>
- <item name="android:spinnerItemStyle">@style/Widget.TextView.SpinnerItem</item>
- <item name="menuItemSwitcherLayoutStyle">@style/Widget.MenuItemSwitcherLayout</item>
- <item name="menuItemDefaultStyle">@style/Widget.MenuItemDefault</item>
- <item name="menuItemSecondaryActionBarStyle">@style/Widget.MenuItemSecondaryActionBar</item>
- </style>
-
-</resources>
diff --git a/mobile/android/base/resources/values-v16/search_styles.xml b/mobile/android/base/resources/values-v16/search_styles.xml
deleted file mode 100644
index 3da95f06a..000000000
--- a/mobile/android/base/resources/values-v16/search_styles.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <style name="TextAppearance.EmptyView.Title" parent="@android:style/TextAppearance.Small">
- <item name="android:textColor">@color/text_and_tabs_tray_grey</item>
- <item name="android:textSize">20sp</item>
- <item name="android:fontFamily">sans-serif-light</item>
- </style>
-
- <style name="TextAppearance.EmptyView.Message" parent="@android:style/TextAppearance.Small">
- <item name="android:textColor">@color/placeholder_grey</item>
- <item name="android:textSize">16sp</item>
- <item name="android:lineSpacingExtra">4sp</item>
- </style>
-
-</resources>
diff --git a/mobile/android/base/resources/values-v16/styles.xml b/mobile/android/base/resources/values-v16/styles.xml
deleted file mode 100644
index 15243891e..000000000
--- a/mobile/android/base/resources/values-v16/styles.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="TextAppearance.EmptyMessage" parent="TextAppearance.Large">
- <item name="android:fontFamily">sans-serif-light</item>
- </style>
-
- <style name="TextAppearance.Widget.Home.ItemTitle" parent="TextAppearance.Medium">
- <item name="android:fontFamily">sans-serif-light</item>
- </style>
-
- <style name="TextAppearance.FirstrunTextLight">
- <item name="android:fontFamily">sans-serif-light</item>
- </style>
-
- <style name="TextAppearance.FirstrunTextRegular">
- <item name="android:fontFamily">sans-serif</item>
- </style>
-
- <style name="TextAppearance.UrlBar.Title" parent="TextAppearance.Small">
- <item name="android:textSize">15sp</item>
- <item name="android:fontFamily">sans-serif-light</item>
- </style>
-
-</resources>
diff --git a/mobile/android/base/resources/values-v19/dimens.xml b/mobile/android/base/resources/values-v19/dimens.xml
deleted file mode 100644
index e2c964d82..000000000
--- a/mobile/android/base/resources/values-v19/dimens.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
- <dimen name="toast_button_corner_radius">24dp</dimen>
-</resources>
diff --git a/mobile/android/base/resources/values-v19/styles.xml b/mobile/android/base/resources/values-v19/styles.xml
deleted file mode 100644
index 6114c8229..000000000
--- a/mobile/android/base/resources/values-v19/styles.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
- <style name="Toast" parent="ToastBase">
- <item name="android:layout_marginLeft">8dp</item>
- <item name="android:layout_marginRight">8dp</item>
- </style>
-
- <style name="ToastElementBase">
- <item name="android:background">@null</item>
- <item name="android:paddingLeft">24dp</item>
- <item name="android:paddingRight">24dp</item>
- <item name="android:paddingTop">11dp</item>
- <item name="android:paddingBottom">11dp</item>
- </style>
-
- <style name="ToastDivider" parent="ToastDividerBase">
- <item name="android:layout_marginTop">12dp</item>
- <item name="android:layout_marginBottom">12dp</item>
- </style>
-
- <style name="ToastMessage" parent="ToastMessageBase">
- <item name="android:textSize">16sp</item>
- <item name="android:fontFamily">sans-serif-condensed</item>
- <item name="android:shadowColor">#BB000000</item>
- <item name="android:shadowRadius">2.75</item>
- </style>
-
- <style name="ToastButton" parent="ToastButtonBase">
- <item name="android:textSize">12sp</item>
- <item name="android:textStyle">bold</item>
- <item name="android:fontFamily">sans-serif-condensed</item>
- <item name="android:shadowColor">#BB000000</item>
- <item name="android:shadowRadius">2.75</item>
- </style>
-
-</resources>
diff --git a/mobile/android/base/resources/values-v21/dimens.xml b/mobile/android/base/resources/values-v21/dimens.xml
deleted file mode 100644
index dbf33ea28..000000000
--- a/mobile/android/base/resources/values-v21/dimens.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
- <dimen name="context_menu_item_horizontal_padding">17dp</dimen>
-</resources>
diff --git a/mobile/android/base/resources/values-v21/integers.xml b/mobile/android/base/resources/values-v21/integers.xml
deleted file mode 100644
index 76fd5f483..000000000
--- a/mobile/android/base/resources/values-v21/integers.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <integer name="search_assist_launch_res">@drawable/search_launcher</integer>
-
-</resources>
diff --git a/mobile/android/base/resources/values-v21/styles.xml b/mobile/android/base/resources/values-v21/styles.xml
deleted file mode 100644
index 5449c2334..000000000
--- a/mobile/android/base/resources/values-v21/styles.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <style name="ActionBarTitleTextStyle" parent="@android:style/TextAppearance.Material.Widget.ActionBar.Title">
- <item name="android:textColor">#fff</item>
- </style>
-
-</resources>
diff --git a/mobile/android/base/resources/values-v21/themes.xml b/mobile/android/base/resources/values-v21/themes.xml
deleted file mode 100644
index 140f066c4..000000000
--- a/mobile/android/base/resources/values-v21/themes.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <!--
- Base application theme.
- -->
- <style name="GeckoBase" parent="Theme.AppCompat.Light.DarkActionBar">
- <item name="colorPrimary">@color/text_and_tabs_tray_grey</item>
- <item name="colorPrimaryDark">@color/text_and_tabs_tray_grey</item>
- <item name="windowActionBar">false</item>
- <item name="windowNoTitle">true</item>
- <item name="android:windowContentOverlay">@null</item>
- <item name="android:alertDialogTheme">@style/GeckoAlertDialog</item>
- </style>
-
- <style name="GeckoAlertDialog" parent="@android:style/Theme.Material.Light.Dialog.Alert">
- <item name="android:colorAccent">@color/fennec_ui_orange</item>
- </style>
-
- <style name="GeckoAppBase" parent="Gecko">
- <item name="android:actionButtonStyle">@style/GeckoActionBar.Button</item>
- <item name="android:listViewStyle">@style/Widget.ListView</item>
- <item name="android:spinnerDropDownItemStyle">@style/Widget.DropDownItem.Spinner</item>
- <item name="android:spinnerItemStyle">@style/Widget.TextView.SpinnerItem</item>
- <item name="menuItemSwitcherLayoutStyle">@style/Widget.MenuItemSwitcherLayout</item>
- <item name="menuItemDefaultStyle">@style/Widget.MenuItemDefault</item>
- <item name="menuItemSecondaryActionBarStyle">@style/Widget.MenuItemSecondaryActionBar</item>
- <item name="tabGridLayoutViewStyle">@style/Widget.TabsGridLayout</item>
- </style>
-
-</resources>
diff --git a/mobile/android/base/resources/values-w400dp/styles.xml b/mobile/android/base/resources/values-w400dp/styles.xml
deleted file mode 100644
index 5308af9ca..000000000
--- a/mobile/android/base/resources/values-w400dp/styles.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <style name="Toast" parent="ToastBase">
- <item name="android:layout_width">400dp</item>
- </style>
-
-</resources>
diff --git a/mobile/android/base/resources/values-xlarge-land-v11/dimens.xml b/mobile/android/base/resources/values-xlarge-land-v11/dimens.xml
deleted file mode 100644
index ca605a31b..000000000
--- a/mobile/android/base/resources/values-xlarge-land-v11/dimens.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <dimen name="tab_panel_grid_padding">64dp</dimen>
-
-</resources>
diff --git a/mobile/android/base/resources/values-xlarge-land-v11/styles.xml b/mobile/android/base/resources/values-xlarge-land-v11/styles.xml
deleted file mode 100644
index 1b8666f29..000000000
--- a/mobile/android/base/resources/values-xlarge-land-v11/styles.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
- <style name="Widget.TopSitesListView" parent="Widget.BookmarksListView">
- <item name="android:paddingTop">30dp</item>
- <item name="android:paddingLeft">120dp</item>
- <item name="android:paddingRight">120dp</item>
- <item name="android:clipToPadding">false</item>
- <item name="topDivider">false</item>
- </style>
-
- <style name="Widget.TopSitesGridView" parent="Widget.GridView">
- <item name="android:paddingLeft">55dp</item>
- <item name="android:paddingRight">55dp</item>
- <item name="android:paddingBottom">30dp</item>
- <item name="android:horizontalSpacing">56dp</item>
- <item name="android:verticalSpacing">20dp</item>
- </style>
-
-</resources>
diff --git a/mobile/android/base/resources/values-xlarge-v11/dimens.xml b/mobile/android/base/resources/values-xlarge-v11/dimens.xml
deleted file mode 100644
index a666854f1..000000000
--- a/mobile/android/base/resources/values-xlarge-v11/dimens.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <dimen name="panel_grid_view_column_width">250dp</dimen>
- <dimen name="tab_panel_grid_padding">48dp</dimen>
-
-</resources>
diff --git a/mobile/android/base/resources/values-xlarge-v11/integers.xml b/mobile/android/base/resources/values-xlarge-v11/integers.xml
deleted file mode 100644
index 55e1babea..000000000
--- a/mobile/android/base/resources/values-xlarge-v11/integers.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <integer name="number_of_top_sites">9</integer>
- <integer name="number_of_top_sites_cols">3</integer>
- <integer name="panel_icon_grid_view_columns">6</integer>
- <integer name="number_of_inline_share_devices">4</integer>
-
-</resources>
diff --git a/mobile/android/base/resources/values-xlarge-v11/styles.xml b/mobile/android/base/resources/values-xlarge-v11/styles.xml
deleted file mode 100644
index d0bcbae31..000000000
--- a/mobile/android/base/resources/values-xlarge-v11/styles.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <!--
- Only overriden styles for Honeycomb/Ice cream sandwich XLARGE tablets are specified here.
- Please refer to values/styles.xml for default styles.
- -->
-
- <!-- TabWidget -->
- <style name="TabWidget">
- <item name="android:layout_width">300dip</item>
- <item name="android:layout_height">48dip</item>
- </style>
-
- <style name="Widget.TopSitesListView" parent="Widget.BookmarksListView">
- <item name="android:paddingTop">30dp</item>
- <item name="android:paddingLeft">32dp</item>
- <item name="android:paddingRight">32dp</item>
- <item name="android:clipToPadding">false</item>
- <item name="topDivider">false</item>
- </style>
-
-</resources>
diff --git a/mobile/android/base/resources/values/arrays.xml b/mobile/android/base/resources/values/arrays.xml
deleted file mode 100644
index d220ca9bb..000000000
--- a/mobile/android/base/resources/values/arrays.xml
+++ /dev/null
@@ -1,176 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-
-<resources>
- <string-array name="pref_home_updates_entries">
- <item>@string/pref_home_updates_enabled</item>
- <item>@string/pref_home_updates_wifi</item>
- </string-array>
- <string-array name="pref_home_updates_values">
- <item>0</item>
- <item>1</item>
- </string-array>
- <string-array name="pref_plugins_entries">
- <item>@string/pref_plugins_enabled</item>
- <item>@string/pref_plugins_tap_to_play</item>
- <item>@string/pref_plugins_disabled</item>
- </string-array>
- <string-array name="pref_plugins_values">
- <item>1</item>
- <item>2</item>
- <item>0</item>
- </string-array>
- <string-array name="pref_font_size_entries">
- <item>@string/pref_font_size_tiny</item>
- <item>@string/pref_font_size_small</item>
- <item>@string/pref_font_size_medium</item>
- <item>@string/pref_font_size_large</item>
- <item>@string/pref_font_size_xlarge</item>
- </string-array>
- <string-array name="pref_font_size_values">
- <item>0</item>
- <item>80</item>
- <item>120</item>
- <item>160</item>
- <item>240</item>
- </string-array>
- <string-array name="pref_char_encoding_entries">
- <item>@string/pref_char_encoding_on</item>
- <item>@string/pref_char_encoding_off</item>
- </string-array>
- <string-array name="pref_char_encoding_values">
- <item>true</item>
- <item>false</item>
- </string-array>
- <string-array name="pref_cookies_entries">
- <item>@string/pref_cookies_accept_all</item>
- <item>@string/pref_cookies_not_accept_foreign</item>
- <item>@string/pref_cookies_disabled</item>
- </string-array>
- <string-array name="pref_tracking_protection_values">
- <item>2</item>
- <item>1</item>
- <item>0</item>
- </string-array>
- <string-array name="pref_tracking_protection_entries">
- <item>@string/pref_tracking_protection_enabled</item>
- <item>@string/pref_tracking_protection_enabled_pb</item>
- <item>@string/pref_tracking_protection_disabled</item>
- </string-array>
- <string-array name="pref_cookies_values">
- <item>0</item>
- <item>1</item>
- <item>2</item>
- </string-array>
- <string-array name="pref_import_android_entries">
- <item>@string/bookmarks_title</item>
- <item>@string/history_title</item>
- </string-array>
- <string-array name="pref_import_android_defaults">
- <item>true</item>
- <item>true</item>
- </string-array>
- <string-array name="pref_import_android_values">
- <item>android_import.data.bookmarks</item>
- <item>android_import.data.history</item>
- </string-array>
- <string-array name="pref_private_data_entries">
- <item>@string/pref_private_data_history2</item>
- <item>@string/pref_private_data_searchHistory</item>
- <item>@string/pref_private_data_downloadFiles2</item>
- <item>@string/pref_private_data_formdata2</item>
- <item>@string/pref_private_data_cookies2</item>
- <item>@string/pref_private_data_cache</item>
- <item>@string/pref_private_data_offlineApps</item>
- <item>@string/pref_private_data_siteSettings</item>
- <item>@string/pref_private_data_syncedTabs</item>
- <item>@string/pref_private_data_passwords</item>
- </string-array>
- <string-array name="pref_private_data_defaults">
- <item>true</item>
- <item>true</item>
- <item>true</item>
- <item>true</item>
- <item>true</item>
- <item>true</item>
- <item>true</item>
- <item>true</item>
- <item>true</item>
- <item>false</item>
- </string-array>
- <string-array name="pref_private_data_values">
- <item>private.data.history</item>
- <item>private.data.searchHistory</item>
- <item>private.data.downloadFiles</item>
- <item>private.data.formdata</item>
- <item>private.data.cookies_sessions</item>
- <item>private.data.cache</item>
- <item>private.data.offlineApps</item>
- <item>private.data.siteSettings</item>
- <item>private.data.syncedTabs</item>
- <item>private.data.passwords</item>
- </string-array>
- <string-array name="pref_private_data_keys">
- <item>private.data.history</item>
- <item>private.data.searchHistory</item>
- <item>private.data.downloadFiles</item>
- <item>private.data.formdata</item>
- <item>private.data.cookies_sessions</item>
- <item>private.data.cache</item>
- <item>private.data.offlineApps</item>
- <item>private.data.siteSettings</item>
- <item>private.data.syncedTabs</item>
- <item>private.data.passwords</item>
- </string-array>
- <string-array name="pref_clear_on_exit_defaults">
- <item>false</item>
- <item>false</item>
- <item>false</item>
- <item>false</item>
- <item>false</item>
- <item>false</item>
- <item>false</item>
- <item>false</item>
- <item>false</item>
- <item>false</item>
- </string-array>
- <string-array name="pref_restore_entries">
- <item>@string/pref_restore_always</item>
- <item>@string/pref_restore_quit</item>
- </string-array>
- <string-array name="pref_restore_values">
- <item>always</item>
- <item>quit</item>
- </string-array>
- <string-array name="pref_update_autodownload_entries">
- <item>@string/pref_update_autodownload_enabled</item>
- <item>@string/pref_update_autodownload_wifi</item>
- <item>@string/pref_update_autodownload_disabled</item>
- </string-array>
- <string-array name="pref_update_autodownload_values">
- <item>enabled</item>
- <item>wifi</item>
- <item>disabled</item>
- </string-array>
- <!-- This value is similar to config_longPressVibePattern in android frameworks/base/core/res/res/values/config.xml-->
- <integer-array name="long_press_vibrate_msec">
- <item>0</item>
- <item>1</item>
- <item>20</item>
- <item>21</item>
- </integer-array>
- <!-- browser.image_blocking -->
- <string-array name="pref_browser_image_blocking_entries">
- <item>@string/pref_tap_to_load_images_enabled</item>
- <item>@string/pref_tap_to_load_images_data</item>
- <item>@string/pref_tap_to_load_images_disabled2</item>
- </string-array>
- <string-array name="pref_browser_image_blocking_values">
- <item>1</item> <!-- Always -->
- <item>2</item> <!-- Wifi-only -->
- <item>0</item> <!-- Blocked -->
- </string-array>
-</resources>
diff --git a/mobile/android/base/resources/values/attrs.xml b/mobile/android/base/resources/values/attrs.xml
deleted file mode 100644
index 0a75f9884..000000000
--- a/mobile/android/base/resources/values/attrs.xml
+++ /dev/null
@@ -1,188 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- Theme level attributes -->
- <declare-styleable name="GeckoTheme">
-
- <!-- Style for GeckoMenu ListView -->
- <attr name="geckoMenuListViewStyle" format="reference"/>
-
- <!-- Style for MenuItemActionBar -->
- <attr name="menuItemActionBarStyle" format="reference"/>
-
- <!-- Style for MenuItemActionBar -->
- <attr name="menuItemActionModeStyle" format="reference"/>
-
- <!-- Style for MenuItemSwitcherLayout -->
- <attr name="menuItemSwitcherLayoutStyle" format="reference"/>
-
- <!-- Style for MenuItemDefault -->
- <attr name="menuItemDefaultStyle" format="reference"/>
-
- <!-- Style for MenuItemActionBar when shown in SecondaryActionBar -->
- <attr name="menuItemSecondaryActionBarStyle" format="reference"/>
-
- <!-- Default style for the BookmarksListView -->
- <attr name="bookmarksListViewStyle" format="reference" />
-
- <!-- Default style for the TopSitesGridItemView -->
- <attr name="topSitesGridItemViewStyle" format="reference" />
-
- <!-- Styles for dynamic panel grid views -->
- <attr name="panelIconViewStyle" format="reference" />
-
- <!-- Style for the TabsGridLayout -->
- <attr name="tabGridLayoutViewStyle" format="reference" />
-
- <!-- Default style for the TopSitesGridView -->
- <attr name="topSitesGridViewStyle" format="reference" />
-
- <!-- Default style for the TopSitesThumbnailView -->
- <attr name="topSitesThumbnailViewStyle" format="reference" />
-
- <!-- Default style for the HomeListView -->
- <attr name="homeListViewStyle" format="reference" />
-
- </declare-styleable>
-
- <declare-styleable name="MenuItem">
- <attr name="android:id"/>
- <attr name="android:orderInCategory"/>
- <attr name="android:title"/>
- <attr name="android:icon"/>
- <attr name="android:checkable"/>
- <attr name="android:checked"/>
- <attr name="android:visible"/>
- <attr name="android:enabled"/>
- <attr name="android:showAsAction"/>
- </declare-styleable>
-
- <declare-styleable name="MenuItemDefault">
- <attr name="state_more" format="boolean"/>
- </declare-styleable>
-
- <declare-styleable name="TabThumbnailWrapper">
- <attr name="state_recording" format="boolean"/>
- </declare-styleable>
-
- <declare-styleable name="FlowLayout">
- <attr name="spacing" format="dimension"/>
- </declare-styleable>
-
- <declare-styleable name="MultiChoicePreference">
- <attr name="entries" format="string"/>
- <attr name="entryValues" format="string"/>
- <attr name="initialValues" format="string"/>
- </declare-styleable>
-
- <declare-styleable name="MultiPrefMultiChoicePreference">
- <attr name="entryKeys" format="string"/>
- </declare-styleable>
-
- <declare-styleable name="TabsLayout">
- <attr name="tabs">
- <flag name="tabs_normal" value="0x00" />
- <flag name="tabs_private" value ="0x01" />
- </attr>
- </declare-styleable>
-
- <declare-styleable name="TabCounter">
- <attr name="android:layout"/>
- </declare-styleable>
-
- <declare-styleable name="PrivateBrowsing">
- <attr name="state_private" format="boolean"/>
- </declare-styleable>
-
- <declare-styleable name="LightweightTheme">
- <attr name="state_light" format="boolean"/>
- <attr name="state_dark" format="boolean"/>
- <attr name="autoUpdateTheme" format="boolean"/>
- </declare-styleable>
-
- <declare-styleable name="TwoWayView">
- <attr name="android:orientation"/>
- <attr name="android:choiceMode"/>
- <attr name="android:listSelector"/>
- <attr name="android:drawSelectorOnTop"/>
- </declare-styleable>
-
- <declare-styleable name="HomeListView">
- <!-- Draws a divider on top of the list, if true. Defaults to false. -->
- <attr name="topDivider" format="boolean"/>
- </declare-styleable>
-
- <declare-styleable name="FadedTextView">
- <attr name="fadeWidth" format="dimension"/>
- </declare-styleable>
-
- <declare-styleable name="FadedMultiColorTextView">
- <!-- The background color we should be fading over. Useful because the
- background is full alpha and we need to copy the background underneath. -->
- <attr name="fadeBackgroundColor" format="dimension"/>
- </declare-styleable>
-
- <declare-styleable name="IconTabWidget">
- <attr name="android:layout"/>
-
- <!-- Sets the tab's content type. Defaults to icon. -->
- <attr name="display">
- <enum name="icon" value="0x00" />
- <enum name="text" value="0x01" />
- </attr>
- </declare-styleable>
-
- <declare-styleable name="TopSitesGridView">
- <attr name="android:horizontalSpacing"/>
- <attr name="android:verticalSpacing"/>
- </declare-styleable>
-
- <declare-styleable name="TabMenuStrip">
- <attr name="strip" format="reference"/>
- <attr name="tabsMarginLeft" format="dimension" />
- <attr name="activeTextColor" format="color" />
- <attr name="inactiveTextColor" format="color" />
- <attr name="titlebarFill" format="boolean" />
- </declare-styleable>
-
- <declare-styleable name="CustomColorPreference">
- <attr name="titleColor" format="color" />
- <attr name="summaryColor" format="color" />
- </declare-styleable>
-
- <declare-styleable name="TabPanelBackButton">
- <attr name="rightDivider" format="reference"/>
- <attr name="dividerVerticalPadding" format="dimension"/>
- </declare-styleable>
-
- <declare-styleable name="EllipsisTextView">
- <attr name="ellipsizeAtLine" format="integer"/>
- </declare-styleable>
-
- <declare-styleable name="FaviconView">
- <attr name="dominantBorderEnabled" format="boolean" />
- <attr name="overrideScaleType" format="boolean" />
- <attr name="enableRoundCorners" format="boolean"/>
- </declare-styleable>
-
- <declare-styleable name="OverlayDialogButton">
- <attr name="drawable" format="reference" />
- <attr name="enabledText" format="string" />
- <attr name="disabledText" format="string" />
- </declare-styleable>
-
- <declare-styleable name="ThemedView">
- <!-- A reimplementation of android:tintList which is
- otherwise only available on API 21+.
-
- Using this attribute is mutually exclusive with android:tint
- and setting colorFilters in code. This is because on pre-Lollipop,
- android:tint and DrawableCompat.tint* uses colorFilters under the hood. -->
- <attr name="drawableTintList" format="color" />
- </declare-styleable>
-
-</resources>
-
diff --git a/mobile/android/base/resources/values/bool.xml b/mobile/android/base/resources/values/bool.xml
deleted file mode 100644
index 6ef7090c6..000000000
--- a/mobile/android/base/resources/values/bool.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ This Source Code Form is subject to the terms of the Mozilla Public
- ~ License, v. 2.0. If a copy of the MPL was not distributed with this
- ~ file, you can obtain one at http://mozilla.org/MPL/2.0/.
- -->
-
-<resources>
- <!-- Some devices use resources based on configuration (e.g. large, xlarge) that are inconsistent
- with the configuration retrieved by HardwareUtils (e.g. some custom ROMs allow the user to
- choose a phone or tablet version of the UI even though the hardware stays the same). This
- can cause crashes when we branch on the value returned by HardwareUtils.
-
- In order to work around this, we define the resource size in resources with the expectation that
- we branch on that value, rather than HardwareUtils, so our code is consistent with the used resources.
- See bug 1277379 for a initiative to move all of the HardwareUtils code over. -->
- <bool name="is_large_resource">false</bool>
-</resources>
diff --git a/mobile/android/base/resources/values/colors.xml b/mobile/android/base/resources/values/colors.xml
deleted file mode 100644
index 318ad5026..000000000
--- a/mobile/android/base/resources/values/colors.xml
+++ /dev/null
@@ -1,148 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
- <!-- Fennec color palette (bug 1127517) -->
- <color name="fennec_ui_orange">#FF9500</color>
- <color name="affirmative_green">#6FBE4A</color>
- <color name="rejection_red">#D23228</color>
- <color name="action_orange">#E66000</color>
- <color name="action_orange_pressed">#DC5600</color>
- <color name="link_blue">#0096DD</color>
- <color name="link_blue_pressed">#0082C6</color>
- <color name="private_browsing_purple">#CF68FF</color>
-
- <color name="placeholder_active_grey">#222222</color>
- <color name="placeholder_grey">#777777</color>
- <color name="private_toolbar_grey">#292C29</color>
- <color name="text_and_tabs_tray_grey">#363B40</color>
- <color name="tabs_tray_grey_pressed">#45494E</color>
- <color name="toolbar_icon_grey">#5F6368</color>
-
- <color name="tabs_tray_icon_grey">#AFB1B3</color>
- <color name="disabled_grey">#BFBFBF</color>
- <color name="toolbar_grey_pressed">#D7D7DC</color>
- <color name="toolbar_menu_dark_grey">#E1E1E6</color>
- <color name="toolbar_grey">#EBEBF0</color>
- <color name="about_page_header_grey">#F5F5F5</color>
-
- <color name="url_bar_shadow_private">#7878A5</color>
-
- <!-- Restricted profiles palette -->
-
- <color name="restricted_profile_background_gold">#ffffcb51</color>
- <color name="restricted_profile_background_green">#1aaa86</color>
-
- <!-- Non-palette colors -->
-
- <!-- Synced w/ toolbar_grey -->
- <color name="background_normal_lwt">#DDEBEBF0</color>
-
- <color name="highlight">#33000000</color>
- <color name="highlight_focused">#1A000000</color>
- <color name="highlight_dark">#33FFFFFF</color>
- <color name="highlight_dark_focused">#1AFFFFFF</color>
-
- <!-- Synced w/ toolbar_grey_pressed -->
- <color name="tablet_highlight_lwt">#AAD7D7DC</color>
-
- <!-- Synced w/ tabs_tray_grey_pressed -->
- <color name="tablet_highlight_dark_lwt">#AA45494E</color>
-
- <!-- (bug 1077195) Focused state values are temporary. -->
- <color name="tablet_highlight_focused">#C0C9D0</color>
-
- <!-- highlight on shaped button: 20% white over text_and_tabs_tray_grey -->
- <color name="highlight_shaped">#FF696D71</color>
-
- <!-- highlight-focused on shaped button: 10% white over text_and_tabs_tray_grey -->
- <color name="highlight_shaped_focused">#FF565B60</color>
-
- <!-- highlight on private nav button: 20% white over private_toolbar_grey -->
- <color name="highlight_nav_pb">#FF545654</color>
-
- <color name="dark_transparent_overlay">#99000000</color>
-
- <!-- Firstrun tour -->
- <color name="firstrun_pager_header">#E8E8E8</color>
-
- <!-- Tab Queue -->
- <color name="tab_queue_dismiss_button_foreground">#16A3DF</color>
- <color name="tab_queue_dismiss_button_foreground_pressed">#1193CB</color>
-
- <!--
- Application theme colors
- -->
- <!-- Default colors -->
- <color name="text_color_tertiary">#9198A1</color>
-
- <!-- Default inverse colors -->
- <color name="text_color_secondary_inverse">#DDDDDD</color>
-
- <!-- Disabled colors -->
- <color name="text_color_primary_disable_only">#999999</color>
-
- <!-- Hint colors -->
- <color name="text_color_hint">#666666</color>
- <color name="text_color_hint_inverse">#7F828A</color>
-
- <!-- Highlight colors -->
- <color name="text_color_highlight_inverse">#D06BFF</color>
-
- <!-- Link colors -->
- <color name="text_color_link">#22629E</color>
-
- <!-- Divider colors -->
- <color name="toolbar_divider_grey">#D7D9DB</color>
-
- <color name="doorhanger_link">#FF2AA1FE</color>
-
- <color name="validation_message_text">#ffffff</color>
- <color name="url_bar_text_highlight_pb">#FFD06BFF</color>
- <color name="tab_row_pressed">#4D000000</color>
-
- <color name="url_bar_urltext">#AFB1B3</color>
- <color name="url_bar_urltext_private">#777777</color>
- <color name="url_bar_domaintext">#363B40</color>
- <color name="url_bar_domaintext_private">#FFFFFF</color>
- <color name="url_bar_blockedtext">#b14646</color>
- <color name="url_bar_shadow">#12000000</color>
-
- <color name="panel_image_item_background">#D1D9E1</color>
- <color name="panel_icon_item_title_background">#32000000</color>
- <color name="panel_tab_text_normal">#FFBFBFBF</color>
-
- <!-- Remote tabs setup -->
- <color name="remote_tabs_setup_button_background_hit">#D95300</color>
-
- <!-- Button toast colors. -->
- <color name="toast_background">#DD222222</color>
- <color name="toast_button_background">#00000000</color>
- <color name="toast_button_pressed">#DD2C3136</color>
- <color name="toast_button_text">#FFFFFFFF</color>
-
- <!-- Tab History colors. -->
- <color name="tab_history_timeline_separator">#D7D9DB</color>
- <color name="tab_history_favicon_border">#D7D9DB</color>
- <color name="tab_history_favicon_background">#FFFFFF</color>
- <color name="tab_history_border_color">#DADADF</color>
-
- <!-- Canvas delegate paint color -->
- <color name="canvas_delegate_paint">#FFFF0000</color>
-
- <!-- Top sites thumbnail colors -->
- <color name="top_site_default">#FFECF0F3</color>
- <color name="top_site_border">#FFCFD9E1</color>
-
- <color name="private_active_text">#FFFFFF</color>
-
- <color name="action_bar_bg_color">@color/toolbar_grey</color>
-
- <color name="activity_stream_divider">#FFD2D2D2</color>
- <color name="activity_stream_subtitle">#FF919191</color>
- <color name="activity_stream_timestamp">#FFD3D3D3</color>
- <color name="activity_stream_icon">#FF919191</color>
-
-</resources>
diff --git a/mobile/android/base/resources/values/dimens.xml b/mobile/android/base/resources/values/dimens.xml
deleted file mode 100644
index b730f9671..000000000
--- a/mobile/android/base/resources/values/dimens.xml
+++ /dev/null
@@ -1,227 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <dimen name="standard_corner_radius">4dp</dimen>
-
- <dimen name="autocomplete_min_width">200dp</dimen>
- <dimen name="autocomplete_row_height">32dp</dimen>
-
- <dimen name="browser_toolbar_height">48dp</dimen>
- <!-- This value is the height of the Tabs Panel header view
- (browser_toolbar_height) minus the height of the indicator
- (6dp). This value should change when the height of the view changes. -->
- <dimen name="tabs_panel_indicator_selected_padding_top">42dp</dimen>
-
- <!-- We use two different values for browser_toolbar_height on tablet
- which is inconsistent. Temporary value until bug 1150730 is fixed. -->
- <dimen name="browser_toolbar_height_flipper">48dp</dimen>
- <dimen name="browser_toolbar_button_padding">12dp</dimen>
- <dimen name="browser_toolbar_icon_width">48dp</dimen>
- <dimen name="browser_toolbar_menu_icon_height">16dp</dimen>
-
- <!-- favicon_size includes 4dp of right padding. We can't use margin (which would allow us to
- specify the actual size) because that would decrease the size of our hit target. -->
- <dimen name="browser_toolbar_favicon_size">21.33dip</dimen>
- <dimen name="browser_toolbar_shadow_size">2dp</dimen>
-
- <!-- If you update one of these values, update the others. -->
- <dimen name="tablet_nav_button_width">42dp</dimen>
- <dimen name="tablet_nav_button_width_half">21dp</dimen>
- <dimen name="tablet_nav_button_width_plus_half">63dp</dimen>
-
- <!-- This is the system default for the vertical padding for the divider of the TabWidget.
- Used to mimic the divider padding on the tablet tabs panel back button. -->
- <dimen name="tab_panel_divider_vertical_padding">12dp</dimen>
-
- <dimen name="tablet_tab_strip_height">48dp</dimen>
- <dimen name="tablet_tab_strip_item_width">208dp</dimen>
- <dimen name="tablet_tab_strip_item_margin">-28dp</dimen>
- <dimen name="tablet_tab_strip_fading_edge_size">15dp</dimen>
- <dimen name="tablet_browser_toolbar_menu_item_width">56dp</dimen>
- <!-- Padding combines with an 18dp image to form the menu item width and height. -->
- <dimen name="tablet_browser_toolbar_menu_item_padding_horizontal">19dp</dimen>
- <dimen name="tablet_browser_toolbar_menu_item_inset_vertical">5dp</dimen>
- <dimen name="tablet_browser_toolbar_menu_item_inset_horizontal">3dp</dimen>
- <dimen name="tablet_tab_strip_button_inset">5dp</dimen>
-
- <!-- Dimensions used by Favicons and FaviconView -->
- <dimen name="favicon_bg">32dp</dimen>
- <dimen name="favicon_corner_radius">4dp</dimen>
- <!-- Set the upper limit on the size of favicon that will be processed. Favicons larger than
- this will be downscaled to this value. If you need to use larger Favicons (Due to a UI
- redesign sometime after this is written) you should increase this value to the largest
- commonly-used size of favicon and, performance permitting, fetch the remainder from the
- database. The largest available size is always stored in the database, regardless of this
- value.-->
- <dimen name="favicon_largest_interesting_size">32dp</dimen>
-
- <dimen name="firstrun_content_width">300dp</dimen>
- <dimen name="firstrun_min_height">120dp</dimen>
- <dimen name="firstrun_background_height">120dp</dimen>
-
- <dimen name="overlay_prompt_content_width">260dp</dimen>
- <dimen name="overlay_prompt_button_width">148dp</dimen>
- <dimen name="overlay_prompt_container_width">@dimen/match_parent</dimen>
-
- <!-- Site security icon -->
- <dimen name="browser_toolbar_site_security_height">32dp</dimen>
- <dimen name="browser_toolbar_site_security_width">32dp</dimen>
- <dimen name="browser_toolbar_site_security_margin_right">0dp</dimen>
- <dimen name="browser_toolbar_site_security_padding_vertical">7dp</dimen>
- <dimen name="browser_toolbar_site_security_padding_horizontal">7dp</dimen>
-
- <!-- If one of these values changes, they all should. -->
- <dimen name="browser_toolbar_site_security_margin_bottom">.5dp</dimen>
- <dimen name="site_security_unknown_inset_top">1dp</dimen>
- <dimen name="site_security_unknown_inset_bottom">-1dp</dimen>
-
- <dimen name="home_folder_title_oneline_textsize">16sp</dimen>
- <dimen name="home_folder_title_twoline_textsize">14sp</dimen>
- <dimen name="home_twolinepagerow_title_textsize">16sp</dimen>
-
- <dimen name="page_row_edge_padding">16dp</dimen>
-
- <!-- Regular page row on about:home -->
- <dimen name="page_row_height">64dp</dimen>
-
- <!-- Group/heading page row on about:home -->
- <dimen name="page_group_height">56dp</dimen>
- <dimen name="home_header_item_height">56dp</dimen>
- <dimen name="page_row_divider_height">1dp</dimen>
-
- <!-- Remote Tabs static view top padding. Less in landscape on phones. -->
- <dimen name="home_remote_tabs_top_padding">48dp</dimen>
-
- <!-- Remote Tabs Hidden devices row height -->
- <dimen name="home_remote_tabs_hidden_footer_height">40dp</dimen>
-
- <!-- Search Engine Row height -->
- <dimen name="search_row_height">48dp</dimen>
-
- <dimen name="doorhanger_width">300dp</dimen>
- <dimen name="doorhanger_input_width">250dp</dimen>
- <dimen name="doorhanger_offsetX">12dp</dimen>
- <dimen name="doorhanger_offsetY">67dp</dimen>
- <dimen name="doorhanger_drawable_padding">5dp</dimen>
- <dimen name="doorhanger_subsection_padding">8dp</dimen>
- <dimen name="doorhanger_section_padding_small">10dp</dimen>
- <dimen name="doorhanger_section_padding_medium">20dp</dimen>
- <dimen name="doorhanger_section_padding_large">30dp</dimen>
- <dimen name="doorhanger_icon_size">60dp</dimen>
- <dimen name="doorhanger_rounded_corner_radius">4dp</dimen>
-
- <dimen name="context_menu_item_horizontal_padding">10dp</dimen>
-
- <dimen name="flow_layout_spacing">6dp</dimen>
- <dimen name="menu_item_icon">21dp</dimen>
- <dimen name="menu_item_textsize">16sp</dimen>
- <dimen name="menu_item_state_icon">18dp</dimen>
- <!-- This is chosen to match Android's listPreferredItemHeight.
- TODO: We should inherit these from the system.
- http://androidxref.com/4.2.2_r1/xref/frameworks/base/core/res/res/values/themes.xml#123 -->
- <dimen name="menu_item_row_height">64dip</dimen>
- <dimen name="menu_item_row_width">240dp</dimen>
- <dimen name="menu_popup_width">256dp</dimen>
- <dimen name="nav_button_border_width">1dp</dimen>
- <dimen name="prompt_service_group_padding_size">32dp</dimen>
- <dimen name="prompt_service_icon_size">36dp</dimen>
- <dimen name="prompt_service_icon_text_padding">10dp</dimen>
- <dimen name="prompt_service_inputs_padding">16dp</dimen>
- <dimen name="prompt_service_left_right_text_with_icon_padding">10dp</dimen>
- <dimen name="prompt_service_top_bottom_text_with_icon_padding">8dp</dimen>
- <dimen name="tabs_panel_indicator_width">60dp</dimen>
- <dimen name="tabs_panel_button_width">48dp</dimen>
- <dimen name="tabs_strip_height">48dp</dimen>
- <dimen name="tabs_strip_button_width">100dp</dimen>
- <dimen name="tabs_strip_button_padding">18dp</dimen>
- <dimen name="tabs_strip_shadow_size">1dp</dimen>
- <dimen name="validation_message_height">50dp</dimen>
- <dimen name="validation_message_margin_top">6dp</dimen>
-
- <dimen name="tab_thumbnail_width">121dp</dimen>
- <dimen name="tab_thumbnail_height">90dp</dimen>
- <dimen name="tab_panel_column_width">129dp</dimen>
- <dimen name="tab_panel_grid_padding">20dp</dimen>
- <dimen name="tab_panel_grid_vspacing">20dp</dimen>
- <dimen name="tab_panel_grid_padding_top">19dp</dimen>
-
- <dimen name="tab_highlight_stroke_width">4dp</dimen>
-
- <!-- PageActionButtons dimensions -->
- <dimen name="page_action_button_width">32dp</dimen>
-
- <!-- Banner -->
- <dimen name="home_banner_height">72dp</dimen>
- <dimen name="home_banner_close_width">42dp</dimen>
- <dimen name="home_banner_icon_height">48dip</dimen>
- <dimen name="home_banner_icon_width">48dip</dimen>
-
- <!-- Icon Grid -->
- <dimen name="icongrid_columnwidth">128dp</dimen>
- <dimen name="icongrid_padding">16dp</dimen>
-
- <!-- PanelRecyclerView dimensions -->
- <dimen name="panel_grid_view_column_width">150dp</dimen>
- <dimen name="panel_grid_view_horizontal_spacing">3dp</dimen>
- <dimen name="panel_grid_view_vertical_spacing">3dp</dimen>
- <dimen name="panel_grid_view_outer_spacing">3dp</dimen>
-
- <!-- PanelItemView dimensions -->
- <dimen name="panel_article_item_height">95dp</dimen>
-
- <!-- Button toast dimenstions. -->
- <dimen name="toast_button_corner_radius">2dp</dimen>
-
- <!-- TabHistoryItemRow dimensions. -->
- <dimen name="tab_history_timeline_width">3dp</dimen>
- <dimen name="tab_history_timeline_height">14dp</dimen>
- <dimen name="tab_history_favicon_bg">32dp</dimen>
- <dimen name="tab_history_favicon_padding">5dp</dimen>
- <dimen name="tab_history_favicon_border_enabled">3dp</dimen>
- <dimen name="tab_history_favicon_border_disabled">1dp</dimen>
- <dimen name="tab_history_combo_margin_left">15dp</dimen>
- <dimen name="tab_history_combo_margin_right">15dp</dimen>
- <dimen name="tab_history_title_fading_width">50dp</dimen>
- <dimen name="tab_history_title_margin_right">15dp</dimen>
- <dimen name="tab_history_title_text_size">14sp</dimen>
- <dimen name="tab_history_bg_width">2dp</dimen>
- <dimen name="tab_history_border_padding">2dp</dimen>
-
- <!-- ZoomedView dimensions. -->
- <dimen name="zoomed_view_toolbar_height">44dp</dimen>
- <dimen name="drawable_dropshadow_size">3dp</dimen>
-
- <!-- Find-In-Page dialog dimensions. -->
- <dimen name="find_in_page_text_margin_left">5dip</dimen>
- <dimen name="find_in_page_text_margin_right">12dip</dimen>
- <dimen name="find_in_page_text_padding_left">10dip</dimen>
- <dimen name="find_in_page_text_padding_right">10dip</dimen>
- <dimen name="find_in_page_status_margin_right">10dip</dimen>
- <dimen name="find_in_page_control_margin_top">2dip</dimen>
- <dimen name="progress_bar_scroll_offset">1.5dp</dimen>
-
- <!-- Matches the built-in divider height. fwiw, in the framework
- I suspect this is a drawable rather than a dimen. -->
- <dimen name="action_bar_divider_height">2dp</dimen>
-
- <!-- http://blog.danlew.net/2015/01/06/handling-android-resources-with-non-standard-formats/ -->
- <item name="match_parent" type="dimen">-1</item>
- <item name="wrap_content" type="dimen">-2</item>
-
- <item name="tab_strip_content_start" type="dimen">12dp</item>
- <item name="firstrun_tab_strip_content_start" type="dimen">15dp</item>
-
- <item name="notification_media_cover" type="dimen">128dp</item>
-
- <item name="activity_stream_base_margin" type="dimen">10dp</item>
- <item name="activity_stream_desired_tile_width" type="dimen">90dp</item>
- <item name="activity_stream_desired_tile_height" type="dimen">70dp</item>
- <item name="activity_stream_top_sites_text_height" type="dimen">30dp</item>
-
- <!-- Default touch target size for buttons/imageviews that might be of small size -->
- <item name="touch_target_size" type="dimen">48dp</item>
-</resources>
diff --git a/mobile/android/base/resources/values/ids.xml b/mobile/android/base/resources/values/ids.xml
deleted file mode 100644
index 2fe1ca793..000000000
--- a/mobile/android/base/resources/values/ids.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <item type="id" name="tabQueueNotification"/>
- <item type="id" name="tabQueueSettingsNotification" />
- <item type="id" name="guestNotification"/>
- <item type="id" name="original_height"/>
- <item type="id" name="menu_items"/>
- <item type="id" name="menu_margin"/>
- <item type="id" name="recycler_view_click_support" />
- <item type="id" name="range_list"/>
- <item type="id" name="pref_header_general"/>
- <item type="id" name="pref_header_privacy"/>
- <item type="id" name="pref_header_search"/>
- <item type="id" name="updateServicePermissionNotification" />
- <item type="id" name="websiteContentNotification" />
- <item type="id" name="foregroundNotification" />
-
-</resources>
diff --git a/mobile/android/base/resources/values/integers.xml b/mobile/android/base/resources/values/integers.xml
deleted file mode 100644
index b3451e05d..000000000
--- a/mobile/android/base/resources/values/integers.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <integer name="number_of_top_sites">6</integer>
- <integer name="number_of_top_sites_cols">2</integer>
- <integer name="max_icon_grid_columns">4</integer>
- <integer name="panel_icon_grid_view_columns">3</integer>
- <integer name="number_of_inline_share_devices">2</integer>
- <integer name="max_search_suggestions">2</integer>
- <integer name="max_saved_suggestions">2</integer>
- <integer name="search_assist_launch_res">0</integer>
-
-</resources>
diff --git a/mobile/android/base/resources/values/search_attrs.xml b/mobile/android/base/resources/values/search_attrs.xml
deleted file mode 100644
index d1b0ded59..000000000
--- a/mobile/android/base/resources/values/search_attrs.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
- <declare-styleable name="FacetButton"/>
-
- <!--This attribute is required so that we can create a facet button-->
- <!--pragmatically. The defStyle param used in the View constructor-->
- <!--must be an attr, see: https://code.google.com/p/android/issues/detail?id=12683-->
- <attr name="facetButtonStyle" />
-</resources>
diff --git a/mobile/android/base/resources/values/search_colors.xml b/mobile/android/base/resources/values/search_colors.xml
deleted file mode 100644
index 8718d1b51..000000000
--- a/mobile/android/base/resources/values/search_colors.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <!-- card colors -->
- <color name="row_background">#ffffff</color>
- <color name="row_background_pressed">#DCDCE1</color>
-
- <color name="widget_button_pressed">#33000000</color>
-
- <!--Facet button colors-->
- <color name="facet_button_background_color_default">@android:color/white</color>
- <color name="facet_button_background_color_pressed">#FAFAFA</color>
-
- <color name="facet_button_text_color_default">#ADB0B1</color>
- <color name="facet_button_text_color_selected">#383E42</color>
-
- <color name="network_error_link">#0092DB</color>
-
- <!-- Suggestion highlight color -->
- <color name="suggestion_highlight">#FF999999</color>
-</resources>
diff --git a/mobile/android/base/resources/values/search_dimens.xml b/mobile/android/base/resources/values/search_dimens.xml
deleted file mode 100644
index d35641a16..000000000
--- a/mobile/android/base/resources/values/search_dimens.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
- <!-- The height of the search bar is also used to offset the PreSearchFragment
- and PostSearchFragment contents -->
- <dimen name="search_bar_height">65dp</dimen>
- <dimen name="progress_bar_height">3dp</dimen>
-
- <!-- Size of the text for query input and suggestions -->
- <dimen name="query_text_size">16sp</dimen>
-
- <dimen name="search_row_padding">15dp</dimen>
- <dimen name="search_bar_padding_y">10dp</dimen>
-
- <!-- Padding to account for search engine icon/clear button -->
- <dimen name="search_bar_padding_right">25dp</dimen>
-
- <dimen name="search_history_drawable_padding">10dp</dimen>
-
- <!-- Widget Buttons -->
- <dimen name="widget_header_height">70dp</dimen>
- <dimen name="widget_text_size">14sp</dimen>
- <dimen name="widget_padding">7dp</dimen>
- <dimen name="widget_drawable_corner_radius">4dp</dimen>
- <dimen name="widget_bg_border_offset">3dp</dimen>
-
- <dimen name="facet_button_underline_thickness">5dp</dimen>
-</resources>
diff --git a/mobile/android/base/resources/values/search_styles.xml b/mobile/android/base/resources/values/search_styles.xml
deleted file mode 100644
index bc5adcd2e..000000000
--- a/mobile/android/base/resources/values/search_styles.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <!-- Base application theme. -->
- <style name="AppTheme" parent="@android:style/Theme.Light.NoTitleBar">
- <item name="android:windowBackground">@color/toolbar_grey</item>
- <item name="android:colorBackground">@color/toolbar_grey</item>
-
- <!--This attribute is required so that we can create a facet button-->
- <!--pragmatically. The defStyle param used in the View constructor-->
- <!--must be an attr, see: https://code.google.com/p/android/issues/detail?id=12683-->
- <item name="facetButtonStyle">@style/FacetButtonStyle</item>
- </style>
-
- <style name="SettingsTheme" parent="@android:style/Theme.Light" />
-
- <style name="FacetButtonStyle">
- <!--Since we're not inflating xml, we have to apply the layout params -->
- <!--after instantiation. See FacetBar.addFacet.-->
- <item name="android:textSize">15sp</item>
- <item name="android:textColor">@color/facet_button_text_color</item>
- <item name="android:background">@drawable/facet_button_background</item>
- <item name="android:gravity">center</item>
- <item name="android:clickable">true</item>
- </style>
-
- <style name="TextAppearance.EmptyView.Title" parent="@android:style/TextAppearance.Small">
- <item name="android:textColor">@color/text_and_tabs_tray_grey</item>
- <item name="android:textSize">20sp</item>
- </style>
-
- <style name="TextAppearance.EmptyView.Message" parent="@android:style/TextAppearance.Small">
- <item name="android:textColor">@color/placeholder_grey</item>
- <item name="android:textSize">16sp</item>
- </style>
-
-</resources>
diff --git a/mobile/android/base/resources/values/styles.xml b/mobile/android/base/resources/values/styles.xml
deleted file mode 100644
index dd22ef00b..000000000
--- a/mobile/android/base/resources/values/styles.xml
+++ /dev/null
@@ -1,832 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
-
- <!--
- Base application styles. This could be overridden in other res/values-XXX/themes.xml.
- -->
- <style name="Widget"/>
-
- <style name="Widget.BaseButton" parent="android:style/Widget.Button"/>
-
- <style name="Widget.BaseDropDownItem" parent="android:style/Widget.DropDownItem"/>
-
- <style name="Widget.BaseEditText" parent="android:style/Widget.EditText"/>
-
- <style name="Widget.BaseListView" parent="android:style/Widget.ListView"/>
-
- <style name="Widget.BaseGridView" parent="android:style/Widget.GridView"/>
-
- <style name="Widget.BaseTextView" parent="android:style/Widget.TextView"/>
-
- <style name="Widget.ProgressBar.Horizontal" parent="android:style/Widget.ProgressBar.Horizontal"/>
-
- <!--
- Application styles. All customizations that are not specific
- to a particular API level can go here.
- -->
- <style name="Widget.Button" parent="Widget.BaseButton">
- <item name="android:textAppearance">@style/TextAppearance.Widget.Button</item>
- </style>
-
- <style name="Widget.DropDownItem" parent="Widget.BaseDropDownItem">
- <item name="android:textAppearance">@style/TextAppearance.Widget.DropDownItem</item>
- </style>
-
- <style name="Widget.EditText" parent="Widget.BaseEditText">
- <item name="android:textAppearance">@style/TextAppearance.Widget.EditText</item>
- </style>
-
- <style name="Widget.TextView" parent="Widget.BaseTextView">
- <item name="android:textAppearance">@style/TextAppearance.Widget.TextView</item>
- </style>
-
- <style name="Widget.ListView" parent="Widget.BaseListView">
- <item name="android:divider">@color/toolbar_divider_grey</item>
- <item name="android:dividerHeight">@dimen/page_row_divider_height</item>
- <item name="android:cacheColorHint">@android:color/transparent</item>
- <item name="android:listSelector">@drawable/action_bar_button</item>
- </style>
-
- <style name="Widget.ExpandableListView" parent="Widget.ListView">
- <item name="android:groupIndicator">@android:color/transparent</item>
- </style>
-
- <style name="Widget.GridView" parent="Widget.BaseGridView">
- <item name="android:verticalSpacing">0dip</item>
- <item name="android:horizontalSpacing">0dip</item>
- <item name="android:cacheColorHint">@android:color/transparent</item>
- <item name="android:listSelector">@drawable/action_bar_button</item>
- </style>
-
- <style name="Widget.Home.HomeList">
- <item name="android:scrollbarStyle">outsideOverlay</item>
- </style>
-
- <style name="Widget.ListItem">
- <item name="android:minHeight">?android:attr/listPreferredItemHeight</item>
- <item name="android:textAppearance">?android:attr/textAppearanceLargeInverse</item>
- <item name="android:gravity">center_vertical</item>
- <item name="android:paddingLeft">12dip</item>
- <item name="android:paddingRight">7dip</item>
- <item name="android:checkMark">?android:attr/listChoiceIndicatorMultiple</item>
- <item name="android:ellipsize">marquee</item>
- </style>
-
- <style name="Widget.Spinner" parent="android:style/Widget.Spinner">
- <item name="android:minWidth">@dimen/doorhanger_input_width</item>
- </style>
-
- <style name="Widget.GeckoMenuListView" parent="Widget.ListView">
- <item name="android:listSelector">@drawable/menu_item_action_bar_bg</item>
- <item name="android:divider">@null</item>
- <item name="android:dividerHeight">0dp</item>
- </style>
-
- <style name="Widget.MenuItemActionBar">
- <item name="android:padding">10dip</item>
- <item name="android:background">@drawable/menu_item_action_bar_bg</item>
- <item name="android:scaleType">fitCenter</item>
- <item name="drawableTintList">@color/action_bar_menu_item_colors</item>"
- </style>
-
- <style name="Widget.MenuItemSecondaryActionBar">
- <item name="android:padding">8dip</item>
- <item name="android:background">@drawable/menu_item_action_bar_bg</item>
- <item name="android:scaleType">centerInside</item>
- <item name="drawableTintList">@color/action_bar_secondary_menu_item_colors</item>
- </style>
-
- <style name="Widget.MenuItemSwitcherLayout">
- <item name="android:gravity">left</item>
- </style>
-
- <style name="Widget.MenuItemDefault">
- <item name="android:paddingLeft">15dip</item>
- <item name="android:paddingRight">10dip</item>
- <item name="android:drawablePadding">6dip</item>
- <item name="android:gravity">center_vertical</item>
- <item name="android:textAppearance">@style/TextAppearance</item>
- <item name="android:singleLine">true</item>
- <item name="android:ellipsize">middle</item>
- <item name="android:textSize">@dimen/menu_item_textsize</item>
- </style>
-
- <style name="Widget.FolderTitle">
- <item name="android:textAppearance">@style/TextAppearance.Widget.Home.ItemTitle</item>
- </style>
-
- <style name="Widget.FolderTitle.OneLine">
- <item name="android:textSize">@dimen/home_folder_title_oneline_textsize</item>
- </style>
-
- <style name="Widget.FolderTitle.TwoLine">
- <item name="android:textSize">@dimen/home_folder_title_twoline_textsize</item>
- </style>
-
- <style name="Widget.TwoLinePageRow" >
- <item name="android:background">@color/pressed_about_page_header_grey</item>
- </style>
-
- <style name="Widget.TwoLinePageRow.Title">
- <item name="android:textAppearance">@style/TextAppearance.Widget.Home.ItemTitle</item>
- <item name="android:textSize">@dimen/home_twolinepagerow_title_textsize</item>
- </style>
-
- <style name="Widget.TwoLinePageRow.Url">
- <item name="android:textAppearance">@style/TextAppearance.Widget.Home.ItemDescription</item>
- <item name="android:includeFontPadding">false</item>
- <item name="android:singleLine">true</item>
- </style>
-
- <style name="Widget.FolderView" parent="Widget.FolderTitle.OneLine">
- <item name="android:layout_height">@dimen/page_group_height</item>
- <item name="android:minHeight">@dimen/page_group_height</item>
- <item name="android:singleLine">true</item>
- <item name="android:ellipsize">none</item>
- <item name="android:background">@color/about_page_header_grey</item>
- <item name="android:paddingLeft">20dp</item>
- <item name="android:drawablePadding">20dp</item>
- </style>
-
- <style name="Widget.PanelItemView" />
-
- <style name="Widget.PanelItemView.Title">
- <item name="android:textAppearance">@style/TextAppearance.Widget.Home.ItemTitle</item>
- <item name="android:maxLines">2</item>
- <item name="android:ellipsize">end</item>
- </style>
-
- <style name="Widget.PanelItemView.Description">
- <item name="android:textAppearance">@style/TextAppearance.Widget.Home.ItemDescription</item>
- <item name="android:includeFontPadding">false</item>
- <item name="android:maxLines">2</item>
- <item name="android:ellipsize">end</item>
- </style>
-
- <style name="Widget.TopSitesGridView" parent="Widget.GridView">
- <item name="android:padding">7dp</item>
- <item name="android:horizontalSpacing">0dp</item>
- <item name="android:verticalSpacing">7dp</item>
- </style>
-
- <style name="Widget.TopSitesGridItemView">
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">match_parent</item>
- <item name="android:padding">5dip</item>
- <item name="android:orientation">vertical</item>
- </style>
-
- <style name="Widget.TabsGridLayout" parent="Widget.GridView">
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">match_parent</item>
- <item name="android:paddingTop">0dp</item>
- <item name="android:stretchMode">spacingWidth</item>
- <item name="android:scrollbarStyle">outsideOverlay</item>
- <item name="android:gravity">center</item>
- <item name="android:numColumns">auto_fit</item>
- <item name="android:columnWidth">@dimen/tab_panel_column_width</item>
- <item name="android:horizontalSpacing">2dp</item>
- <item name="android:verticalSpacing">@dimen/tab_panel_grid_vspacing</item>
- <item name="android:drawSelectorOnTop">true</item>
- <item name="android:clipToPadding">false</item>
- </style>
-
- <style name="Widget.BookmarkItemView" parent="Widget.TwoLinePageRow"/>
-
- <style name="Widget.BookmarksListView" parent="Widget.HomeListView"/>
-
- <style name="Widget.TopSitesThumbnailView">
- <item name="android:padding">0dip</item>
- <item name="android:scaleType">centerCrop</item>
- </style>
-
- <style name="Widget.TopSitesGridItemPin">
- <item name="android:minWidth">30dip</item>
- <item name="android:minHeight">30dip</item>
- <item name="android:padding">0dip</item>
- </style>
-
- <style name="Widget.TopSitesGridItemTitle">
- <item name="android:textColor">@color/top_sites_grid_item_title</item>
- <item name="android:textSize">12sp</item>
- <item name="android:paddingTop">5dip</item>
- <item name="android:gravity">left</item>
- </style>
-
- <style name="Widget.HomeListView" parent="Widget.ListView">
- <item name="android:divider">@color/toolbar_divider_grey</item>
- </style>
-
- <style name="Widget.TopSitesListView" parent="Widget.BookmarksListView"/>
-
- <style name="Widget.HomeBanner"/>
-
- <style name="Widget.Home" />
-
- <style name="Widget.Home.HeaderItem">
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">36dp</item>
- <item name="android:textAppearance">@style/TextAppearance.Widget.Home.Header</item>
- <item name="android:background">@android:color/white</item>
- <item name="android:focusable">false</item>
- <item name="android:gravity">center|left</item>
- <item name="android:paddingLeft">16dp</item>
- <item name="android:paddingRight">16dp</item>
- <item name="android:paddingTop">11dp</item>
- <item name="android:paddingBottom">11dp</item>
- <item name="android:includeFontPadding">false</item>
- </style>
-
- <style name="Widget.Home.ActionButton">
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">48dp</item>
- <item name="android:textColor">@color/text_and_tabs_tray_grey</item>
- <item name="android:textSize">14sp</item>
- <item name="android:background">@drawable/home_history_clear_button_bg</item>
- <item name="android:focusable">true</item>
- <item name="android:gravity">center</item>
- <item name="android:paddingLeft">10dip</item>
- <item name="android:paddingRight">10dip</item>
- </style>
-
- <style name="Widget.Home.ActionItem">
- <item name="android:layout_width">fill_parent</item>
- <item name="android:layout_height">40dip</item>
- <item name="android:textColor">#000000</item>
- <item name="android:gravity">center</item>
- </style>
-
- <style name="Widget.Firstrun.Button" parent="Widget.BaseButton">
- <item name="android:layout_width">@dimen/firstrun_content_width</item>
- <item name="android:layout_height">60dp</item>
- <item name="android:textColor">@color/android:white</item>
- <item name="android:background">@color/action_orange</item>
- <item name="android:textSize">18sp</item>
- </style>
-
- <style name="Widget.Doorhanger.Button" parent="Widget.BaseButton">
- <item name="android:layout_width">0dp</item>
- <item name="android:layout_height">wrap_content</item>
- <item name="android:layout_weight">1</item>
- <item name="android:minHeight">48dp</item>
- <item name="android:textSize">14sp</item>
- </style>
-
- <!--
- We are overriding the snackbar message style to guarantee a consistent style across Android versions (bug 1217416).
- -->
- <style name="TextAppearance.Design.Snackbar.Message" parent="android:TextAppearance" tools:override="true">
- <item name="android:textSize">@dimen/design_snackbar_text_size</item>
- <item name="android:textColor">@android:color/white</item>
- </style>
-
- <!--
- TextAppearance
- Note: Gecko uses light theme as default, while Android uses dark.
- If Android convention has to be followd, the list of colors specified
- in themes.xml would be inverse, and things would get confusing.
- Hence, Gecko's TextAppearance is based on text over light theme and
- TextAppearance.Inverse is based on text over dark theme.
- -->
- <style name="TextAppearance">
- <item name="android:textColor">?android:attr/textColorPrimary</item>
- <item name="android:textColorHighlight">@color/fennec_ui_orange</item>
- <item name="android:textColorHint">?android:attr/textColorHint</item>
- <item name="android:textColorLink">?android:attr/textColorLink</item>
- <item name="android:textSize">@dimen/menu_item_textsize</item>
- <item name="android:textStyle">normal</item>
- </style>
-
- <style name="TextAppearance.Inverse">
- <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
- <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
- <item name="android:textColorHighlight">@color/text_color_highlight_inverse</item>
- <item name="android:textColorLink">?android:attr/textColorLink</item>
- </style>
-
- <style name="TextAppearance.Large">
- <item name="android:textSize">22sp</item>
- </style>
-
- <style name="TextAppearance.Large.Inverse">
- <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
- <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
- <item name="android:textColorHighlight">@color/text_color_highlight_inverse</item>
- <item name="android:textColorLink">?android:attr/textColorLink</item>
- </style>
-
- <style name="TextAppearance.Medium">
- <item name="android:textSize">18sp</item>
- </style>
-
- <style name="TextAppearance.Medium.Inverse">
- <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
- <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
- <item name="android:textColorHighlight">@color/text_color_highlight_inverse</item>
- <item name="android:textColorLink">?android:attr/textColorLink</item>
- </style>
-
- <style name="TextAppearance.Small">
- <item name="android:textSize">14sp</item>
- <item name="android:textColor">?android:attr/textColorSecondary</item>
- </style>
-
- <style name="TextAppearance.Small.Inverse">
- <item name="android:textColor">?android:attr/textColorSecondaryInverse</item>
- <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
- <item name="android:textColorHighlight">@color/text_color_highlight_inverse</item>
- <item name="android:textColorLink">?android:attr/textColorLink</item>
- </style>
-
- <style name="TextAppearance.EmptyMessage" parent="TextAppearance.Large"/>
-
- <style name="TextAppearance.EmptyHint" parent="TextAppearance.Medium">
- <item name="android:textColor">#FFA62F</item>
- <item name="android:textStyle">italic</item>
- </style>
-
- <style name="TextAppearance.Micro">
- <item name="android:textSize">12sp</item>
- <item name="android:textColor">?android:attr/textColorTertiary</item>
- </style>
-
- <style name="TextAppearance.Micro.Inverse">
- <item name="android:textColor">?android:attr/textColorTertiaryInverse</item>
- <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
- <item name="android:textColorHighlight">@color/text_color_highlight_inverse</item>
- <item name="android:textColorLink">?android:attr/textColorLink</item>
- </style>
-
- <style name="TextAppearance.Widget" />
-
- <style name="TextAppearance.Widget.Button" parent="TextAppearance.Small">
- <item name="android:textColor">@color/primary_text</item>
- </style>
-
- <style name="TextAppearance.Widget.DropDownItem">
- <item name="android:textColor">@color/primary_text</item>
- </style>
-
- <style name="TextAppearance.Widget.EditText">
- <item name="android:textColor">@color/primary_text</item>
- </style>
-
- <style name="TextAppearance.Widget.TextView">
- <item name="android:textColor">@color/primary_text</item>
- </style>
-
- <style name="TextAppearance.Widget.HomePagerTabMenuStrip" parent="TextAppearance.Small">
- <item name="android:textColor">?android:attr/textColorHint</item>
- <item name="android:textSize">14sp</item>
- </style>
-
- <style name="TextAppearance.Widget.Home" />
-
- <style name="TextAppearance.Widget.Home.Header" parent="TextAppearance.Small">
- <item name="android:textColor">@color/disabled_grey</item>
- <item name="android:textSize">12sp</item>
- </style>
-
- <style name="TextAppearance.Widget.Home.ItemTitle" parent="TextAppearance">
- <item name="android:textSize">16dp</item>
- </style>
-
- <style name="TextAppearance.Widget.Home.ItemDescription" parent="TextAppearance.Micro">
- <item name="android:textColor">@color/tabs_tray_icon_grey</item>
- </style>
-
- <style name="TextAppearance.Widget.HomeBanner" parent="TextAppearance.Small">
- <item name="android:textColor">?android:attr/textColorHint</item>
- </style>
-
- <style name="TextAppearance.DoorHanger">
- <item name="android:textColor">@color/placeholder_active_grey</item>
- <item name="android:textColorLink">@color/doorhanger_link</item>
- </style>
-
- <style name="TextAppearance.DoorHanger.Medium">
- <item name="android:textSize">16dp</item>
- </style>
-
- <style name="TextAppearance.DoorHanger.Medium.Bold">
- <item name="android:fontFamily">sans-serif-medium</item>
- </style>
-
- <style name="TextAppearance.DoorHanger.Medium.Light">
- <item name="android:fontFamily">sans-serif-light</item>
- </style>
-
- <style name="TextAppearance.DoorHanger.Small">
- <item name="android:textSize">14sp</item>
- </style>
-
- <style name="TextAppearance.UrlBar.Title" parent="TextAppearance.Small">
- <item name="android:textSize">15sp</item>
- </style>
-
- <!-- BrowserToolbar -->
- <style name="BrowserToolbar">
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">@dimen/browser_toolbar_height</item>
- <item name="android:orientation">horizontal</item>
- </style>
-
- <!-- URL bar -->
- <style name="UrlBar">
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">match_parent</item>
- <item name="android:orientation">horizontal</item>
- </style>
-
- <!-- URL bar - Button -->
- <style name="UrlBar.Button">
- <item name="android:layout_height">match_parent</item>
- <item name="android:background">@android:color/transparent</item>
- </style>
-
- <!-- URL bar - Button -->
- <style name="UrlBar.Title" parent="UrlBar.Button">
- <item name="android:textAppearance">@style/TextAppearance.UrlBar.Title</item>
- <item name="android:textColor">@color/url_bar_title</item>
- <item name="android:textColorHint">@color/url_bar_title_hint</item>
- <item name="android:textColorHighlight">@color/fennec_ui_orange</item>
- <item name="android:textSelectHandle">@drawable/handle_middle</item>
- <item name="android:textSelectHandleLeft">@drawable/handle_start</item>
- <item name="android:textSelectHandleRight">@drawable/handle_end</item>
- <item name="android:textCursorDrawable">@null</item>
- <item name="android:singleLine">true</item>
- <item name="android:gravity">center_vertical|left</item>
- <item name="android:hint">@string/url_bar_default_text</item>
- </style>
-
- <!-- URL bar - Image Button -->
- <style name="UrlBar.ImageButtonBase" parent="UrlBar.Button">
- <item name="android:scaleType">center</item>
- <item name="android:layout_gravity">center_vertical</item>
- <item name="android:background">@android:color/transparent</item>
- </style>
-
- <style name="UrlBar.ImageButton" parent="UrlBar.ImageButtonBase">
- <item name="android:layout_width">@dimen/browser_toolbar_height</item>
- </style>
-
- <!-- TabsLayout -->
- <style name="TabsLayoutBase">
- <item name="android:background">@android:color/transparent</item>
- <item name="android:listSelector">@android:color/transparent</item>
- </style>
-
- <style name="TabsLayout" parent="TabsLayoutBase">
- <item name="android:orientation">vertical</item>
- <item name="android:scrollbars">vertical</item>
- </style>
-
- <style name="TabsItem">
- <item name="android:nextFocusRight">@+id/close</item>
- </style>
-
- <style name="TabsItemClose">
- <item name="android:nextFocusLeft">@+id/info</item>
- </style>
-
- <!-- Tabs panel -->
- <style name="TabsPanelSectionBase">
- <item name="android:orientation">vertical</item>
- <item name="android:layout_marginLeft">40dp</item>
- <item name="android:layout_marginRight">40dp</item>
- </style>
-
- <style name="TabsPanelSection" parent="TabsPanelSectionBase">
- <!-- We set values in landscape. -->
- </style>
-
- <style name="TabsPanelItemBase">
- <item name="android:layout_marginBottom">28dp</item>
- <item name="android:layout_gravity">center</item>
- <item name="android:gravity">center</item>
- </style>
-
- <style name="TabsPanelItem" parent="TabsPanelItemBase">
- <!-- We set values in landscape. -->
- </style>
-
- <style name="TabsPanelItem.TextAppearance">
- <item name="android:textColor">#C0C9D0</item>
- <item name="android:textSize">14sp</item>
- <item name="android:lineSpacingMultiplier">1.35</item>
- </style>
-
- <style name="TabsPanelItem.TextAppearance.Header">
- <item name="android:textSize">18sp</item>
- <item name="android:layout_marginBottom">16dp</item>
- </style>
-
- <style name="TabsPanelItem.TextAppearance.Linkified">
- <item name="android:clickable">true</item>
- <item name="android:focusable">true</item>
- <item name="android:textColor">#0292D6</item>
- </style>
-
- <style name="Widget.RemoteTabsItemView" parent="Widget.TwoLinePageRow"/>
-
- <style name="Widget.RemoteTabsClientView" parent="Widget.TwoLinePageRow">
- <item name="android:background">@color/about_page_header_grey</item>
- </style>
-
- <style name="Widget.RemoteTabsListView" parent="Widget.HomeListView">
- <item name="android:childDivider">@color/toolbar_divider_grey</item>
- </style>
-
- <style name="Widget.HistoryListView" parent="Widget.HomeListView">
- <item name="android:childDivider">@color/toolbar_divider_grey</item>
- <item name="android:drawSelectorOnTop">true</item>
- </style>
-
- <!-- TabsLayout Row -->
- <style name="TabLayoutItemTextAppearance">
- <item name="android:textColor">#FFFFFFFF</item>
- <item name="android:singleLine">true</item>
- <item name="android:ellipsize">middle</item>
- </style>
-
- <!-- TabsLayout RemoteTabs Row Url -->
- <style name="TabLayoutItemTextAppearance.Url">
- <item name="android:textColor">#FFA4A7A9</item>
- </style>
-
- <!-- TabWidget -->
- <style name="TabWidget">
- <item name="android:layout_width">wrap_content</item>
- <item name="android:layout_height">40dip</item>
- <item name="android:layout_weight">1.0</item>
- </style>
-
- <!-- Find bar -->
- <style name="FindBar">
- <item name="android:background">@color/text_and_tabs_tray_grey</item>
- <item name="android:paddingLeft">3dip</item>
- <item name="android:paddingRight">3dip</item>
- <item name="android:paddingTop">6dip</item>
- <item name="android:paddingBottom">6dip</item>
- </style>
-
- <!-- Find bar - Image Button -->
- <style name="FindBar.ImageButton">
- <item name="android:layout_width">wrap_content</item>
- <item name="android:layout_height">wrap_content</item>
- <item name="android:layout_marginLeft">5dip</item>
- <item name="android:layout_marginRight">5dip</item>
- <item name="android:scaleType">fitCenter</item>
- <item name="android:layout_centerVertical">true</item>
- <item name="android:background">@drawable/action_bar_button_inverse</item>
- </style>
-
- <style name="GeckoDialogTitle">
- <item name="android:textAppearance">@android:style/TextAppearance.DialogWindowTitle</item>
- </style>
-
- <style name="GeckoDialogTitle.SubTitle" />
-
- <style name="PopupAnimation">
- <item name="@android:windowEnterAnimation">@anim/popup_show</item>
- <item name="@android:windowExitAnimation">@anim/popup_hide</item>
- </style>
-
- <style name="ToastBase">
- <item name="android:background">@drawable/toast_background</item>
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">wrap_content</item>
- <item name="android:layout_alignParentBottom">true</item>
- <item name="android:layout_centerHorizontal">true</item>
- <item name="android:layout_gravity">bottom|center_horizontal</item>
- <item name="android:layout_marginBottom">64dp</item>
- <item name="android:layout_marginTop">0dp</item>
- <item name="android:orientation">horizontal</item>
- <item name="android:clickable">true</item>
- </style>
-
- <style name="Toast" parent="ToastBase">
- <item name="android:layout_marginLeft">16dp</item>
- <item name="android:layout_marginRight">16dp</item>
- </style>
-
- <style name="ToastElementBase">
- <item name="android:background">@null</item>
- <item name="android:paddingLeft">12dp</item>
- <item name="android:paddingRight">12dp</item>
- <item name="android:paddingTop">11dp</item>
- <item name="android:paddingBottom">11dp</item>
- </style>
-
- <style name="ToastDividerBase">
- <item name="android:background">@color/toolbar_divider_grey</item>
- <item name="android:layout_width">1dp</item>
- <item name="android:layout_height">match_parent</item>
- </style>
-
- <style name="ToastMessageBase" parent="ToastElementBase">
- <item name="android:textColor">@color/toast_button_text</item>
- <item name="android:layout_width">0dp</item>
- <item name="android:layout_weight">1</item>
- <item name="android:layout_height">wrap_content</item>
- <item name="android:layout_gravity">center_vertical</item>
- <item name="android:ellipsize">end</item>
- <item name="android:maxLines">1</item>
- <item name="android:clickable">false</item>
- <item name="android:focusable">false</item>
- </style>
-
- <style name="ToastButtonBase" parent="ToastElementBase">
- <item name="android:background">@drawable/toast_button_background</item>
- <item name="android:textColor">@color/toast_button_text</item>
- <item name="android:minHeight">0dp</item>
- <item name="android:minWidth">0dp</item>
- <item name="android:layout_width">wrap_content</item>
- <item name="android:layout_height">wrap_content</item>
- <item name="android:layout_gravity">center_vertical</item>
- <item name="android:drawablePadding">12dp</item>
- <item name="android:maxWidth">160dp</item>
- </style>
-
- <style name="ToastDivider" parent="ToastDividerBase">
- <item name="android:layout_marginTop">14dp</item>
- <item name="android:layout_marginBottom">14dp</item>
- </style>
-
- <style name="ToastMessage" parent="ToastMessageBase">
- <item name="android:textAppearance">?android:textAppearanceSmall</item>
- </style>
-
- <style name="ToastButton" parent="ToastButtonBase">
- <item name="android:textAppearance">?android:textAppearanceSmall</item>
- <item name="android:textStyle">bold</item>
- </style>
-
- <!-- Ideally, we use the same style for the action bar & action mode, but unfortunately
- some attrs that share a purpose have different names so instead we inherit. -->
- <style name="GeckoActionBar" parent="ThemeOverlay.AppCompat.ActionBar">
- <item name="android:colorBackground">@color/action_bar_bg_color</item>
- <item name="colorAccent">@color/fennec_ui_orange</item>
- <item name="colorControlNormal">@color/toolbar_icon_grey</item>
- </style>
- <style name="GeckoActionBar.ActionMode">
- <item name="android:background">@color/action_bar_bg_color</item>
- <!-- Note: the bottom divider is drawn in code. -->
- </style>
-
- <style name="PreferencesActionBar" parent="Widget.AppCompat.ActionBar.Solid">
- <item name="displayOptions">showHome|homeAsUp|showTitle</item>
- </style>
-
- <style name="GeckoActionBar.Title">
- <item name="android:gravity">center_vertical</item>
- <item name="android:minWidth">0dp</item>
- <item name="android:background">@android:color/transparent</item>
- <item name="android:textAppearance">@style/TextAppearance.Medium</item>
- <item name="android:drawableLeft">@drawable/ab_done</item>
- <item name="android:paddingLeft">15dp</item>
- <item name="android:paddingRight">15dp</item>
- <item name="android:contentDescription">@string/actionbar_done</item>
- </style>
-
- <style name="GeckoActionBar.Button" parent="Widget.MenuItemActionBar">
- <item name="android:padding">8dp</item>
- </style>
-
- <style name="GeckoActionBar.Button.MenuButton">
- <item name="android:scaleType">center</item>
- <item name="android:src">@drawable/menu</item>
- <item name="android:tint">@color/toolbar_icon_grey</item>
- <item name="android:contentDescription">@string/actionbar_menu</item>
- <item name="android:background">@android:color/transparent</item>
- </style>
-
- <style name="GeckoActionBar.Buttons">
- <item name="android:background">@android:color/transparent</item>
- <item name="android:textColor">@color/placeholder_active_grey</item>
- <item name="android:gravity">right</item>
- </style>
-
- <style name="ShareOverlayTitle">
- <item name="android:gravity">center_horizontal</item>
- <item name="android:paddingLeft">15dp</item>
- <item name="android:paddingRight">15dp</item>
- </style>
-
- <style name="TextAppearance.ShareOverlay">
- <item name="android:fontFamily">sans-serif</item>
- </style>
-
- <style name="TextAppearance.ShareOverlay.Header">
- <item name="android:textColor">@android:color/white</item>
- <item name="android:fontFamily">sans-serif-light</item>
- <item name="android:textStyle">normal</item>
- </style>
-
- <style name="ShareOverlayRow">
- <item name="android:minHeight">60dp</item>
- <item name="android:gravity">center_vertical</item>
- <item name="android:background">@drawable/overlay_share_button_background</item>
- <item name="android:focusableInTouchMode">false</item>
- </style>
-
- <style name="TabInput"></style>
-
- <style name="TabInput.TabWidget">
- <item name="android:divider">@drawable/divider_vertical</item>
- <item name="android:background">@drawable/tab_indicator_background</item>
- </style>
-
- <style name="TabInput.Tab">
- <item name="android:background">@drawable/tabs_strip_indicator</item>
- <item name="android:gravity">center</item>
- <item name="android:minHeight">@dimen/menu_item_row_height</item>
- </style>
-
- <style name="TextAppearance.FirstrunLight"/>
- <style name="TextAppearance.FirstrunRegular"/>
-
- <style name="TextAppearance.FirstrunLight.Main">
- <item name="android:textSize">20sp</item>
- <item name="android:textColor">@color/text_and_tabs_tray_grey</item>
- </style>
-
- <style name="TextAppearance.FirstrunRegular.Body">
- <item name="android:textSize">16sp</item>
- <item name="android:textColor">@color/placeholder_grey</item>
- <item name="android:lineSpacingMultiplier">1.25</item>
- </style>
-
- <style name="TextAppearance.FirstrunRegular.Link">
- <item name="android:textSize">16sp</item>
- <item name="android:textColor">@color/link_blue</item>
- </style>
-
- <!-- Remote Tabs home panel -->
- <style name="RemoteTabsPanelFrame">
- <item name="android:paddingLeft">32dp</item>
- <item name="android:paddingRight">32dp</item>
- <item name="android:paddingTop">@dimen/home_remote_tabs_top_padding</item>
- <item name="android:orientation">vertical</item>
- <item name="android:layout_gravity">center_horizontal</item>
- <item name="android:layout_width">wrap_content</item>
- <item name="android:layout_height">wrap_content</item>
- </style>
-
- <style name="RemoteTabsPanelItem">
- <item name="android:layout_gravity">center</item>
- <item name="android:gravity">center</item>
- <item name="android:layout_marginBottom">16dp</item>
- <item name="android:maxWidth">320dp</item>
- </style>
-
- <style name="RemoteTabsPanelItem.TextAppearance">
- <item name="android:textColor">@color/placeholder_grey</item>
- <item name="android:textSize">16sp</item>
- <item name="android:lineSpacingMultiplier">1.25</item>
- <item name="android:layout_marginLeft">8dp</item>
- <item name="android:layout_marginRight">8dp</item>
- </style>
-
- <style name="RemoteTabsPanelItem.TextAppearance.Header">
- <item name="android:textColor">@color/placeholder_active_grey</item>
- <item name="android:textSize">20sp</item>
- <item name="android:layout_marginBottom">8dp</item>
- </style>
-
- <style name="RemoteTabsPanelItem.TextAppearance.Linkified">
- <item name="android:clickable">true</item>
- <item name="android:focusable">true</item>
- <item name="android:textColor">#0092DB</item>
- </style>
-
- <style name="RemoteTabsPanelItem.Button">
- <item name="android:background">@drawable/remote_tabs_setup_button_background</item>
- <item name="android:textColor">#FFFFFF</item>
- <item name="android:textSize">20sp</item>
- <item name="android:gravity">center</item>
- <item name="android:paddingTop">16dp</item>
- <item name="android:paddingBottom">16dp</item>
- <item name="android:paddingLeft">8dp</item>
- <item name="android:paddingRight">8dp</item>
-
- <!-- AppCompat sets Button text to all caps so we override that here. -->
- <item name="textAllCaps">false</item>
- </style>
-
- <style name="TabQueueActivity" parent="android:style/Theme.NoDisplay" />
-
- <style name="ActivityStreamContextMenuText">
- <item name="android:textSize">16sp</item>
- </style>
-
- <!-- We use this style to provide our own divider that has an inset on the left side -->
- <style name="ActivityStreamContextMenuStyle">
- <item name="android:listDivider">@drawable/as_contextmenu_divider</item>
- </style>
-
-</resources>
diff --git a/mobile/android/base/resources/values/themes.xml b/mobile/android/base/resources/values/themes.xml
deleted file mode 100644
index f62a9d454..000000000
--- a/mobile/android/base/resources/values/themes.xml
+++ /dev/null
@@ -1,123 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<resources>
-
- <!--
- Base application theme. This could be overridden by GeckoBaseTheme
- in other res/values-XXX/themes.xml.
- -->
- <style name="GeckoBase" parent="Theme.AppCompat.Light.DarkActionBar">
- <item name="windowNoTitle">true</item>
- <item name="windowActionBar">false</item>
- <item name="android:windowContentOverlay">@null</item>
- </style>
-
- <style name="GeckoDialogBase" parent="@android:style/Theme.Dialog">
- <item name="android:windowNoTitle">true</item>
- <item name="android:windowContentOverlay">@null</item>
- </style>
-
- <style name="GeckoTitleDialogBase" parent="@android:style/Theme.Dialog" />
-
- <style name="Gecko.Preferences">
- <item name="windowActionBar">true</item>
- <item name="windowNoTitle">false</item>
- <item name="actionBarTheme">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item>
- <item name="actionBarStyle">@style/PreferencesActionBar</item>
- </style>
-
- <!--
- Application Theme. All customizations that are not specific
- to a particular API level can go here.
- -->
- <style name="Gecko" parent="GeckoBase">
- <!-- Default colors -->
- <item name="android:textColorPrimary">@color/primary_text</item>
- <item name="android:textColorSecondary">@color/secondary_text</item>
- <item name="android:textColorTertiary">@color/tertiary_text</item>
-
- <!-- Default inverse colors -->
- <item name="android:textColorPrimaryInverse">@color/primary_text</item>
- <item name="android:textColorSecondaryInverse">@color/secondary_text</item>
- <item name="android:textColorTertiaryInverse">@color/tertiary_text</item>
-
- <!-- Disabled colors -->
- <item name="android:textColorPrimaryDisableOnly">@color/text_color_primary_disable_only</item>
-
- <!-- Hint colors -->
- <item name="android:textColorHint">@color/text_color_hint</item>
- <item name="android:textColorHintInverse">@color/text_color_hint_inverse</item>
-
- <!-- Highlight colors -->
- <item name="android:textColorHighlight">@color/fennec_ui_orange</item>
- <item name="android:textColorHighlightInverse">@color/text_color_highlight_inverse</item>
-
- <!-- Link colors -->
- <item name="android:textColorLink">@color/text_color_link</item>
-
- <!-- TextAppearances -->
- <item name="android:textAppearance">@style/TextAppearance</item>
- <item name="android:textAppearanceInverse">@style/TextAppearance.Inverse</item>
- <item name="android:textAppearanceLarge">@style/TextAppearance.Large</item>
- <item name="android:textAppearanceMedium">@style/TextAppearance.Medium</item>
- <item name="android:textAppearanceSmall">@style/TextAppearance.Small</item>
- <item name="android:textAppearanceLargeInverse">@style/TextAppearance.Large.Inverse</item>
- <item name="android:textAppearanceMediumInverse">@style/TextAppearance.Medium.Inverse</item>
- <item name="android:textAppearanceSmallInverse">@style/TextAppearance.Small.Inverse</item>
-
- <item name="colorAccent">@color/action_orange</item>
-
- <item name="actionBarTheme">@style/GeckoActionBar</item>
- </style>
-
- <style name="Gecko.Dialog" parent="GeckoDialogBase"/>
-
- <style name="Gecko.TitleDialog" parent="GeckoTitleDialogBase"/>
-
- <!--
- Activity based themes, dependent on API level. This theme is replaced
- by GeckoAppBase from res/values-vXX/themes.xml on newer devices.
- -->
- <style name="GeckoAppBase" parent="Gecko">
- <item name="android:buttonStyle">@style/Widget.Button</item>
- <item name="android:dropDownItemStyle">@style/Widget.DropDownItem</item>
- <item name="android:editTextStyle">@style/Widget.EditText</item>
- <item name="android:textViewStyle">@style/Widget.TextView</item>
- <item name="menuItemDefaultStyle">@style/Widget.MenuItemDefault</item>
- </style>
-
- <!-- All customizations that are NOT specific to a particular API-level can go here. -->
- <style name="Gecko.App" parent="GeckoAppBase">
- <item name="android:gridViewStyle">@style/Widget.GridView</item>
- <item name="android:spinnerStyle">@style/Widget.Spinner</item>
- <item name="android:windowBackground">@android:color/white</item>
- <item name="bookmarksListViewStyle">@style/Widget.BookmarksListView</item>
- <item name="tabGridLayoutViewStyle">@style/Widget.TabsGridLayout</item>
- <item name="geckoMenuListViewStyle">@style/Widget.GeckoMenuListView</item>
- <item name="homeListViewStyle">@style/Widget.HomeListView</item>
- <item name="menuItemActionBarStyle">@style/Widget.MenuItemActionBar</item>
- <item name="menuItemActionModeStyle">@style/GeckoActionBar.Button</item>
- <item name="topSitesGridItemViewStyle">@style/Widget.TopSitesGridItemView</item>
- <item name="topSitesGridViewStyle">@style/Widget.TopSitesGridView</item>
- <item name="topSitesThumbnailViewStyle">@style/Widget.TopSitesThumbnailView</item>
- </style>
-
- <!-- Make an activity appear like an overlay. -->
- <style name="OverlayActivity" parent="Gecko">
- <item name="android:windowBackground">@android:color/transparent</item>
- <item name="android:windowNoTitle">true</item>
- <item name="android:windowIsTranslucent">true</item>
- <item name="android:backgroundDimEnabled">true</item>
-
- <!-- Set the app's title bar color in the recent app switcher.
-
- Note: We'd prefer not to show up in the recent app switcher (bug 1137928). -->
- <item name="android:colorPrimary">@color/text_and_tabs_tray_grey</item>
- <!-- We display the overlay on top of other Activities so show their status bar. -->
- <item name="android:statusBarColor">@android:color/transparent</item>
- </style>
-
-</resources>
diff --git a/mobile/android/base/resources/values/vpi__attrs.xml b/mobile/android/base/resources/values/vpi__attrs.xml
deleted file mode 100644
index ffe895f22..000000000
--- a/mobile/android/base/resources/values/vpi__attrs.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 Jake Wharton
- Copyright (C) 2011 Patrik Åkerfeldt
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <declare-styleable name="ViewPagerIndicator">
- <!-- Style of the circle indicator. -->
- <attr name="vpiCirclePageIndicatorStyle" format="reference"/>
- <!-- Style of the icon indicator's views. -->
- <attr name="vpiIconPageIndicatorStyle" format="reference"/>
- <!-- Style of the line indicator. -->
- <attr name="vpiLinePageIndicatorStyle" format="reference"/>
- <!-- Style of the title indicator. -->
- <attr name="vpiTitlePageIndicatorStyle" format="reference"/>
- <!-- Style of the tab indicator's tabs. -->
- <attr name="vpiTabPageIndicatorStyle" format="reference"/>
- <!-- Style of the underline indicator. -->
- <attr name="vpiUnderlinePageIndicatorStyle" format="reference"/>
- </declare-styleable>
-
- <attr name="centered" format="boolean" />
- <attr name="selectedColor" format="color" />
- <attr name="strokeWidth" format="dimension" />
- <attr name="unselectedColor" format="color" />
-
- <declare-styleable name="CirclePageIndicator">
- <!-- Whether or not the indicators should be centered. -->
- <attr name="centered" />
- <!-- Color of the filled circle that represents the current page. -->
- <attr name="fillColor" format="color" />
- <!-- Color of the filled circles that represents pages. -->
- <attr name="pageColor" format="color" />
- <!-- Orientation of the indicator. -->
- <attr name="android:orientation"/>
- <!-- Radius of the circles. This is also the spacing between circles. -->
- <attr name="radius" format="dimension" />
- <!-- Whether or not the selected indicator snaps to the circles. -->
- <attr name="snap" format="boolean" />
- <!-- Color of the open circles. -->
- <attr name="strokeColor" format="color" />
- <!-- Width of the stroke used to draw the circles. -->
- <attr name="strokeWidth" />
- <!-- View background -->
- <attr name="android:background"/>
- </declare-styleable>
-</resources>
diff --git a/mobile/android/base/resources/values/vpi__defaults.xml b/mobile/android/base/resources/values/vpi__defaults.xml
deleted file mode 100644
index f902f48a8..000000000
--- a/mobile/android/base/resources/values/vpi__defaults.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 Jake Wharton
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <bool name="default_circle_indicator_centered">true</bool>
- <color name="default_circle_indicator_fill_color">#FFFFFFFF</color>
- <color name="default_circle_indicator_page_color">#00000000</color>
- <integer name="default_circle_indicator_orientation">0</integer>
- <dimen name="default_circle_indicator_radius">3dp</dimen>
- <bool name="default_circle_indicator_snap">false</bool>
- <color name="default_circle_indicator_stroke_color">#FFDDDDDD</color>
- <dimen name="default_circle_indicator_stroke_width">1dp</dimen>
-</resources> \ No newline at end of file
diff --git a/mobile/android/base/resources/xml-v11/preference_headers.xml b/mobile/android/base/resources/xml-v11/preference_headers.xml
deleted file mode 100644
index dedd90806..000000000
--- a/mobile/android/base/resources/xml-v11/preference_headers.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- Tablet only: Sync is nested within the "General" header on tablets,
- instead of being a top-level menu item.
- See xml-v11/preferences.xml for single-pane v11+ phone layout. -->
-
-<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
-
- <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
- android:title="@string/pref_header_general"
- android:id="@+id/pref_header_general">
- <extra android:name="resource"
- android:value="preferences_general_tablet"/>
- </header>
-
- <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
- android:title="@string/pref_header_search"
- android:id="@+id/pref_header_search">
- <extra android:name="resource"
- android:value="preferences_search"/>
- </header>
-
- <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
- android:title="@string/pref_header_privacy_short"
- android:id="@+id/pref_header_privacy">
- <extra android:name="resource"
- android:value="preferences_privacy"/>
- </header>
-
- <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
- android:title="@string/pref_header_accessibility"
- android:id="@+id/pref_header_accessibility">
- <extra android:name="resource"
- android:value="preferences_accessibility"/>
- </header>
-
- <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
- android:title="@string/pref_header_notifications"
- android:id="@+id/pref_header_notifications">
- <extra android:name="resource"
- android:value="preferences_notifications"/>
- </header>
-
- <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
- android:title="@string/pref_header_advanced"
- android:id="@+id/pref_header_advanced">
- <extra android:name="resource"
- android:value="preferences_advanced"/>
- </header>
-
- <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
- android:title="@string/pref_clear_private_data_now"
- android:id="@+id/pref_header_clear_private_data">
- <extra android:name="resource"
- android:value="preferences_privacy_clear_tablet"/>
- </header>
-
- <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
- android:title="@string/pref_default_browser"
- android:id="@+id/pref_header_default_browser">
- <extra android:name="resource"
- android:value="preferences_default_browser_tablet"/>
- </header>
-
- <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
- android:title="@string/pref_header_vendor">
- <extra android:name="resource"
- android:value="preferences_vendor"/>
- </header>
-
-</preference-headers>
diff --git a/mobile/android/base/resources/xml-v11/preferences_default_browser_tablet.xml b/mobile/android/base/resources/xml-v11/preferences_default_browser_tablet.xml
deleted file mode 100644
index 53c2c2b56..000000000
--- a/mobile/android/base/resources/xml-v11/preferences_default_browser_tablet.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
- <org.mozilla.gecko.preferences.LinkPreference android:key="android.not_a_preference.default_browser.link"
- android:title="@string/pref_default_browser_mozilla_support_tablet"
- android:persistent="false"
- url="https://support.mozilla.org/kb/make-firefox-default-browser-android?utm_source=inproduct&amp;utm_medium=settings&amp;utm_campaign=mobileandroid"/>
-</PreferenceScreen> \ No newline at end of file
diff --git a/mobile/android/base/resources/xml-v11/preferences_privacy_clear_tablet.xml b/mobile/android/base/resources/xml-v11/preferences_privacy_clear_tablet.xml
deleted file mode 100644
index 49cc895c4..000000000
--- a/mobile/android/base/resources/xml-v11/preferences_privacy_clear_tablet.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto">
- <org.mozilla.gecko.preferences.PrivateDataPreference
- android:key="android.not_a_preference.privacy.clear"
- android:title="@string/pref_clear_private_data_now_tablet"
- android:persistent="true"
- android:positiveButtonText="@string/button_clear_data"
- gecko:entries="@array/pref_private_data_entries"
- gecko:entryValues="@array/pref_private_data_values"
- gecko:entryKeys="@array/pref_private_data_keys"
- gecko:initialValues="@array/pref_private_data_defaults" />
-</PreferenceScreen>
diff --git a/mobile/android/base/resources/xml-v11/preferences_search.xml b/mobile/android/base/resources/xml-v11/preferences_search.xml
deleted file mode 100644
index 937b05b61..000000000
--- a/mobile/android/base/resources/xml-v11/preferences_search.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- android:title="@string/pref_category_search"
- android:enabled="false">
-
- <PreferenceCategory android:title="@string/pref_category_add_search_providers">
-
- <org.mozilla.gecko.preferences.ModifiableHintPreference
- android:layout="@layout/preference_search_tip"
- android:enabled="false"
- android:selectable="false"/>
-
- </PreferenceCategory>
-
- <org.mozilla.gecko.preferences.SearchPreferenceCategory
- android:title="@string/pref_category_installed_search_engines"/>
-
- <CheckBoxPreference android:key="browser.search.suggest.enabled"
- android:title="@string/pref_search_suggestions"
- android:defaultValue="false"
- android:persistent="false" />
-
- <CheckBoxPreference android:key="android.not_a_preference.search.search_history.enabled"
- android:title="@string/pref_history_search_suggestions"
- android:defaultValue="true"
- android:persistent="true" />
-
-</PreferenceScreen>
diff --git a/mobile/android/base/resources/xml/preference_headers.xml b/mobile/android/base/resources/xml/preference_headers.xml
deleted file mode 100644
index e17c9bd77..000000000
--- a/mobile/android/base/resources/xml/preference_headers.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- This file is a stub to allow IDs to be used in code
- even for a version-limited build. -->
-
-<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
- <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
- android:id="@+id/pref_header_search">
- </header>
- <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
- android:id="@+id/pref_header_notifications">
- </header>
- <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
- android:id="@+id/pref_header_advanced">
- </header>
- <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
- android:id="@+id/pref_header_accessibility">
- </header>
- <header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
- android:id="@+id/pref_header_clear_private_data">
- </header>
-</preference-headers>
diff --git a/mobile/android/base/resources/xml/preferences.xml b/mobile/android/base/resources/xml/preferences.xml
deleted file mode 100644
index 06716cd9a..000000000
--- a/mobile/android/base/resources/xml/preferences.xml
+++ /dev/null
@@ -1,85 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- See xml-v11/preference_headers.xml for tablet layout. -->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- android:enabled="false">
-
- <org.mozilla.gecko.preferences.SyncPreference android:key="android.not_a_preference.sync"
- android:title="@string/pref_sync"
- android:icon="@drawable/sync_avatar_default"
- android:summary="@string/pref_sync_summary"
- android:persistent="false" />
-
- <PreferenceScreen android:title="@string/pref_category_general"
- android:summary="@string/pref_category_general_summary"
- android:key="android.not_a_preference.general_screen"
- android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
- <extra android:name="resource"
- android:value="preferences_general"/>
- </PreferenceScreen>
-
- <PreferenceScreen android:title="@string/pref_category_search"
- android:summary="@string/pref_category_search_summary"
- android:key="android.not_a_preference.search_screen"
- android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
- <extra android:name="resource"
- android:value="preferences_search"/>
- </PreferenceScreen>
-
- <PreferenceScreen android:title="@string/pref_category_privacy_short"
- android:summary="@string/pref_category_privacy_summary"
- android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
- <extra android:name="resource"
- android:value="preferences_privacy" />
- </PreferenceScreen>
-
- <PreferenceScreen android:title="@string/pref_category_accessibility"
- android:summary="@string/pref_category_accessibility_summary"
- android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
- <extra android:name="resource"
- android:value="preferences_accessibility" />
- </PreferenceScreen>
-
- <PreferenceScreen android:title="@string/pref_category_notifications"
- android:summary="@string/pref_category_notifications_summary"
- android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment">
- <extra android:name="resource"
- android:value="preferences_notifications"/>
- </PreferenceScreen>
-
- <PreferenceScreen android:title="@string/pref_category_advanced"
- android:summary="@string/pref_category_advanced_summary"
- android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
- android:key="android.not_a_preference.advanced_screen" >
- <extra android:name="resource"
- android:value="preferences_advanced"/>
- </PreferenceScreen>
-
- <org.mozilla.gecko.preferences.PrivateDataPreference
- android:key="android.not_a_preference.privacy.clear"
- android:title="@string/pref_clear_private_data_now"
- android:persistent="true"
- android:positiveButtonText="@string/button_clear_data"
- gecko:entries="@array/pref_private_data_entries"
- gecko:entryValues="@array/pref_private_data_values"
- gecko:entryKeys="@array/pref_private_data_keys"
- gecko:initialValues="@array/pref_private_data_defaults" />
-
- <org.mozilla.gecko.preferences.LinkPreference android:key="android.not_a_preference.default_browser.link"
- android:title="@string/pref_default_browser"
- android:persistent="false"
- url="https://support.mozilla.org/kb/make-firefox-default-browser-android?utm_source=inproduct&amp;utm_medium=settings&amp;utm_campaign=mobileandroid"/>
-
- <PreferenceScreen android:title="@string/pref_category_vendor"
- android:summary="@string/pref_category_vendor_summary"
- android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
- <extra android:name="resource"
- android:value="preferences_vendor"/>
- </PreferenceScreen>
-
-</PreferenceScreen>
diff --git a/mobile/android/base/resources/xml/preferences_accessibility.xml b/mobile/android/base/resources/xml/preferences_accessibility.xml
deleted file mode 100644
index 8e352f1fd..000000000
--- a/mobile/android/base/resources/xml/preferences_accessibility.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- android:title="@string/pref_category_accessibility"
- android:enabled="false">
-
- <org.mozilla.gecko.preferences.FontSizePreference
- android:key="font.size.inflation.minTwips"
- android:title="@string/pref_text_size"
- android:positiveButtonText="@string/pref_font_size_set"
- android:negativeButtonText="@string/button_cancel"
- android:persistent="false" />
-
- <SwitchPreference android:key="browser.ui.zoom.force-user-scalable"
- android:title="@string/pref_zoom_force_enabled"
- android:summary="@string/pref_zoom_force_enabled_summary" />
-
- <SwitchPreference android:key="ui.zoomedview.enabled"
- android:title="@string/pref_magnifying_glass_enabled"
- android:summary="@string/pref_magnifying_glass_enabled_summary" />
-
- <SwitchPreference android:key="android.not_a_preference.voice_input_enabled"
- android:title="@string/pref_voice_input"
- android:summary="@string/pref_voice_input_summary"
- android:defaultValue="true"/>
-
- <SwitchPreference android:key="android.not_a_preference.qrcode_enabled"
- android:title="@string/pref_qrcode_enabled"
- android:summary="@string/pref_qrcode_enabled_summary"
- android:defaultValue="true"/>
-
-</PreferenceScreen>
diff --git a/mobile/android/base/resources/xml/preferences_advanced.xml b/mobile/android/base/resources/xml/preferences_advanced.xml
deleted file mode 100644
index 32cdf0b91..000000000
--- a/mobile/android/base/resources/xml/preferences_advanced.xml
+++ /dev/null
@@ -1,100 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- android:title="@string/pref_category_advanced"
- android:enabled="false">
-
- <org.mozilla.gecko.preferences.AndroidImportPreference
- android:key="android.not_a_preference.import_android"
- gecko:entries="@array/pref_import_android_entries"
- gecko:entryValues="@array/pref_import_android_values"
- gecko:initialValues="@array/pref_import_android_defaults"
- android:title="@string/pref_import_android"
- android:summary="@string/pref_import_android_summary"
- android:positiveButtonText="@string/bookmarkhistory_button_import"
- android:negativeButtonText="@string/button_cancel"
- android:persistent="false" />
-
- <ListPreference android:key="app.update.autodownload"
- android:title="@string/pref_update_autodownload"
- android:entries="@array/pref_update_autodownload_entries"
- android:entryValues="@array/pref_update_autodownload_values"
- android:persistent="false" />
-
- <ListPreference android:key="android.not_a_preference.restoreSession3"
- android:title="@string/pref_restore"
- android:defaultValue="always"
- android:entries="@array/pref_restore_entries"
- android:entryValues="@array/pref_restore_values"
- android:persistent="true" />
-
- <ListPreference android:key="browser.menu.showCharacterEncoding"
- android:title="@string/pref_char_encoding"
- android:entries="@array/pref_char_encoding_entries"
- android:entryValues="@array/pref_char_encoding_values"
- android:persistent="false" />
-
- <PreferenceCategory android:title="@string/pref_category_data_saver">
-
- <ListPreference android:key="browser.image_blocking"
- android:title="@string/pref_tap_to_load_images_title2"
- android:entries="@array/pref_browser_image_blocking_entries"
- android:entryValues="@array/pref_browser_image_blocking_values"
- android:persistent="false" />
-
- <SwitchPreference android:key="browser.display.use_document_fonts"
- android:title="@string/pref_show_web_fonts"
- android:summary="@string/pref_show_web_fonts_summary"/>
-
- </PreferenceCategory>
-
- <PreferenceCategory android:title="@string/pref_category_media">
-
- <ListPreference android:key="plugin.enable"
- android:title="@string/pref_plugins"
- android:entries="@array/pref_plugins_entries"
- android:entryValues="@array/pref_plugins_values"
- android:persistent="false" />
-
- <SwitchPreference android:key="media.autoplay.enabled"
- android:title="@string/pref_media_autoplay_enabled"
- android:summary="@string/pref_media_autoplay_enabled_summary" />
-
- </PreferenceCategory>
-
- <PreferenceCategory android:title="@string/pref_category_developer_tools">
-
- <SwitchPreference android:key="devtools.remote.usb.enabled"
- android:title="@string/pref_developer_remotedebugging_usb" />
-
- <SwitchPreference android:key="devtools.remote.wifi.enabled"
- android:title="@string/pref_developer_remotedebugging_wifi" />
-
- <org.mozilla.gecko.preferences.AlignRightLinkPreference android:key="android.not_a_preference.remote_debugging.link"
- android:title="@string/pref_learn_more"
- android:persistent="false"
- url="https://developer.mozilla.org/docs/Tools/Remote_Debugging/Debugging_Firefox_for_Android_with_WebIDE" />
- </PreferenceCategory>
-
- <PreferenceCategory
- android:key="android.not_a_preference.category_experimental"
- android:title="@string/pref_category_experimental">
-
- <SwitchPreference android:key="android.not_a_preference.activitystream"
- android:title="@string/pref_activity_stream"
- android:summary="@string/pref_activity_stream_summary"
- android:defaultValue="false" />
-
-
- <SwitchPreference android:key="android.not_a_preference.customtabs"
- android:title="@string/pref_custom_tabs"
- android:summary="@string/pref_custom_tabs_summary"
- android:defaultValue="false" />
-
- </PreferenceCategory>
-
-</PreferenceScreen>
diff --git a/mobile/android/base/resources/xml/preferences_general.xml b/mobile/android/base/resources/xml/preferences_general.xml
deleted file mode 100644
index f50bceb55..000000000
--- a/mobile/android/base/resources/xml/preferences_general.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- Changes should be mirrored to preferences_general_tablet.xml. -->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- android:enabled="false">
-
- <PreferenceScreen android:key="android.not_a_preference.general_home"
- android:title="@string/pref_category_home"
- android:summary="@string/pref_category_home_summary"
- android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
- <extra android:name="resource"
- android:value="preferences_home" />
- </PreferenceScreen>
-
- <PreferenceScreen android:title="@string/pref_category_language"
- android:summary="@string/pref_category_language_summary"
- android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
- <extra android:name="resource"
- android:value="preferences_locale" />
- </PreferenceScreen>
-
- <SwitchPreference android:key="browser.chrome.dynamictoolbar"
- android:title="@string/pref_scroll_title_bar2"
- android:summary="@string/pref_scroll_title_bar_summary" />
-
- <SwitchPreference android:key="android.not_a_preference.tab_queue"
- android:title="@string/pref_tab_queue_title"
- android:summary="@string/pref_tab_queue_summary"
- android:defaultValue="false" />
-
-</PreferenceScreen>
-
diff --git a/mobile/android/base/resources/xml/preferences_general_tablet.xml b/mobile/android/base/resources/xml/preferences_general_tablet.xml
deleted file mode 100644
index f05be9827..000000000
--- a/mobile/android/base/resources/xml/preferences_general_tablet.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!-- Tablet only: The contents under the "General" header for tablets,
- See xml-v11/preferences.xml for single-pane v11+ phone layout.
- Changes to preferences should be mirrored to preferences_general.xml. -->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- android:title="@string/pref_category_general"
- android:enabled="false">
-
- <org.mozilla.gecko.preferences.SyncPreference android:key="android.not_a_preference.sync"
- android:title="@string/pref_sync"
- android:persistent="false" />
-
- <PreferenceScreen android:key="android.not_a_preference.general_home"
- android:title="@string/pref_category_home"
- android:summary="@string/pref_category_home_summary"
- android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
- <extra android:name="resource"
- android:value="preferences_home" />
- </PreferenceScreen>
-
- <PreferenceScreen android:title="@string/pref_category_language"
- android:summary="@string/pref_category_language_summary"
- android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
- <extra android:name="resource"
- android:value="preferences_locale" />
- </PreferenceScreen>
-
- <SwitchPreference android:key="browser.chrome.dynamictoolbar"
- android:title="@string/pref_scroll_title_bar2"
- android:summary="@string/pref_scroll_title_bar_summary" />
-
- <SwitchPreference android:key="android.not_a_preference.tab_queue"
- android:title="@string/pref_tab_queue_title"
- android:summary="@string/pref_tab_queue_summary"
- android:defaultValue="false" />
-
-</PreferenceScreen>
-
diff --git a/mobile/android/base/resources/xml/preferences_home.xml b/mobile/android/base/resources/xml/preferences_home.xml
deleted file mode 100644
index ecc90e093..000000000
--- a/mobile/android/base/resources/xml/preferences_home.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- android:title="@string/pref_category_home"
- android:enabled="false">
-
- <PreferenceCategory android:title="@string/pref_category_home_homepage"
- android:key="android.not_a_preference.category_homepage">
-
- <org.mozilla.gecko.preferences.SetHomepagePreference
- android:key="android.not_a_preference.homepage"
- android:title="@string/home_homepage_title"
- android:persistent="false"
- android:dialogLayout="@layout/preference_set_homepage"/>
-
- </PreferenceCategory>
-
- <org.mozilla.gecko.preferences.PanelsPreferenceCategory
- android:title="@string/pref_category_home_panels"/>
-
- <PreferenceCategory android:title="@string/pref_category_home_add_ons">
-
- <ListPreference android:key="home.sync.updateMode"
- android:title="@string/pref_home_updates"
- android:entries="@array/pref_home_updates_entries"
- android:entryValues="@array/pref_home_updates_values"
- android:persistent="false" />
-
- </PreferenceCategory>
-
-</PreferenceScreen>
diff --git a/mobile/android/base/resources/xml/preferences_locale.xml b/mobile/android/base/resources/xml/preferences_locale.xml
deleted file mode 100644
index 80173c8e0..000000000
--- a/mobile/android/base/resources/xml/preferences_locale.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- android:title="@string/pref_category_language"
- android:enabled="false">
- <PreferenceCategory android:title="@string/pref_browser_locale">
- <!-- No title set here. We set the title to the current locale in
- GeckoPreferences. -->
- <org.mozilla.gecko.preferences.LocaleListPreference
- android:key="locale"
- android:persistent="true"
- android:defaultValue=""
- />
- </PreferenceCategory>
-</PreferenceScreen>
diff --git a/mobile/android/base/resources/xml/preferences_notifications.xml b/mobile/android/base/resources/xml/preferences_notifications.xml
deleted file mode 100644
index b9080d110..000000000
--- a/mobile/android/base/resources/xml/preferences_notifications.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
- <SwitchPreference android:key="android.not_a_preference.notifications.content"
- android:title="@string/pref_content_notifications"
- android:summary="@string/pref_content_notifications_summary"
- android:defaultValue="true" />
- <org.mozilla.gecko.preferences.AlignRightLinkPreference
- android:key="android.not_a_preference.notifications.content.learn_more"
- android:title="@string/pref_learn_more"
- android:persistent="false"
- url="https://support.mozilla.org/kb/notifications-firefox-android?utm_source=inproduct&amp;utm_medium=notifications&amp;utm_campaign=mobileandroid" />
- <SwitchPreference android:key="android.not_a_preference.notifications.whats_new"
- android:title="@string/pref_whats_new_notification"
- android:summary="@string/pref_whats_new_notification_summary"
- android:defaultValue="true" />
-</PreferenceScreen> \ No newline at end of file
diff --git a/mobile/android/base/resources/xml/preferences_privacy.xml b/mobile/android/base/resources/xml/preferences_privacy.xml
deleted file mode 100644
index 7b3215cb2..000000000
--- a/mobile/android/base/resources/xml/preferences_privacy.xml
+++ /dev/null
@@ -1,113 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- android:title="@string/pref_category_privacy_short"
- android:enabled="false">
-
- <SwitchPreference android:key="privacy.donottrackheader.enabled"
- android:title="@string/pref_donottrack_title"
- android:summary="@string/pref_donottrack_summary"
- android:persistent="false" />
-
- <org.mozilla.gecko.preferences.AlignRightLinkPreference
- android:key="android.not_a_preference.donottrackheader.learn_more"
- android:title="@string/pref_learn_more"
- android:persistent="false"
- url="https://www.mozilla.org/firefox/dnt/" />
-
- <CheckBoxPreference android:key="privacy.trackingprotection.pbmode.enabled"
- android:title="@string/pref_tracking_protection_title"
- android:summary="@string/pref_tracking_protection_summary"
- android:persistent="false" />
-
- <ListPreference android:key="privacy.trackingprotection.state"
- android:title="@string/pref_tracking_protection_title"
- android:entries="@array/pref_tracking_protection_entries"
- android:entryValues="@array/pref_tracking_protection_values"
- android:persistent="false" />
-
- <org.mozilla.gecko.preferences.AlignRightLinkPreference
- android:key="android.not_a_preference.trackingprotection.learn_more"
- android:title="@string/pref_learn_more"
- android:persistent="false"
- url="https://support.mozilla.org/kb/firefox-android-tracking-protection" />
-
- <ListPreference android:key="network.cookie.cookieBehavior"
- android:title="@string/pref_cookies_menu"
- android:entries="@array/pref_cookies_entries"
- android:entryValues="@array/pref_cookies_values"
- android:persistent="false" />
-
-
- <!-- This pref is persisted in both Gecko and Java -->
- <org.mozilla.gecko.preferences.ListCheckboxPreference
- android:key="android.not_a_preference.history.clear_on_exit"
- gecko:entries="@array/pref_private_data_entries"
- gecko:entryValues="@array/pref_private_data_values"
- gecko:initialValues="@array/pref_clear_on_exit_defaults"
-
- android:title="@string/pref_clear_on_exit_title"
- android:summary="@string/pref_clear_on_exit_summary2"
-
- android:dialogTitle="@string/pref_clear_on_exit_dialog_title"
- android:positiveButtonText="@string/button_set"/>
-
- <PreferenceCategory android:title="@string/pref_category_logins">
-
- <org.mozilla.gecko.preferences.LinkPreference
- android:key="android.not_a_preference.signon.manage"
- android:title="@string/pref_manage_logins"
- url="about:logins"/>
-
- <CheckBoxPreference
- android:key="signon.rememberSignons"
- android:title="@string/pref_remember_signons"
- android:persistent="false" />
-
- <CheckBoxPreference
- android:key="privacy.masterpassword.enabled"
- android:title="@string/pref_use_master_password"
- android:persistent="false" />
-
- </PreferenceCategory>
-
- <PreferenceCategory android:key="android.not_a_preference.datareporting.preferences"
- android:title="@string/pref_category_datareporting">
-
- <CheckBoxPreference android:key="toolkit.telemetry.enabled"
- android:title="@string/datareporting_telemetry_title"
- android:summary="@string/datareporting_telemetry_summary" />
-
- <CheckBoxPreference android:key="datareporting.crashreporter.submitEnabled"
- android:title="@string/datareporting_crashreporter_title_short"
- android:summary="@string/datareporting_crashreporter_summary"
- android:defaultValue="false" />
-
- <CheckBoxPreference android:key="android.not_a_preference.app.geo.reportdata"
- android:title="@string/datareporting_wifi_title"
- android:summary="@string/datareporting_wifi_geolocation_summary" />
-
- <org.mozilla.gecko.preferences.AlignRightLinkPreference android:key="android.not_a_preference.geo.learn_more"
- android:title="@string/pref_learn_more"
- android:persistent="false"
- url="https://location.services.mozilla.com/" />
-
- <CheckBoxPreference android:key="android.not_a_preference.healthreport.uploadEnabled"
- android:title="@string/datareporting_fhr_title"
- android:summary="@string/datareporting_fhr_summary2"
- android:defaultValue="true" />
-
- <org.mozilla.gecko.preferences.AlignRightLinkPreference android:key="android.not_a_preference.healthreport.link"
- android:title="@string/datareporting_abouthr_title"
- android:persistent="false"
- url="about:healthreport" />
-
- </PreferenceCategory>
-
-</PreferenceScreen>
-
-
diff --git a/mobile/android/base/resources/xml/preferences_search.xml b/mobile/android/base/resources/xml/preferences_search.xml
deleted file mode 100644
index 440167fe5..000000000
--- a/mobile/android/base/resources/xml/preferences_search.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- android:title="@string/pref_category_search"
- android:enabled="false">
-
- <PreferenceCategory android:title="@string/pref_category_add_search_providers">
-
- <org.mozilla.gecko.preferences.ModifiableHintPreference
- android:layout="@layout/preference_search_tip"
- android:enabled="false"
- android:selectable="false"/>
-
- </PreferenceCategory>
-
- <org.mozilla.gecko.preferences.SearchPreferenceCategory
- android:title="@string/pref_category_installed_search_engines"/>
-
- <PreferenceCategory android:title="@string/pref_category_search_restore_defaults">
-
- <Preference android:key="android.not_a_preference.search.restore_defaults"
- android:title="@string/pref_search_restore_defaults_summary" />
-
- </PreferenceCategory>
-
- <CheckBoxPreference android:key="browser.search.suggest.enabled"
- android:title="@string/pref_search_suggestions"
- android:defaultValue="false"
- android:persistent="false" />
-
- <CheckBoxPreference android:key="android.not_a_preference.search.search_history.enabled"
- android:title="@string/pref_history_search_suggestions"
- android:defaultValue="true"
- android:persistent="true" />
-
-</PreferenceScreen>
diff --git a/mobile/android/base/resources/xml/preferences_vendor.xml b/mobile/android/base/resources/xml/preferences_vendor.xml
deleted file mode 100644
index 38eba9d30..000000000
--- a/mobile/android/base/resources/xml/preferences_vendor.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:gecko="http://schemas.android.com/apk/res-auto"
- android:title="@string/pref_category_vendor"
- android:enabled="false">
-
- <org.mozilla.gecko.preferences.LinkPreference android:key="android.not_a_preference.about.link"
- android:title="@string/pref_about_firefox"
- android:persistent="false"
- url="about:" />
-
- <org.mozilla.gecko.preferences.LinkPreference android:key="android.not_a_preference.faq.link"
- android:title="@string/pref_vendor_faqs"
- android:persistent="false"/>
-
- <org.mozilla.gecko.preferences.LinkPreference android:key="android.not_a_preference.feedback.link"
- android:title="@string/pref_vendor_feedback"
- android:persistent="false"/>
-
-</PreferenceScreen>
diff --git a/mobile/android/base/resources/xml/search_preferences.xml b/mobile/android/base/resources/xml/search_preferences.xml
deleted file mode 100644
index 510e3f425..000000000
--- a/mobile/android/base/resources/xml/search_preferences.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
- <Preference
- android:key="search.not_a_preference.clear_history"
- android:title="@string/pref_clearHistory_title"/>
-</PreferenceScreen>
diff --git a/mobile/android/base/resources/xml/search_widget_info.xml b/mobile/android/base/resources/xml/search_widget_info.xml
deleted file mode 100644
index 7fd66f7ed..000000000
--- a/mobile/android/base/resources/xml/search_widget_info.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
- android:minWidth="250dp"
- android:minHeight="40dp"
- android:label="@string/search_widget_name"
- android:widgetCategory="home_screen"
- android:previewImage="@drawable/launcher_widget"
- android:initialLayout="@layout/search_widget"/>
diff --git a/mobile/android/base/resources/xml/searchable.xml b/mobile/android/base/resources/xml/searchable.xml
deleted file mode 100644
index 3cd7333bd..000000000
--- a/mobile/android/base/resources/xml/searchable.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<searchable xmlns:android="http://schemas.android.com/apk/res/android"
- android:label="@string/moz_app_displayname"
- android:searchSuggestAuthority="@string/content_authority_db_browser"
- android:searchSuggestIntentAction="android.intent.action.SEARCH"
- android:searchSettingsDescription="@string/searchable_description"
- android:includeInGlobalSearch="true"/>
diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in
deleted file mode 100644
index 3511a4eca..000000000
--- a/mobile/android/base/strings.xml.in
+++ /dev/null
@@ -1,639 +0,0 @@
-#filter substitution
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
- - License, v. 2.0. If a copy of the MPL was not distributed with this
- - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!DOCTYPE resources [
-#includesubst @BRANDPATH@
-#includesubst @STRINGSPATH@
-#includesubst @SYNCSTRINGSPATH@
-#includesubst @SEARCHSTRINGSPATH@
-
-<!-- C-style format strings. -->
-<!ENTITY formatI "&#037;I">
-<!ENTITY formatS "&#037;s">
-<!ENTITY formatS1 "&#037;1&#036;s">
-<!ENTITY formatS2 "&#037;2&#036;s">
-<!ENTITY formatS3 "&#037;3&#036;s">
-<!ENTITY formatD "&#037;d">
-]>
-
-<resources>
- <string name="moz_app_displayname">@MOZ_APP_DISPLAYNAME@</string>
- <string name="content_authority_db_browser">@ANDROID_PACKAGE_NAME@.db.browser</string>
- <string name="moz_android_shared_fxaccount_type">@ANDROID_PACKAGE_NAME@_fxaccount</string>
- <string name="android_package_name_for_ui">@ANDROID_PACKAGE_NAME@</string>
-
-#include ../search/strings/search_strings.xml.in
-
-#include ../services/strings.xml.in
-
- <string name="firstrun_panel_title_welcome">&firstrun_panel_title_welcome;</string>
-
- <string name="firstrun_urlbar_message">&firstrun_urlbar_message;</string>
- <string name="firstrun_urlbar_subtext">&firstrun_urlbar_subtext;</string>
- <string name="firstrun_bookmarks_title">&firstrun_bookmarks_title;</string>
- <string name="firstrun_bookmarks_message">&firstrun_bookmarks_message;</string>
- <string name="firstrun_bookmarks_subtext">&firstrun_bookmarks_subtext;</string>
- <string name="firstrun_data_title">&firstrun_data_title;</string>
- <string name="firstrun_data_message">&firstrun_data_message;</string>
- <string name="firstrun_data_subtext">&firstrun_data_subtext2;</string>
- <string name="firstrun_sync_title">&firstrun_sync_title;</string>
- <string name="firstrun_sync_message">&firstrun_sync_message;</string>
- <string name="firstrun_sync_subtext">&firstrun_sync_subtext;</string>
- <string name="firstrun_signin_message">&firstrun_signin_message;</string>
- <string name="firstrun_signin_button">&firstrun_signin_button;</string>
- <string name="firstrun_welcome_button_browser">&onboard_start_button_browser;</string>
- <string name="firstrun_button_notnow">&firstrun_button_notnow;</string>
- <string name="firstrun_button_next">&firstrun_button_next;</string>
-
- <string name="firstrun_tabqueue_title">&firstrun_tabqueue_title;</string>
- <string name="firstrun_tabqueue_message_off">&firstrun_tabqueue_message_off;</string>
- <string name="firstrun_tabqueue_subtext_off">&firstrun_tabqueue_subtext_off;</string>
- <string name="firstrun_tabqueue_message_on">&firstrun_tabqueue_message_on;</string>
- <string name="firstrun_tabqueue_subtext_on">&firstrun_tabqueue_subtext_on;</string>
-
- <string name="firstrun_readerview_title">&firstrun_readerview_title;</string>
- <string name="firstrun_readerview_message">&firstrun_readerview_message;</string>
- <string name="firstrun_readerview_subtext">&firstrun_readerview_subtext;</string>
-
- <string name="firstrun_account_title">&firstrun_account_title;</string>
- <string name="firstrun_account_message">&firstrun_account_message;</string>
-
- <string name="firstrun_welcome_restricted">&onboard_start_restricted1;</string>
-
- <string name="bookmarks_title">&bookmarks_title;</string>
- <string name="history_title">&history_title;</string>
-
- <string name="switch_to_tab">&switch_to_tab;</string>
-
- <string name="tab_offline_version">&tab_offline_version;</string>
-
- <string name="crash_reporter_title">&crash_reporter_title;</string>
- <string name="crash_message2">&crash_message2;</string>
- <string name="crash_send_report_message3">&crash_send_report_message3;</string>
- <string name="crash_include_url2">&crash_include_url2;</string>
- <string name="crash_sorry">&crash_sorry;</string>
- <string name="crash_comment">&crash_comment;</string>
- <string name="crash_allow_contact2">&crash_allow_contact2;</string>
- <string name="crash_email">&crash_email;</string>
- <string name="crash_closing_alert">&crash_closing_alert;</string>
- <string name="sending_crash_report">&sending_crash_report;</string>
- <string name="crash_close_label">&crash_close_label;</string>
- <string name="crash_restart_label">&crash_restart_label;</string>
-
- <string name="url_bar_default_text">&url_bar_default_text2;</string>
-
- <!-- https://support.mozilla.org/1/mobile/%VERSION%/%OS%/%LOCALE%/mobile-help -->
- <string name="help_link">https://support.mozilla.org/1/mobile/&formatS1;/&formatS2;/&formatS3;/mobile-help</string>
- <string name="help_menu">&help_menu;</string>
-
- <string name="quit">&quit;</string>
- <string name="bookmark">&bookmark;</string>
- <string name="bookmark_remove">&bookmark_remove;</string>
- <string name="bookmark_added">&bookmark_added;</string>
- <string name="bookmark_already_added">&bookmark_already_added;</string>
- <string name="bookmark_removed">&bookmark_removed;</string>
- <string name="bookmark_updated">&bookmark_updated;</string>
- <string name="bookmark_options">&bookmark_options;</string>
- <string name="screenshot_added_to_bookmarks">&screenshot_added_to_bookmarks;</string>
- <string name="screenshot_folder_label_in_bookmarks">&screenshot_folder_label_in_bookmarks;</string>
- <string name="readinglist_smartfolder_label_in_bookmarks">&readinglist_smartfolder_label_in_bookmarks;</string>
- <string name="bookmark_folder_items">&bookmark_folder_items;</string>
- <string name="bookmark_folder_one_item">&bookmark_folder_one_item;</string>
-
- <string name="reader_saved_offline">&reader_saved_offline;</string>
- <string name="reader_switch_to_bookmarks">&reader_switch_to_bookmarks;</string>
-
- <string name="history_today_section">&history_today_section;</string>
- <string name="history_yesterday_section">&history_yesterday_section;</string>
- <string name="history_week_section">&history_week_section3;</string>
- <string name="history_older_section">&history_older_section3;</string>
-
- <string name="share">&share;</string>
- <string name="share_title">&share_title;</string>
- <string name="share_image_failed">&share_image_failed;</string>
- <string name="save_as_pdf">&save_as_pdf;</string>
- <string name="print">&print;</string>
- <string name="find_in_page">&find_in_page;</string>
- <string name="desktop_mode">&desktop_mode;</string>
- <string name="page">&page;</string>
- <string name="tools">&tools;</string>
-
- <string name="find_text">&find_text;</string>
- <string name="find_prev">&find_prev;</string>
- <string name="find_next">&find_next;</string>
- <string name="find_close">&find_close;</string>
-
- <string name="media_sending_to">&media_sending_to;</string>
- <string name="media_play">&media_play;</string>
- <string name="media_pause">&media_pause;</string>
- <string name="media_stop">&media_stop;</string>
-
- <string name="overlay_share_send_other">&overlay_share_send_other;</string>
- <string name="overlay_share_label">&overlay_share_label;</string>
- <string name="overlay_share_bookmark_btn_label">&overlay_share_bookmark_btn_label;</string>
- <string name="overlay_share_bookmark_btn_label_already">&overlay_share_bookmark_btn_label_already;</string>
- <string name="overlay_share_send_tab_btn_label">&overlay_share_send_tab_btn_label;</string>
- <string name="overlay_share_no_url">&overlay_share_no_url;</string>
- <string name="overlay_share_select_device">&overlay_share_select_device;</string>
- <string name="overlay_no_synced_devices">&overlay_no_synced_devices;</string>
-
- <string name="settings">&settings;</string>
- <string name="settings_title">&settings_title;</string>
- <string name="pref_category_general">&pref_category_general;</string>
- <string name="pref_category_general_summary">&pref_category_general_summary3;</string>
-
- <string name="pref_category_search">&pref_category_search3;</string>
- <string name="pref_category_search_summary">&pref_category_search_summary2;</string>
- <string name="pref_category_accessibility">&pref_category_accessibility;</string>
- <string name="pref_category_accessibility_summary">&pref_category_accessibility_summary2;</string>
- <string name="pref_category_privacy_short">&pref_category_privacy_short;</string>
- <string name="pref_category_privacy_summary">&pref_category_privacy_summary4;</string>
- <string name="pref_category_vendor">&pref_category_vendor2;</string>
- <string name="pref_category_vendor_summary">&pref_category_vendor_summary2;</string>
- <string name="pref_category_datareporting">&pref_category_datareporting;</string>
- <string name="pref_category_logins">&pref_category_logins;</string>
- <string name="pref_category_installed_search_engines">&pref_category_installed_search_engines;</string>
- <string name="pref_category_add_search_providers">&pref_category_add_search_providers;</string>
- <string name="pref_category_search_restore_defaults">&pref_category_search_restore_defaults;</string>
- <string name="pref_search_restore_defaults">&pref_search_restore_defaults;</string>
- <string name="pref_search_restore_defaults_summary">&pref_search_restore_defaults_summary;</string>
- <string name="pref_search_hint">&pref_search_hint2;</string>
-
- <string name="pref_category_language">&pref_category_language;</string>
- <string name="pref_category_language_summary">&pref_category_language_summary;</string>
- <string name="pref_browser_locale">&pref_browser_locale;</string>
- <string name="locale_system_default">&locale_system_default;</string>
-
- <string name="pref_category_advanced">&pref_category_advanced;</string>
- <string name="pref_category_advanced_summary">&pref_category_advanced_summary3;</string>
- <string name="pref_developer_remotedebugging_usb">&pref_developer_remotedebugging_usb;</string>
- <string name="pref_developer_remotedebugging_wifi">&pref_developer_remotedebugging_wifi;</string>
- <string name="pref_developer_remotedebugging_wifi_disabled_summary">&pref_developer_remotedebugging_wifi_disabled_summary;</string>
-
- <string name="pref_category_notifications">&pref_category_notifications;</string>
- <string name="pref_category_notifications_summary">&pref_category_notifications_summary;</string>
- <string name="pref_content_notifications">&pref_content_notifications;</string>
- <string name="pref_content_notifications_summary">&pref_content_notifications_summary2;</string>
-
- <string name="pref_category_home">&pref_category_home;</string>
- <string name="pref_category_home_summary">&pref_category_home_summary;</string>
- <string name="pref_category_home_panels">&pref_category_home_panels;</string>
- <string name="pref_home_updates_wifi">&pref_home_updates_wifi;</string>
- <string name="pref_category_home_add_ons">&pref_category_home_add_ons;</string>
- <string name="pref_home_updates">&pref_home_updates2;</string>
- <string name="pref_home_updates_enabled">&pref_home_updates_enabled;</string>
- <string name="pref_category_home_homepage">&pref_category_home_homepage;</string>
- <string name="home_homepage_title">&home_homepage_title;</string>
- <string name="home_homepage_radio_default">&home_homepage_radio_default;</string>
- <string name="home_homepage_radio_user_address">&home_homepage_radio_user_address;</string>
- <string name="home_homepage_hint_user_address">&home_homepage_hint_user_address;</string>
-
- <string name="pref_header_general">&pref_header_general;</string>
- <string name="pref_header_search">&pref_header_search;</string>
- <string name="pref_header_accessibility">&pref_header_accessibility;</string>
- <string name="pref_header_privacy_short">&pref_header_privacy_short;</string>
- <string name="pref_header_notifications">&pref_header_notifications;</string>
- <string name="pref_header_advanced">&pref_header_advanced;</string>
- <string name="pref_header_vendor">&pref_header_vendor;</string>
-
- <string name="pref_learn_more">&pref_learn_more;</string>
-
- <string name="pref_remember_signons">&pref_remember_signons2;</string>
-
- <string name="pref_manage_logins">&pref_manage_logins;</string>
-
- <string name="pref_cookies_menu">&pref_cookies_menu;</string>
- <string name="pref_cookies_accept_all">&pref_cookies_accept_all;</string>
- <string name="pref_cookies_not_accept_foreign">&pref_cookies_not_accept_foreign;</string>
- <string name="pref_cookies_disabled">&pref_cookies_disabled;</string>
-
- <string name="pref_category_data_saver">&pref_category_data_saver;</string>
- <string name="pref_category_media">&pref_category_media;</string>
- <string name="pref_category_developer_tools">&pref_category_developer_tools;</string>
-
- <string name="pref_tap_to_load_images_title2">&pref_tap_to_load_images_title2;</string>
- <string name="pref_tap_to_load_images_enabled">&pref_tap_to_load_images_enabled;</string>
- <string name="pref_tap_to_load_images_data">&pref_tap_to_load_images_data;</string>
- <string name="pref_tap_to_load_images_disabled2">&pref_tap_to_load_images_disabled2;</string>
-
- <string name="pref_show_web_fonts">&pref_show_web_fonts;</string>
- <string name="pref_show_web_fonts_summary">&pref_show_web_fonts_summary2;</string>
-
- <string name="pref_tracking_protection_title">&pref_tracking_protection_title2;</string>
- <string name="pref_tracking_protection_summary">&pref_tracking_protection_summary3;</string>
- <string name="pref_donottrack_title">&pref_donottrack_title;</string>
- <string name="pref_donottrack_summary">&pref_donottrack_summary;</string>
-
- <string name="pref_tracking_protection_enabled">&pref_tracking_protection_enabled;</string>
- <string name="pref_tracking_protection_enabled_pb">&pref_tracking_protection_enabled_pb;</string>
- <string name="pref_tracking_protection_disabled">&pref_tracking_protection_disabled;</string>
-
- <string name="pref_whats_new_notification">&pref_whats_new_notification;</string>
- <string name="pref_whats_new_notification_summary">&pref_whats_new_notification_summary;</string>
-
- <string name="pref_category_experimental">&pref_category_experimental;</string>
-
- <string name="pref_custom_tabs">&pref_custom_tabs;</string>
- <string name="pref_custom_tabs_summary">&pref_custom_tabs_summary3;</string>
-
- <string name="pref_activity_stream">&pref_activity_stream;</string>
- <string name="pref_activity_stream_summary">&pref_activity_stream_summary;</string>
-
- <string name="pref_char_encoding">&pref_char_encoding;</string>
- <string name="pref_char_encoding_on">&pref_char_encoding_on;</string>
- <string name="pref_char_encoding_off">&pref_char_encoding_off;</string>
- <string name="pref_clear_private_data_now">&pref_clear_private_data2;</string>
- <string name="pref_clear_private_data_now_tablet">&pref_clear_private_data_now_tablet;</string>
- <string name="pref_clear_on_exit_title">&pref_clear_on_exit_title3;</string>
- <string name="pref_clear_on_exit_summary2">&pref_clear_on_exit_summary2;</string>
- <string name="pref_clear_on_exit_dialog_title">&pref_clear_on_exit_dialog_title;</string>
- <string name="pref_plugins">&pref_plugins;</string>
- <string name="pref_plugins_enabled">&pref_plugins_enabled;</string>
- <string name="pref_plugins_tap_to_play">&pref_plugins_tap_to_play2;</string>
- <string name="pref_plugins_disabled">&pref_plugins_disabled;</string>
- <string name="pref_text_size">&pref_text_size;</string>
- <string name="pref_font_size_tiny">&pref_font_size_tiny;</string>
- <string name="pref_font_size_small">&pref_font_size_small;</string>
- <string name="pref_font_size_medium">&pref_font_size_medium;</string>
- <string name="pref_font_size_large">&pref_font_size_large;</string>
- <string name="pref_font_size_xlarge">&pref_font_size_xlarge;</string>
- <string name="pref_font_size_set">&pref_font_size_set;</string>
- <string name="pref_font_size_adjust_char">&pref_font_size_adjust_char;</string>
- <string name="pref_font_size_preview_text">&pref_font_size_preview_text;</string>
- <string name="pref_media_autoplay_enabled">&pref_media_autoplay_enabled;</string>
- <string name="pref_media_autoplay_enabled_summary">&pref_media_autoplay_enabled_summary;</string>
- <string name="pref_zoom_force_enabled">&pref_zoom_force_enabled;</string>
- <string name="pref_zoom_force_enabled_summary">&pref_zoom_force_enabled_summary;</string>
- <string name="pref_voice_input">&pref_voice_input;</string>
- <string name="pref_voice_input_summary">&pref_voice_input_summary2;</string>
- <string name="pref_qrcode_enabled">&pref_qrcode_enabled;</string>
- <string name="pref_qrcode_enabled_summary">&pref_qrcode_enabled_summary2;</string>
- <string name="pref_restore">&pref_restore_tabs;</string>
- <string name="pref_restore_always">&pref_restore_always;</string>
- <string name="pref_restore_quit">&pref_restore_quit;</string>
- <string name="pref_sync">&pref_sync2;</string>
- <string name="pref_sync_summary">&pref_sync_summary2;</string>
- <string name="pref_search_suggestions">&pref_search_suggestions;</string>
- <string name="pref_history_search_suggestions">&pref_history_search_suggestions;</string>
- <string name="pref_private_data_history2">&pref_private_data_history2;</string>
- <string name="pref_private_data_searchHistory">&pref_private_data_searchHistory;</string>
- <string name="pref_private_data_formdata2">&pref_private_data_formdata2;</string>
- <string name="pref_private_data_cookies2">&pref_private_data_cookies2;</string>
- <string name="pref_private_data_passwords">&pref_private_data_passwords2;</string>
- <string name="pref_private_data_cache">&pref_private_data_cache;</string>
- <string name="pref_private_data_offlineApps">&pref_private_data_offlineApps;</string>
- <string name="pref_private_data_siteSettings">&pref_private_data_siteSettings2;</string>
- <string name="pref_private_data_downloadFiles2">&pref_private_data_downloadFiles2;</string>
- <string name="pref_private_data_syncedTabs">&pref_private_data_syncedTabs;</string>
- <string name="pref_import_android">&pref_import_options;</string>
- <string name="pref_import_android_summary">&pref_import_android_summary;</string>
- <string name="pref_update_autodownload">&pref_update_autodownload3;</string>
- <string name="pref_update_autodownload_wifi">&pref_update_autodownload_wifi;</string>
- <string name="pref_update_autodownload_disabled">&pref_update_autodownload_never;</string>
- <string name="pref_update_autodownload_enabled">&pref_update_autodownload_always;</string>
-
- <string name="tracking_protection_prompt_title">&tracking_protection_prompt_title;</string>
- <string name="tracking_protection_prompt_text">&tracking_protection_prompt_text;</string>
- <string name="tracking_protection_prompt_tip_text">&tracking_protection_prompt_tip_text;</string>
- <string name="tracking_protection_prompt_action_button">&tracking_protection_prompt_action_button;</string>
-
- <string name="pref_tab_queue_title">&pref_tab_queue_title3;</string>
- <string name="pref_tab_queue_summary">&pref_tab_queue_summary4;</string>
- <string name="tab_queue_prompt_title">&tab_queue_prompt_title;</string>
- <string name="tab_queue_prompt_text">&tab_queue_prompt_text4;</string>
- <string name="tab_queue_prompt_tip_text">&tab_queue_prompt_tip_text2;</string>
- <string name="tab_queue_prompt_positive_action_button">&tab_queue_prompt_positive_action_button;</string>
- <string name="tab_queue_prompt_negative_action_button">&tab_queue_prompt_negative_action_button;</string>
- <string name="tab_queue_prompt_permit_drawing_over_apps">&tab_queue_prompt_permit_drawing_over_apps;</string>
- <string name="tab_queue_prompt_settings_button">&tab_queue_prompt_settings_button;</string>
- <string name="tab_queue_toast_message">&tab_queue_toast_message3;</string>
- <string name="tab_queue_toast_action">&tab_queue_toast_action;</string>
- <string name="tab_queue_notification_text_singular">&tab_queue_notification_text_singular2;</string>
- <string name="tab_queue_notification_text_plural">&tab_queue_notification_text_plural2;</string>
- <string name="tab_queue_notification_title">&tab_queue_notification_title;</string>
- <string name="tab_queue_notification_settings">&tab_queue_notification_settings;</string>
-
- <string name="content_notification_summary">&content_notification_summary;</string>
- <string name="content_notification_title_plural">&content_notification_title_plural;</string>
- <string name="content_notification_action_settings">&content_notification_action_settings2;</string>
- <string name="content_notification_action_read_now">&content_notification_action_read_now;</string>
- <string name="content_notification_updated_on">&content_notification_updated_on;</string>
-
- <string name="pref_default_browser">&pref_default_browser;</string>
- <string name="pref_default_browser_mozilla_support_tablet">&pref_default_browser_mozilla_support_tablet;</string>
-
- <string name="pref_about_firefox">&pref_about_firefox;</string>
-
- <string name="pref_vendor_faqs">&pref_vendor_faqs;</string>
- <!-- https://support.mozilla.org/1/mobile/%VERSION%/%OS%/%LOCALE%/faq -->
- <string name="faq_link">https://support.mozilla.org/1/mobile/&formatS1;/&formatS2;/&formatS3;/faq</string>
-
- <string name="pref_vendor_feedback">&pref_vendor_feedback;</string>
- <!-- https://input.mozilla.org/feedback/android/%VERSION%/%CHANNEL%/?utm_source=feedback-settings
- This should be kept in sync with the "app.feedbackURL" pref defined in mobile.js -->
- <string name="feedback_link">https://input.mozilla.org/feedback/android/&formatS1;/&formatS2;/?utm_source=feedback-settings</string>
-
- <string name="pref_dialog_set_default">&pref_dialog_set_default;</string>
- <string name="pref_default">&pref_dialog_default;</string>
- <string name="pref_dialog_remove">&pref_dialog_remove;</string>
-
- <string name="pref_search_last_toast">&pref_search_last_toast;</string>
-
- <string name="pref_panels_show">&pref_panels_show;</string>
- <string name="pref_panels_hide">&pref_panels_hide;</string>
- <string name="pref_panels_reorder">&pref_panels_reorder;</string>
- <string name="pref_panels_move_up">&pref_panels_move_up;</string>
- <string name="pref_panels_move_down">&pref_panels_move_down;</string>
-
- <string name="datareporting_notification_title">&datareporting_notification_title;</string>
- <string name="datareporting_notification_action">&datareporting_notification_action;</string>
- <string name="datareporting_notification_summary">&datareporting_notification_summary;</string>
- <string name="datareporting_notification_ticker_text">&datareporting_notification_ticker_text;</string>
-
- <string name="datareporting_telemetry_title">&datareporting_telemetry_title;</string>
- <string name="datareporting_telemetry_summary">&datareporting_telemetry_summary;</string>
- <string name="datareporting_fhr_title">&datareporting_fhr_title;</string>
- <string name="datareporting_fhr_summary2">&datareporting_fhr_summary2;</string>
- <string name="datareporting_abouthr_title">&datareporting_abouthr_title;</string>
- <string name="datareporting_crashreporter_title_short">&datareporting_crashreporter_title_short;</string>
- <string name="datareporting_crashreporter_summary">&datareporting_crashreporter_summary;</string>
- <string name="datareporting_wifi_title">&datareporting_wifi_title2;</string>
- <string name="datareporting_wifi_geolocation_summary">&datareporting_wifi_geolocation_summary4;</string>
-
- <string name="search">&search;</string>
- <string name="reload">&reload;</string>
- <string name="forward">&forward;</string>
- <string name="menu">&menu;</string>
- <string name="back">&back;</string>
- <string name="stop">&stop;</string>
- <string name="site_security">&site_security;</string>
- <string name="close_tab">&close_tab;</string>
- <string name="new_tab_opened">&new_tab_opened;</string>
- <string name="new_private_tab_opened">&new_private_tab_opened;</string>
- <string name="switch_button_message">&switch_button_message;</string>
- <string name="tab_title_prefix_is_playing_audio">&tab_title_prefix_is_playing_audio;</string>
- <string name="one_tab">&one_tab;</string>
- <string name="num_tabs">&num_tabs2;</string>
- <string name="addons">&addons;</string>
- <string name="logins">&logins;</string>
- <string name="downloads">&downloads;</string>
- <string name="char_encoding">&char_encoding;</string>
- <string name="new_tab">&new_tab;</string>
- <string name="new_private_tab">&new_private_tab;</string>
- <string name="close_all_tabs">&close_all_tabs;</string>
- <string name="close_private_tabs">&close_private_tabs;</string>
- <string name="tabs_normal">&tabs_normal;</string>
- <string name="tabs_private">&tabs_private;</string>
- <string name="edit_mode_cancel">&edit_mode_cancel;</string>
-
- <string name="site_settings_title">&site_settings_title3;</string>
- <string name="site_settings_cancel">&site_settings_cancel;</string>
- <string name="site_settings_clear">&site_settings_clear;</string>
-
- <string name="page_action_dropmarker_description">&page_action_dropmarker_description;</string>
-
- <string name="contextmenu_open_new_tab">&contextmenu_open_new_tab;</string>
- <string name="contextmenu_open_private_tab">&contextmenu_open_private_tab;</string>
- <string name="contextmenu_remove">&contextmenu_remove;</string>
- <string name="contextmenu_add_to_launcher">&contextmenu_add_to_launcher;</string>
- <string name="contextmenu_share">&contextmenu_share;</string>
- <string name="contextmenu_pasteandgo">&contextmenu_pasteandgo;</string>
- <string name="contextmenu_paste">&contextmenu_paste;</string>
- <string name="contextmenu_copyurl">&contextmenu_copyurl;</string>
- <string name="contextmenu_edit_bookmark">&contextmenu_edit_bookmark;</string>
- <string name="contextmenu_subscribe">&contextmenu_subscribe;</string>
- <string name="contextmenu_site_settings">&contextmenu_site_settings;</string>
- <string name="contextmenu_top_sites_edit">&contextmenu_top_sites_edit;</string>
- <string name="contextmenu_top_sites_pin">&contextmenu_top_sites_pin;</string>
- <string name="contextmenu_top_sites_unpin">&contextmenu_top_sites_unpin;</string>
- <string name="contextmenu_add_search_engine">&contextmenu_add_search_engine;</string>
-
- <string name="doorhanger_login_no_username">&doorhanger_login_no_username;</string>
- <string name="doorhanger_login_edit_title">&doorhanger_login_edit_title;</string>
- <string name="doorhanger_login_edit_username_hint">&doorhanger_login_edit_username_hint;</string>
- <string name="doorhanger_login_edit_password_hint">&doorhanger_login_edit_password_hint;</string>
- <string name="doorhanger_login_edit_toggle">&doorhanger_login_edit_toggle;</string>
- <string name="doorhanger_login_edit_toast_error">&doorhanger_login_edit_toast_error;</string>
- <string name="doorhanger_login_select_message">&doorhanger_login_select_message;</string>
- <string name="doorhanger_login_select_toast_copy">&doorhanger_login_select_toast_copy;</string>
- <string name="doorhanger_login_select_toast_copy_error">&doorhanger_login_select_toast_copy_error;</string>
- <string name="doorhanger_login_select_action_text">&doorhanger_login_select_action_text;</string>
- <string name="doorhanger_login_select_title">&doorhanger_login_select_title;</string>
-
- <string name="pref_magnifying_glass_enabled">&pref_magnifying_glass_enabled;</string>
- <string name="pref_magnifying_glass_enabled_summary">&pref_magnifying_glass_enabled_summary2;</string>
-
- <string name="pref_scroll_title_bar2">&pref_scroll_title_bar2;</string>
- <string name="pref_scroll_title_bar_summary">&pref_scroll_title_bar_summary2;</string>
-
- <string name="page_removed">&page_removed;</string>
-
- <string name="bookmark_edit_title">&bookmark_edit_title;</string>
- <string name="bookmark_edit_name">&bookmark_edit_name;</string>
- <string name="bookmark_edit_location">&bookmark_edit_location;</string>
- <string name="bookmark_edit_keyword">&bookmark_edit_keyword;</string>
-
- <string name="pref_use_master_password">&pref_use_master_password;</string>
- <string name="masterpassword_create_title">&masterpassword_create_title;</string>
- <string name="masterpassword_remove_title">&masterpassword_remove_title;</string>
- <string name="masterpassword_password">&masterpassword_password;</string>
- <string name="masterpassword_confirm">&masterpassword_confirm;</string>
-
- <string name="button_ok">&button_ok;</string>
- <string name="button_cancel">&button_cancel;</string>
- <string name="button_clear_data">&button_clear_data;</string>
- <string name="button_set">&button_set;</string>
- <string name="button_clear">&button_clear;</string>
- <string name="button_yes">&button_yes;</string>
- <string name="button_no">&button_no;</string>
- <string name="button_copy">&button_copy;</string>
-
- <string name="home_title">&home_title;</string>
- <string name="home_top_sites_title">&home_top_sites_title;</string>
- <string name="home_top_sites_add">&home_top_sites_add;</string>
- <string name="home_history_title">&home_history_title;</string>
- <string name="home_synced_devices_smartfolder">&home_synced_devices_smartfolder;</string>
- <string name="home_synced_devices_number">&home_synced_devices_number;</string>
- <string name="home_synced_devices_one">&home_synced_devices_one;</string>
- <string name="home_history_back_to">&home_history_back_to2;</string>
- <string name="home_clear_history_button">&home_clear_history_button;</string>
- <string name="home_clear_history_confirm">&home_clear_history_confirm;</string>
- <string name="home_bookmarks_empty">&home_bookmarks_empty;</string>
- <string name="home_closed_tabs_title2">&home_closed_tabs_title2;</string>
- <string name="home_last_tabs_empty">&home_last_tabs_empty;</string>
- <string name="home_restore_all">&home_restore_all;</string>
- <string name="home_closed_tabs_number">&home_closed_tabs_number;</string>
- <string name="home_closed_tabs_one">&home_closed_tabs_one;</string>
- <string name="home_most_recent_empty">&home_most_recent_empty;</string>
- <string name="home_most_recent_emptyhint">&home_most_recent_emptyhint2;</string>
- <string name="home_default_empty">&home_default_empty;</string>
- <string name="home_move_back_to_filter">&home_move_back_to_filter;</string>
- <string name="home_remote_tabs_many_hidden_devices">&home_remote_tabs_many_hidden_devices;</string>
- <string name="home_remote_tabs_hidden_devices_title">&home_remote_tabs_hidden_devices_title;</string>
- <string name="home_remote_tabs_unhide_selected_devices">&home_remote_tabs_unhide_selected_devices;</string>
- <string name="pin_site_dialog_hint">&pin_site_dialog_hint;</string>
-
- <string name="remote_tabs_never_synced">&remote_tabs_never_synced;</string>
-
- <string name="filepicker_title">&filepicker_title;</string>
- <string name="filepicker_audio_title">&filepicker_audio_title;</string>
- <string name="filepicker_image_title">&filepicker_image_title;</string>
- <string name="filepicker_video_title">&filepicker_video_title;</string>
-
- <!-- Default bookmarks. We used to use bookmark titles shared with XUL from mobile's
- profile/bookmarks.inc (see bug 964946). Don't expose the URLs to L10N. -->
- <string name="bookmarkdefaults_title_aboutfirefox">&bookmarks_about_browser;</string>
- <string name="bookmarkdefaults_url_aboutfirefox">about:firefox</string>
-
- <!-- Icon is automatically generated from R.drawable.bookmarkdefaults_favicon_addons -->
- <string name="bookmarkdefaults_title_addons">&bookmarks_addons;</string>
- <string name="bookmarkdefaults_url_addons">https://addons.mozilla.org/android?utm_source=inproduct&amp;utm_medium=default-bookmarks&amp;utm_campaign=mobileandroid</string>
-
- <!-- Icon is automatically generated from R.drawable.bookmarkdefaults_favicon_support -->
- <string name="bookmarkdefaults_title_support">&bookmarks_support;</string>
- <string name="bookmarkdefaults_url_support">https://support.mozilla.org/products/mobile?utm_source=inproduct&amp;utm_medium=default-bookmarks&amp;utm_campaign=mobileandroid</string>
-
- <string name="bookmarkdefaults_title_restricted_webmaker">&bookmarks_restricted_webmaker;</string>
- <string name="bookmarkdefaults_url_restricted_webmaker">https://webmaker.org/</string>
-
- <string name="bookmarkdefaults_title_restricted_support">&bookmarks_restricted_support2;</string>
- <string name="bookmarkdefaults_url_restricted_support">https://support.mozilla.org/kb/controlledaccess?utm_source=inproduct&amp;utm_medium=default-bookmarks&amp;utm_campaign=mobileandroid</string>
-
- <!-- Site identity popup -->
- <string name="identity_connection_secure">&identity_connection_secure;</string>
- <string name="identity_connection_insecure">&identity_connection_insecure;</string>
- <string name="identity_connection_chromeui">&identity_connection_chromeui;</string>
-
- <string name="mixed_content_blocked_all">&mixed_content_blocked_all1;</string>
- <string name="mixed_content_blocked_some">&mixed_content_blocked_some1;</string>
- <string name="mixed_content_display_loaded">&mixed_content_display_loaded1;</string>
- <string name="mixed_content_protection_disabled">&mixed_content_protection_disabled1;</string>
-
- <string name="doorhanger_tracking_title">&doorhanger_tracking_title2;</string>
- <string name="doorhanger_tracking_state_enabled">&doorhanger_tracking_state_enabled;</string>
- <string name="doorhanger_tracking_state_disabled">&doorhanger_tracking_state_disabled;</string>
- <string name="doorhanger_tracking_message_enabled">&doorhanger_tracking_message_enabled1;</string>
- <string name="doorhanger_tracking_message_disabled">&doorhanger_tracking_message_disabled2;</string>
-
- <string name="learn_more">&learn_more;</string>
- <string name="enable_protection">&enable_protection;</string>
- <string name="disable_protection">&disable_protection;</string>
-
- <!-- Clear private data -->
- <string name="private_data_success">&private_data_success;</string>
- <string name="private_data_fail">&private_data_fail;</string>
-
- <!-- Bookmark import/export -->
- <string name="bookmarkhistory_button_import">&bookmarkhistory_button_import;</string>
- <string name="bookmarkhistory_import_both">&bookmarkhistory_import_both;</string>
- <string name="bookmarkhistory_import_bookmarks">&bookmarkhistory_import_bookmarks;</string>
- <string name="bookmarkhistory_import_history">&bookmarkhistory_import_history;</string>
- <string name="bookmarkhistory_import_wait">&bookmarkhistory_import_wait;</string>
-
- <string name="searchable_description">&searchable_description;</string>
-
- <!-- Updater notifications -->
- <string name="updater_start_title">&updater_start_title2;</string>
- <string name="updater_start_select">&updater_start_select2;</string>
-
- <string name="updater_downloading_title">&updater_downloading_title2;</string>
- <string name="updater_downloading_title_failed">&updater_downloading_title_failed2;</string>
- <string name="updater_downloading_select">&updater_downloading_select2;</string>
- <string name="updater_downloading_retry">&updater_downloading_retry2;</string>
-
- <string name="updater_apply_title">&updater_apply_title2;</string>
- <string name="updater_apply_select">&updater_apply_select2;</string>
-
- <string name="updater_permission_title">&brandShortName;</string>
- <string name="updater_permission_text">&updater_permission_text;</string>
- <string name="updater_permission_allow">&updater_permission_allow;</string>
-
- <!-- Awesomescreen screen -->
- <string name="suggestions_prompt">&suggestions_prompt3;</string>
- <string name="search_bar_item_desc">&search_bar_item_desc;</string>
-
- <string name="suggestion_for_engine">&suggestion_for_engine;</string>
-
- <!-- Set Image Notifications -->
- <string name="set_image_fail">&set_image_fail;</string>
- <string name="set_image_path_fail">&set_image_path_fail;</string>
- <string name="set_image_chooser_title">&set_image_chooser_title;</string>
-
- <!-- Guest mode -->
- <string name="new_guest_session">&new_guest_session;</string>
- <string name="exit_guest_session">&exit_guest_session;</string>
- <string name="guest_session_dialog_continue">&guest_session_dialog_continue;</string>
- <string name="guest_session_dialog_cancel">&guest_session_dialog_cancel;</string>
- <string name="new_guest_session_title">&new_guest_session_title;</string>
- <string name="new_guest_session_text">&new_guest_session_text2;</string>
- <string name="guest_browsing_notification_title">&guest_browsing_notification_title;</string>
- <string name="guest_browsing_notification_text">&guest_browsing_notification_text;</string>
-
- <string name="exit_guest_session_title">&exit_guest_session_title;</string>
- <string name="exit_guest_session_text">&exit_guest_session_text;</string>
-
- <string name="actionbar_menu">&actionbar_menu;</string>
- <string name="actionbar_done">&actionbar_done;</string>
-
- <!-- Voice search from the Awesome Bar -->
- <string name="voicesearch_prompt">&voicesearch_prompt;</string>
-
- <!-- Restrictable features -->
- <string name="restrictable_feature_addons_installation">&restrictable_feature_addons_installation;</string>
- <string name="restrictable_feature_addons_installation_description">&restrictable_feature_addons_installation_description;</string>
- <string name="restrictable_feature_private_browsing">&restrictable_feature_private_browsing;</string>
- <string name="restrictable_feature_private_browsing_description">&restrictable_feature_private_browsing_description;</string>
- <string name="restrictable_feature_clear_history">&restrictable_feature_clear_history;</string>
- <string name="restrictable_feature_clear_history_description">&restrictable_feature_clear_history_description;</string>
- <string name="restrictable_feature_advanced_settings">&restrictable_feature_advanced_settings;</string>
- <string name="restrictable_feature_advanced_settings_description">&restrictable_feature_advanced_settings_description;</string>
- <string name="restrictable_feature_camera_microphone">&restrictable_feature_camera_microphone;</string>
- <string name="restrictable_feature_camera_microphone_description">&restrictable_feature_camera_microphone_description;</string>
- <string name="restrictable_feature_block_list">&restrictable_feature_block_list;</string>
- <string name="restrictable_feature_block_list_description">&restrictable_feature_block_list_description;</string>
-
- <!-- Miscellaneous -->
- <string name="ellipsis">&ellipsis;</string>
-
- <string name="colon">&colon;</string>
-
- <string name="percent">&percent;</string>
-
- <string name="remote_tabs_last_synced">&remote_tabs_last_synced;</string>
-
- <string name="intent_uri_private_browsing_prompt">&intent_uri_private_browsing_prompt;</string>
- <string name="intent_uri_private_browsing_multiple_match_title">&intent_uri_private_browsing_multiple_match_title;</string>
-
- <string name="devtools_auth_scan_header">&devtools_auth_scan_header;</string>
-
- <string name="unsupported_sdk_version">&unsupported_sdk_version;</string>
- <string name="eol_notification_title">&eol_notification_title2;</string>
- <string name="eol_notification_summary">&eol_notification_summary;</string>
- <!-- https://support.mozilla.org/1/mobile/%VERSION%/%OS%/%LOCALE%/honeycomb -->
- <string name="eol_notification_url">https://support.mozilla.org/1/mobile/&formatS1;/&formatS2;/&formatS3;/unsupported-version</string>
-
- <string name="whatsnew_notification_title">&whatsnew_notification_title;</string>
- <string name="whatsnew_notification_summary">&whatsnew_notification_summary;</string>
- <!-- https://support.mozilla.org/1/mobile/%VERSION%/%OS%/%LOCALE%/new-android -->
- <string name="whatsnew_notification_url">https://support.mozilla.org/1/mobile/&formatS1;/&formatS2;/&formatS3;/new-android</string>
-
- <string name="promotion_add_to_homescreen">&promotion_add_to_homescreen;</string>
-
- <string name="helper_first_offline_bookmark_title">&helper_first_offline_bookmark_title;</string>
- <string name="helper_first_offline_bookmark_message">&helper_first_offline_bookmark_message;</string>
- <string name="helper_first_offline_bookmark_button">&helper_first_offline_bookmark_button;</string>
-
- <string name="helper_triple_readerview_open_title">&helper_triple_readerview_open_title;</string>
- <string name="helper_triple_readerview_open_message">&helper_triple_readerview_open_message;</string>
- <string name="helper_triple_readerview_open_button">&helper_triple_readerview_open_button;</string>
-
- <string name="activity_stream_topsites">&activity_stream_topsites;</string>
- <string name="activity_stream_highlights">&activity_stream_highlights;</string>
- <string name="activity_stream_highlight_label_bookmarked">&activity_stream_highlight_label_bookmarked;</string>
- <string name="activity_stream_highlight_label_visited">&activity_stream_highlight_label_visited;</string>
- <string name="activity_stream_dismiss">&activity_stream_dismiss;</string>
- <string name="activity_stream_delete_history">&activity_stream_delete_history;</string>
-</resources>