EARHORNMUSIC UPnP · Android TV · Phones · Android Auto · GNU/Linux
Release 0.5.0

AndroidManifest.xml

186 lines · 9.7 KB

Changed in: 0.5.0

1<?xml version="1.0" encoding="utf-8"?>
2<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:tools="http://schemas.android.com/tools">
4
5 <!-- UPnP/DLNA Discovery Permissions -->
6 <uses-permission android:name="android.permission.INTERNET" />
7 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
8 <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
9 <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
10 <!-- WAKE_LOCK is required by jUPnP's AndroidRouter, which acquires a
11 WifiLock (NOT a wake lock — confusingly named) to keep the Wi-Fi
12 radio in high-performance mode during UPnP discovery. WifiLock.acquire()
13 is gated on this permission at the system level. Without it, the very
14 first call into UpnpServiceImpl.startup() throws SecurityException and
15 the entire UPnP stack fails to initialize — silently, because the
16 binder catches it. The phone shows zero servers and there is no
17 user-visible error. Symptom indistinguishable from "no servers on
18 network." Discovered via logcat: AndroidRouter.setWifiLock →
19 WifiLock.acquire → SecurityException. -->
20 <uses-permission android:name="android.permission.WAKE_LOCK" />
21
22 <!-- Playback Service Permission -->
23 <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
24 <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
25
26 <!-- Local media library — read audio files from device storage.
27 READ_MEDIA_AUDIO is the granular permission on API 33+;
28 READ_EXTERNAL_STORAGE is the legacy fallback for API 29-32. -->
29 <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
30 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
31 android:maxSdkVersion="32" />
32
33 <!-- Voice search delegates to the system's RecognizerIntent (the same
34 path the system keyboard's mic button uses). Audio capture happens
35 in the system's recognizer activity, not in our process — so we
36 don't declare RECORD_AUDIO. The <queries> block below is kept so
37 PackageManager.queryIntentActivities can resolve the recognizer
38 activity from our process; without it, on API 30+ launching the
39 intent silently no-ops. -->
40
41 <queries>
42 <intent>
43 <action android:name="android.speech.RecognitionService" />
44 </intent>
45 <intent>
46 <action android:name="android.speech.action.RECOGNIZE_SPEECH" />
47 </intent>
48 <!-- Kiosk hand-back: resolve the launch intent of the `returnTo`
49 package named by an OBSERVE launch (FaveatHaus on the wall
50 screens). API 30+ package-visibility filtering makes
51 getLaunchIntentForPackage return null for unqueried packages —
52 these two filters grant visibility of launcher-able apps on
53 both touch and leanback form factors. -->
54 <intent>
55 <action android:name="android.intent.action.MAIN" />
56 <category android:name="android.intent.category.LAUNCHER" />
57 </intent>
58 <intent>
59 <action android:name="android.intent.action.MAIN" />
60 <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
61 </intent>
62 </queries>
63
64 <!-- TV: declares leanback support. required=false so the app can also
65 install on phones for Android Auto. The LEANBACK_LAUNCHER category
66 in the activity's intent-filter is what makes it appear on TV. -->
67 <uses-feature android:name="android.software.leanback" android:required="false" />
68 <uses-feature android:name="android.hardware.touchscreen" android:required="false" />
69
70 <!-- Decision, 2026-10-29: backup rules (D9). Auto Backup used to copy
71 everything, including the settings DataStore that holds this
72 device's identity (renderer UDN, kiosk return package, observed Cast
73 device, last-known hosts), so a restored phone impersonated the old
74 one. backup_rules / data_extraction_rules exclude only that file. -->
75 <application
76 android:name=".EarhornApp"
77 android:allowBackup="true"
78 android:fullBackupContent="@xml/backup_rules"
79 android:dataExtractionRules="@xml/data_extraction_rules"
80 android:icon="@mipmap/ic_launcher"
81 android:roundIcon="@mipmap/ic_launcher_round"
82 android:label="@string/app_name"
83 android:logo="@mipmap/ic_launcher"
84 android:banner="@drawable/tv_banner"
85 android:theme="@style/Theme.Earhorn"
86 android:usesCleartextTraffic="true"
87 tools:targetApi="35">
88
89 <!-- launchMode=singleTask: required for the voice-cast auto-observe
90 entry point. FaveatHaus fires OBSERVE while Earhorn is in the
91 BACKGROUND; with the default launchMode that would stack a second
92 MainActivity instance (and onNewIntent would never fire).
93 singleTask guarantees exactly one instance, brings its task
94 forward, and delivers repeat launches through onNewIntent — which
95 is also what the notification deep-links already assumed. -->
96 <activity
97 android:name=".MainActivity"
98 android:exported="true"
99 android:launchMode="singleTask"
100 android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize"
101 android:theme="@style/Theme.Earhorn">
102 <intent-filter>
103 <action android:name="android.intent.action.MAIN" />
104 <category android:name="android.intent.category.LAUNCHER" />
105 <!-- This line is critical for Android TV visibility -->
106 <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
107 </intent-filter>
108
109 <!-- E1 primary: exported observe entry point for the voice-cast
110 pipeline (musicnow > FaveatHaus > us). String extra "group"
111 carries the Cast group friendly name exactly as Google Home
112 spells it (UTF-8; spaces and umlauts intact).
113 Verification command: see docs/voice-cast-observe.md
114 (adb "am start" with the extra; the literal flag can't be
115 quoted here because XML comments forbid double hyphens).
116 DEFAULT category is mandatory: implicit startActivity (and
117 adb "am start" by action) only resolves filters carrying it. -->
118 <intent-filter>
119 <action android:name="com.faveat.earhorn.action.OBSERVE" />
120 <category android:name="android.intent.category.DEFAULT" />
121 </intent-filter>
122
123 <!-- E1 secondary: earhorn://observe?group=<url-encoded name> -->
124 <intent-filter>
125 <action android:name="android.intent.action.VIEW" />
126 <category android:name="android.intent.category.DEFAULT" />
127 <category android:name="android.intent.category.BROWSABLE" />
128 <data android:scheme="earhorn" android:host="observe" />
129 </intent-filter>
130 </activity>
131
132 <!-- jUPnP UPnP Service (successor to the abandoned Cling project) -->
133 <service
134 android:name="org.jupnp.android.AndroidUpnpServiceImpl"
135 android:enabled="true"
136 android:exported="false" />
137
138 <!-- Media3 Playback + Browse Service. Extends MediaLibraryService so
139 both the TV launcher (MediaSession) and Android Auto (browse tree)
140 work from the same service. exported=true is required for Auto to
141 bind from the car head unit process. -->
142 <service
143 android:name=".playback.PlaybackService"
144 android:enabled="true"
145 android:exported="true"
146 android:foregroundServiceType="mediaPlayback">
147 <intent-filter>
148 <action android:name="androidx.media3.session.MediaLibraryService" />
149 <action android:name="android.media.browse.MediaBrowserService" />
150 </intent-filter>
151 </service>
152
153 <!-- Decision, 2026-10-29: Media3's media-button receiver, so a
154 Bluetooth / headset Play reaches PlaybackService (and playback
155 resumption) even when the app isn't running (D8). Subclassed so
156 it only starts the service when there is a queue to resume —
157 otherwise Android kills the app for a foreground service that
158 never posted its notification. -->
159 <receiver
160 android:name=".playback.EarhornMediaButtonReceiver"
161 android:exported="true">
162 <intent-filter>
163 <action android:name="android.intent.action.MEDIA_BUTTON" />
164 </intent-filter>
165 </receiver>
166
167 <!-- Android Auto: declares this app provides media playback. -->
168 <meta-data
169 android:name="com.google.android.gms.car.application"
170 android:resource="@xml/automotive_app_desc" />
171
172
173 <service
174 android:name=".cartemplate.EarhornCarAppService"
175 android:exported="true">
176 <intent-filter>
177 <action android:name="androidx.car.app.CarAppService" />
178 <category android:name="androidx.car.app.category.MEDIA" />
179 </intent-filter>
180 <meta-data
181 android:name="androidx.car.app.minCarApiLevel"
182 android:value="7" />
183 </service>
184
185 </application>
186</manifest>