Snap for 8564071 from 958a32cdadd093930bda607e80f1bd809b97019a to mainline-wifi-release
Change-Id: I300407825294a6f8287fb911c3e33e91025aeffd
diff --git a/Android.bp b/Android.bp
index 770a6b1..099c174 100644
--- a/Android.bp
+++ b/Android.bp
@@ -14,6 +14,10 @@
// limitations under the License.
//
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
android_app {
name: "CarRadioApp",
@@ -27,13 +31,13 @@
platform_apis: true,
- required: ["privapp_whitelist_com.android.car.radio"],
+ required: ["allowed_privapp_com.android.car.radio"],
certificate: "platform",
privileged: true,
- libs: ["android.car"],
+ libs: ["android.car-system-stubs"],
static_libs: [
"androidx.lifecycle_lifecycle-livedata",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index eccbf94..cdae76d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -52,13 +52,13 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LAUNCHER_APP" />
- <category android:name="android.intent.category.APP_MUSIC" />
</intent-filter>
</activity>
<!-- RadioAppService is set to be executed in a separate process to make sure the reference
app supports it. It's less optimal though, so it might be desirable to turn it off in
production app. -->
+ <!-- Keep in sync with RadioAppService#getMediaSourceComp. -->
<service
android:name=".service.RadioAppService"
android:exported="true"
diff --git a/src/com/android/car/radio/BrowseFragment.java b/src/com/android/car/radio/BrowseFragment.java
index f81fc1d..3d53620 100644
--- a/src/com/android/car/radio/BrowseFragment.java
+++ b/src/com/android/car/radio/BrowseFragment.java
@@ -26,23 +26,25 @@
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
import com.android.car.broadcastradio.support.Program;
import com.android.car.radio.storage.RadioStorage;
+import com.android.car.radio.util.Log;
import com.android.car.ui.baselayout.Insets;
import com.android.car.ui.baselayout.InsetsChangedListener;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
/**
* Fragment that shows all browseable radio stations from background scan
*/
public class BrowseFragment extends Fragment implements InsetsChangedListener {
+ private static final String TAG = "BcRadioApp.BrwFrg";
private RadioController mRadioController;
private BrowseAdapter mBrowseAdapter;
private RadioStorage mRadioStorage;
private View mRootView;
- private RecyclerView mBrowseList;
+ private CarUiRecyclerView mBrowseList;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
@@ -75,8 +77,13 @@
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
- if (isVisibleToUser) {
+
+ if (!isVisibleToUser) return;
+
+ try {
mRadioController.setSkipMode(SkipMode.BROWSE);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Can't set skip mode", e);
}
}
diff --git a/src/com/android/car/radio/DisplayController.java b/src/com/android/car/radio/DisplayController.java
index 14b131e..b2a09b5 100644
--- a/src/com/android/car/radio/DisplayController.java
+++ b/src/com/android/car/radio/DisplayController.java
@@ -50,7 +50,6 @@
private final Context mContext;
- private final View mToolbar;
private final View mViewpager;
private final TextView mStatusMessage;
private final TextView mChannel;
@@ -85,7 +84,6 @@
@NonNull RadioController radioController) {
mContext = Objects.requireNonNull(activity);
- mToolbar = activity.findViewById(R.id.toolbar);
mViewpager = activity.findViewById(R.id.viewpager);
mStatusMessage = activity.findViewById(R.id.status_message);
mChannel = activity.findViewById(R.id.station_channel);
@@ -144,9 +142,6 @@
if (mFavoriteButton != null) {
mFavoriteButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
}
- if (mToolbar != null) {
- mToolbar.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
- }
if (mViewpager != null) {
mViewpager.setVisibility(enabled ? View.VISIBLE : View.GONE);
}
diff --git a/src/com/android/car/radio/FavoritesFragment.java b/src/com/android/car/radio/FavoritesFragment.java
index 218a318..f4c2f43 100644
--- a/src/com/android/car/radio/FavoritesFragment.java
+++ b/src/com/android/car/radio/FavoritesFragment.java
@@ -26,22 +26,24 @@
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
import com.android.car.broadcastradio.support.Program;
import com.android.car.radio.storage.RadioStorage;
+import com.android.car.radio.util.Log;
import com.android.car.ui.baselayout.Insets;
import com.android.car.ui.baselayout.InsetsChangedListener;
+import com.android.car.ui.recyclerview.CarUiRecyclerView;
/**
* Fragment that shows a list of all the current favorite radio stations
*/
public class FavoritesFragment extends Fragment implements InsetsChangedListener {
+ private static final String TAG = "BcRadioApp.FavFrg";
private RadioController mRadioController;
private BrowseAdapter mBrowseAdapter;
private RadioStorage mRadioStorage;
- private RecyclerView mBrowseList;
+ private CarUiRecyclerView mBrowseList;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
@@ -72,11 +74,17 @@
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
+
if (!isVisibleToUser && mBrowseAdapter != null) {
mBrowseAdapter.removeFormerFavorites();
}
+
if (isVisibleToUser) {
- mRadioController.setSkipMode(SkipMode.FAVORITES);
+ try {
+ mRadioController.setSkipMode(SkipMode.FAVORITES);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Can't set skip mode", e);
+ }
}
}
diff --git a/src/com/android/car/radio/ManualTunerFragment.java b/src/com/android/car/radio/ManualTunerFragment.java
index 9fe6bcc..b668531 100644
--- a/src/com/android/car/radio/ManualTunerFragment.java
+++ b/src/com/android/car/radio/ManualTunerFragment.java
@@ -27,6 +27,7 @@
import androidx.fragment.app.Fragment;
import com.android.car.radio.bands.ProgramType;
+import com.android.car.radio.util.Log;
import com.android.car.ui.baselayout.Insets;
import com.android.car.ui.baselayout.InsetsChangedListener;
@@ -34,6 +35,7 @@
* Fragment that allows tuning to a specific frequency using a keypad
*/
public class ManualTunerFragment extends Fragment implements InsetsChangedListener {
+ private static final String TAG = "BcRadioApp.TunFrg";
private ManualTunerController mController;
private RadioController mRadioController;
@@ -53,11 +55,16 @@
super.setUserVisibleHint(isVisibleToUser);
if (!isVisibleToUser) return;
+
ProgramInfo current = mRadioController.getCurrentProgram().getValue();
- if (current == null) return;
- mController.switchProgramType(ProgramType.fromSelector(current.getSelector()));
- if (isVisibleToUser) {
+ if (current != null) {
+ mController.switchProgramType(ProgramType.fromSelector(current.getSelector()));
+ }
+
+ try {
mRadioController.setSkipMode(SkipMode.TUNE);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Can't set skip mode", e);
}
}
diff --git a/src/com/android/car/radio/RadioActivity.java b/src/com/android/car/radio/RadioActivity.java
index 73582b5..da8eb30 100644
--- a/src/com/android/car/radio/RadioActivity.java
+++ b/src/com/android/car/radio/RadioActivity.java
@@ -16,12 +16,9 @@
package com.android.car.radio;
-import static android.car.media.CarMediaManager.MEDIA_SOURCE_MODE_BROWSE;
-
import static com.android.car.ui.core.CarUi.requireToolbar;
import static com.android.car.ui.toolbar.Toolbar.State.HOME;
-import android.car.Car;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
@@ -32,8 +29,9 @@
import androidx.viewpager.widget.ViewPager;
import com.android.car.media.common.source.MediaSource;
-import com.android.car.media.common.source.MediaSourceViewModel;
+import com.android.car.media.common.source.MediaTrampolineHelper;
import com.android.car.radio.bands.ProgramType;
+import com.android.car.radio.service.RadioAppService;
import com.android.car.radio.util.Log;
import com.android.car.ui.baselayout.Insets;
import com.android.car.ui.baselayout.InsetsChangedListener;
@@ -67,6 +65,9 @@
private ToolbarController mToolbar;
private RadioPagerAdapter mRadioPagerAdapter;
+ private boolean mUseSourceLogoForAppSelector;
+ private MediaTrampolineHelper mMediaTrampoline;
+
@Override
public void onCarUiInsetsChanged(Insets insets) {
// This InsetsChangedListener is just a marker that we will later handle
@@ -82,6 +83,8 @@
setContentView(R.layout.radio_activity);
+ mMediaTrampoline = new MediaTrampolineHelper(this);
+
mRadioController = new RadioController(this);
mRadioController.getCurrentProgram().observe(this, info -> {
ProgramType programType = ProgramType.fromSelector(info.getSelector());
@@ -96,25 +99,20 @@
ViewPager viewPager = findViewById(R.id.viewpager);
viewPager.setAdapter(mRadioPagerAdapter);
+ mUseSourceLogoForAppSelector =
+ getResources().getBoolean(R.bool.use_media_source_logo_for_app_selector);
+
mToolbar = requireToolbar(this);
mToolbar.setState(HOME);
mToolbar.setTitle(R.string.app_name);
- mToolbar.setLogo(R.drawable.logo_fm_radio);
+ if (!mUseSourceLogoForAppSelector) {
+ mToolbar.setLogo(R.drawable.logo_fm_radio);
+ }
mToolbar.registerOnTabSelectedListener(t ->
- viewPager.setCurrentItem(mToolbar.getTabLayout().getTabPosition(t)));
+ viewPager.setCurrentItem(mToolbar.getTabPosition(t)));
updateMenuItems();
setupTabsWithViewPager(viewPager);
-
- MediaSourceViewModel model =
- MediaSourceViewModel.get(getApplication(), MEDIA_SOURCE_MODE_BROWSE);
- model.getPrimaryMediaSource().observe(this, source -> {
- if (source != null) {
- // Always go through the trampoline activity to keep all the dispatching logic
- // there.
- startActivity(new Intent(Car.CAR_INTENT_ACTION_MEDIA_TEMPLATE));
- }
- });
}
@Override
@@ -129,6 +127,34 @@
}
@Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ setIntent(intent); // getIntent() should always return the most recent
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onNewIntent: " + intent);
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ Intent intent = getIntent();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onResume intent: " + intent);
+ }
+
+ if (intent != null) {
+ mMediaTrampoline.setLaunchedMediaSource(RadioAppService.getMediaSourceComp(this));
+
+ // Mark the intent as consumed so that coming back from the media app selector doesn't
+ // set the source again.
+ setIntent(null);
+ }
+ }
+
+ @Override
protected void onStop() {
super.onStop();
@@ -198,7 +224,9 @@
Intent appSelectorIntent = MediaSource.getSourceSelectorIntent(this, false);
MenuItem appSelectorMenuItem = MenuItem.builder(this)
- .setIcon(R.drawable.ic_app_switch)
+ .setIcon(mUseSourceLogoForAppSelector
+ ? R.drawable.logo_fm_radio : R.drawable.ic_app_switch)
+ .setTinted(!mUseSourceLogoForAppSelector)
.setOnClickListener(m -> startActivity(appSelectorIntent))
.build();
diff --git a/src/com/android/car/radio/audio/AudioStreamController.java b/src/com/android/car/radio/audio/AudioStreamController.java
index c29811f..2b92a9b 100644
--- a/src/com/android/car/radio/audio/AudioStreamController.java
+++ b/src/com/android/car/radio/audio/AudioStreamController.java
@@ -21,6 +21,7 @@
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.media.session.PlaybackState;
+import android.util.IndentingPrintWriter;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
@@ -80,7 +81,7 @@
*
* It may be ducked, transiently lost or delayed.
*/
- private boolean mHasSomeFocus = false;
+ private boolean mHasSomeFocus;
private int mCurrentPlaybackState = PlaybackState.STATE_NONE;
private Object mTuningToken;
@@ -231,6 +232,7 @@
* @return true, if request has succeeded (maybe delayed)
*/
public boolean requestMuted(boolean muted) {
+ Log.v(TAG, "requestMuted(" + muted + ")");
synchronized (mLock) {
if (muted) {
if (mTuningToken == null) {
@@ -272,4 +274,19 @@
}
}
}
+
+ /**
+ * Dumps the current audio stream controller state
+ */
+ public void dump(IndentingPrintWriter writer) {
+ writer.println("AudioStreamController");
+ writer.increaseIndent();
+ synchronized (mLock) {
+ writer.printf("Focus Request: %s\n", mGainFocusReq);
+ writer.printf("Has Some Focus: %b\n", mHasSomeFocus);
+ writer.printf("PlayBack State: %d\n", mCurrentPlaybackState);
+ writer.printf("Is Tuning Token Available: %s\n", mTuningToken != null);
+ }
+ writer.decreaseIndent();
+ }
}
diff --git a/src/com/android/car/radio/media/TunerSession.java b/src/com/android/car/radio/media/TunerSession.java
index 0cda1f3..3d9c70c 100644
--- a/src/com/android/car/radio/media/TunerSession.java
+++ b/src/com/android/car/radio/media/TunerSession.java
@@ -149,6 +149,7 @@
@Override
public void onPlay() {
+ mAppService.tuneToDefaultIfNeeded();
mAppService.setMuted(false);
}
diff --git a/src/com/android/car/radio/service/IRadioAppService.aidl b/src/com/android/car/radio/service/IRadioAppService.aidl
index 6ece15a..66f9211 100644
--- a/src/com/android/car/radio/service/IRadioAppService.aidl
+++ b/src/com/android/car/radio/service/IRadioAppService.aidl
@@ -95,4 +95,9 @@
* Returns current region config (like frequency ranges for AM/FM).
*/
RegionConfig getRegionConfig();
+
+ /**
+ * Tunes to the previously selected program or the default channel.
+ */
+ void tuneToDefaultIfNeeded();
}
diff --git a/src/com/android/car/radio/service/RadioAppService.java b/src/com/android/car/radio/service/RadioAppService.java
index 44342d5..e906bcd 100644
--- a/src/com/android/car/radio/service/RadioAppService.java
+++ b/src/com/android/car/radio/service/RadioAppService.java
@@ -18,6 +18,8 @@
import static com.android.car.radio.util.Remote.tryExec;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
@@ -30,7 +32,9 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.service.media.MediaBrowserService;
+import android.util.IndentingPrintWriter;
+import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;
@@ -68,6 +72,11 @@
public static String ACTION_APP_SERVICE = "com.android.car.radio.ACTION_APP_SERVICE";
private static final long PROGRAM_LIST_RATE_LIMITING = 1000;
+ /** Returns the {@link ComponentName} that represents this {@link MediaBrowserService}. */
+ public static @NonNull ComponentName getMediaSourceComp(Context context) {
+ return new ComponentName(context, RadioAppService.class);
+ }
+
private final Object mLock = new Object();
private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
private final List<IRadioAppCallback> mRadioAppCallbacks = new ArrayList<>();
@@ -85,10 +94,13 @@
private TunerSession mMediaSession;
// current observables state for newly bound IRadioAppCallbacks
+ @GuardedBy("mLock")
private ProgramInfo mCurrentProgram = null;
+ @GuardedBy("mLock")
private int mCurrentPlaybackState = PlaybackState.STATE_NONE;
+ @GuardedBy("mLock")
private long mLastProgramListPush;
-
+ @GuardedBy("mLock")
private RegionConfig mRegionConfigCache;
private SkipController mSkipController;
@@ -132,14 +144,13 @@
mProgramList.addOnCompleteListener(this::pushProgramListUpdate);
}
- tuneToDefault(null);
- mAudioStreamController.requestMuted(false);
-
mLifecycleRegistry.markState(Lifecycle.State.CREATED);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.d(TAG, "onStartCommand intent [%s] flags[%d] startId[%d]",
+ intent.toString(), flags, startId);
mLifecycleRegistry.markState(Lifecycle.State.STARTED);
if (BrowseTree.ACTION_PLAY_BROADCASTRADIO.equals(intent.getAction())) {
Log.i(TAG, "Executing general play radio intent");
@@ -153,6 +164,7 @@
@Override
public IBinder onBind(Intent intent) {
+ Log.i(TAG, "onBind intent[" + intent + "]");
mLifecycleRegistry.markState(Lifecycle.State.STARTED);
if (mRadioTuner == null) return null;
if (ACTION_APP_SERVICE.equals(intent.getAction())) {
@@ -186,6 +198,7 @@
}
private void onPlaybackStateChanged(int newState) {
+ Log.d(TAG, "onPlaybackStateChanged new state [%d]", newState);
synchronized (mLock) {
mCurrentPlaybackState = newState;
for (IRadioAppCallback callback : mRadioAppCallbacks) {
@@ -195,6 +208,7 @@
}
private void onProgramListChanged() {
+ if (mProgramList == null) return;
synchronized (mLock) {
if (SystemClock.elapsedRealtime() - mLastProgramListPush > PROGRAM_LIST_RATE_LIMITING) {
pushProgramListUpdate();
@@ -203,6 +217,7 @@
}
private void pushProgramListUpdate() {
+ if (mProgramList == null) return;
List<ProgramInfo> plist = mProgramList.toList();
synchronized (mLock) {
@@ -244,8 +259,9 @@
mAudioStreamController = null;
}
if (mProgramList != null) {
- mProgramList.close();
+ ProgramList oldList = mProgramList;
mProgramList = null;
+ oldList.close();
}
if (mRadioTuner != null) {
mRadioTuner.close();
@@ -279,10 +295,25 @@
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (mSkipController != null) {
- pw.println("SkipController:"); mSkipController.dump(pw, " ");
- } else {
- pw.println("no SkipController");
+ try (IndentingPrintWriter writer = new IndentingPrintWriter(pw)) {
+ pw.println("RadioAppService:");
+ writer.increaseIndent();
+ if (mSkipController != null) {
+ writer.increaseIndent();
+ mSkipController.dump(writer);
+ writer.decreaseIndent();
+ } else {
+ pw.println("No SkipController");
+ }
+
+ if (mAudioStreamController != null) {
+ writer.increaseIndent();
+ mAudioStreamController.dump(writer);
+ writer.decreaseIndent();
+ } else {
+ pw.println("No AudioStreamController");
+ }
+ writer.decreaseIndent();
}
}
@@ -371,6 +402,21 @@
}
@Override
+ public void tuneToDefaultIfNeeded() {
+ synchronized (mLock) {
+ if (mRadioTuner == null) {
+ throw new IllegalStateException("Tuner session is closed");
+ }
+
+ if (mCurrentPlaybackState != PlaybackState.STATE_NONE) {
+ return;
+ }
+ }
+
+ tuneToDefault(null);
+ }
+
+ @Override
public void switchBand(ProgramType band) {
tuneToDefault(band);
}
diff --git a/src/com/android/car/radio/service/RadioAppServiceWrapper.java b/src/com/android/car/radio/service/RadioAppServiceWrapper.java
index 154a408..cfd07af 100644
--- a/src/com/android/car/radio/service/RadioAppServiceWrapper.java
+++ b/src/com/android/car/radio/service/RadioAppServiceWrapper.java
@@ -365,6 +365,13 @@
}
/**
+ * Tunes to the previously selected program or the default channel.
+ */
+ public void tuneToDefaultIfNeeded() {
+ callService(service -> service.tuneToDefaultIfNeeded());
+ }
+
+ /**
* Tune to a default channel of a given program type (band).
*
* Usually, this means tuning to the recently listened program of a given band.
diff --git a/src/com/android/car/radio/service/SkipController.java b/src/com/android/car/radio/service/SkipController.java
index 574399b..5df31cb 100644
--- a/src/com/android/car/radio/service/SkipController.java
+++ b/src/com/android/car/radio/service/SkipController.java
@@ -16,6 +16,7 @@
package com.android.car.radio.service;
import android.os.RemoteException;
+import android.util.IndentingPrintWriter;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
@@ -26,7 +27,6 @@
import com.android.car.radio.SkipMode;
import com.android.car.radio.util.Log;
-import java.io.PrintWriter;
import java.util.List;
/**
@@ -41,13 +41,13 @@
private final IRadioAppService.Stub mService;
- @GuardedBy("mlock")
+ @GuardedBy("mLock")
private List<Program> mFavorites;
- @GuardedBy("mlock")
+ @GuardedBy("mLock")
private int mCurrentIndex;
- @GuardedBy("mlock")
+ @GuardedBy("mLock")
private SkipMode mSkipMode;
SkipController(@NonNull IRadioAppService.Stub service,
@@ -128,25 +128,27 @@
return program;
}
- void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ void dump(IndentingPrintWriter pw) {
+ pw.println("SkipController");
+ pw.increaseIndent();
synchronized (mLock) {
- pw.print(prefix); pw.print("mode: "); pw.println(mSkipMode);
- pw.print(prefix); pw.print("current index: "); pw.println(mCurrentIndex);
+ pw.printf("mode: %s\n", mSkipMode);
+ pw.printf("current index: %d\n", mCurrentIndex);
if (mFavorites == null || mFavorites.isEmpty()) {
- pw.print(prefix); pw.println("no favorites");
- return;
- }
- int size = mFavorites.size();
- pw.print(prefix); pw.print(size); pw.println(" favorites: ");
- String prefix2 = prefix + " ";
- for (int i = 0; i < size; i++) {
- pw.print(prefix2);
- pw.print(i); pw.print(": "); pw.print(mFavorites.get(i).getName());
- if (i == mCurrentIndex) {
- pw.print(" (current)");
+ pw.println("no favorites");
+ } else {
+ pw.printf("%d favorites:\n", mFavorites.size());
+ pw.increaseIndent();
+ for (int i = 0; i < mFavorites.size(); i++) {
+ pw.printf("Favorite[%d]: %s ", i, mFavorites.get(i).getName());
+ if (i == mCurrentIndex) {
+ pw.printf(" is current");
+ }
+ pw.println();
}
- pw.println();
+ pw.decreaseIndent();
}
}
+ pw.decreaseIndent();
}
}