diff options
156 files changed, 7001 insertions, 5563 deletions
diff --git a/car-cluster-demo-renderer/Android.mk b/car-cluster-demo-renderer/Android.mk deleted file mode 100644 index 02befa3138..0000000000 --- a/car-cluster-demo-renderer/Android.mk +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (C) 2015 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. -# -# - -LOCAL_PATH:= $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := $(call all-java-files-under, src) - -LOCAL_PACKAGE_NAME := android.car.cluster.demorenderer - -# Each update should be signed by OEMs -LOCAL_CERTIFICATE := platform -LOCAL_PRIVILEGED_MODULE := true - -LOCAL_PROGUARD_FLAG_FILES := proguard.flags -LOCAL_PROGUARD_ENABLED := disabled - -LOCAL_JAVA_LIBRARIES += android.car - -include $(BUILD_PACKAGE) diff --git a/car-cluster-demo-renderer/AndroidManifest.xml b/car-cluster-demo-renderer/AndroidManifest.xml deleted file mode 100644 index f11350198f..0000000000 --- a/car-cluster-demo-renderer/AndroidManifest.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2016 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. ---> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.car.cluster.demorenderer" - android:versionCode="1" - android:versionName="1.0"> - - <!-- We set TYPE_SYSTEM_ALERT window flag to presentation in order - to show it outside of activity context --> - <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> - - <application android:label="@string/app_name" android:icon="@drawable/ic_launcher"> - </application> -</manifest> diff --git a/car-cluster-demo-renderer/proguard.flags b/car-cluster-demo-renderer/proguard.flags deleted file mode 100644 index 22cc22df14..0000000000 --- a/car-cluster-demo-renderer/proguard.flags +++ /dev/null @@ -1,3 +0,0 @@ --verbose --keep @com.android.internal.annotations.VisibleForTesting class * - diff --git a/car-cluster-demo-renderer/res/drawable-hdpi/ic_contactavatar_large_light.png b/car-cluster-demo-renderer/res/drawable-hdpi/ic_contactavatar_large_light.png Binary files differdeleted file mode 100644 index 138b53ce30..0000000000 --- a/car-cluster-demo-renderer/res/drawable-hdpi/ic_contactavatar_large_light.png +++ /dev/null diff --git a/car-cluster-demo-renderer/res/drawable-hdpi/ic_launcher.png b/car-cluster-demo-renderer/res/drawable-hdpi/ic_launcher.png Binary files differdeleted file mode 100644 index 96a442e5b8..0000000000 --- a/car-cluster-demo-renderer/res/drawable-hdpi/ic_launcher.png +++ /dev/null diff --git a/car-cluster-demo-renderer/res/drawable-ldpi/ic_launcher.png b/car-cluster-demo-renderer/res/drawable-ldpi/ic_launcher.png Binary files differdeleted file mode 100644 index 99238729d8..0000000000 --- a/car-cluster-demo-renderer/res/drawable-ldpi/ic_launcher.png +++ /dev/null diff --git a/car-cluster-demo-renderer/res/drawable-mdpi/ic_contactavatar_large_light.png b/car-cluster-demo-renderer/res/drawable-mdpi/ic_contactavatar_large_light.png Binary files differdeleted file mode 100644 index 7031b839de..0000000000 --- a/car-cluster-demo-renderer/res/drawable-mdpi/ic_contactavatar_large_light.png +++ /dev/null diff --git a/car-cluster-demo-renderer/res/drawable-mdpi/ic_launcher.png b/car-cluster-demo-renderer/res/drawable-mdpi/ic_launcher.png Binary files differdeleted file mode 100644 index 359047dfa4..0000000000 --- a/car-cluster-demo-renderer/res/drawable-mdpi/ic_launcher.png +++ /dev/null diff --git a/car-cluster-demo-renderer/res/drawable-xhdpi/ic_contactavatar_large_light.png b/car-cluster-demo-renderer/res/drawable-xhdpi/ic_contactavatar_large_light.png Binary files differdeleted file mode 100644 index a137cdb20c..0000000000 --- a/car-cluster-demo-renderer/res/drawable-xhdpi/ic_contactavatar_large_light.png +++ /dev/null diff --git a/car-cluster-demo-renderer/res/drawable-xhdpi/ic_launcher.png b/car-cluster-demo-renderer/res/drawable-xhdpi/ic_launcher.png Binary files differdeleted file mode 100644 index 71c6d760f0..0000000000 --- a/car-cluster-demo-renderer/res/drawable-xhdpi/ic_launcher.png +++ /dev/null diff --git a/car-cluster-demo-renderer/res/drawable-xxhdpi/ic_contactavatar_large_light.png b/car-cluster-demo-renderer/res/drawable-xxhdpi/ic_contactavatar_large_light.png Binary files differdeleted file mode 100644 index 7111bb9790..0000000000 --- a/car-cluster-demo-renderer/res/drawable-xxhdpi/ic_contactavatar_large_light.png +++ /dev/null diff --git a/car-cluster-demo-renderer/res/layout/instrument_cluster.xml b/car-cluster-demo-renderer/res/layout/instrument_cluster.xml deleted file mode 100644 index b4235c2e0b..0000000000 --- a/car-cluster-demo-renderer/res/layout/instrument_cluster.xml +++ /dev/null @@ -1,118 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2016 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:orientation="horizontal" - android:layout_width="match_parent" - android:layout_height="match_parent" android:id="@+id/instrument_cluster_layout" - android:theme="@android:style/Theme.Material" android:weightSum="1" - android:background="@android:color/background_dark"> - - <LinearLayout - android:orientation="vertical" - android:layout_width="214dp" - android:layout_height="match_parent"> - </LinearLayout> - <LinearLayout - android:orientation="vertical" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:gravity="center_vertical"> - <TextView - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceLarge" - android:id="@+id/speed" android:textSize="120dp" - android:textAlignment="center" android:gravity="center" - android:layout_marginTop="100dp"/> - <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceMedium" - android:text="@string/fuel_level" android:id="@+id/textView" - android:layout_gravity="left"/> - <ProgressBar style="?android:attr/progressBarStyleHorizontal" android:layout_width="226dp" - android:layout_height="wrap_content" android:id="@+id/fuel_level_progress" - android:max="100" android:progress="30" - /> - <LinearLayout - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="match_parent" android:layout_marginTop="40dp" - android:id="@+id/nav_layout"> - <TextView - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceMedium" - android:id="@+id/nav_event_title" - /> - <TextView - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceMedium" - android:id="@+id/nav_distance" android:gravity="center" - /> - </LinearLayout> - </LinearLayout> - <FrameLayout android:orientation="vertical" android:layout_width="wrap_content" - android:layout_height="match_parent" android:layout_weight="0.63" - android:layout_gravity="center_vertical" android:weightSum="1" - android:layout_marginTop="0dp"> - <LinearLayout android:orientation="vertical" android:layout_width="match_parent" - android:layout_height="wrap_content" android:layout_weight="0.63" - android:layout_gravity="center_horizontal" android:weightSum="1" - android:layout_marginTop="100dp" - android:background="@android:color/background_dark" - android:id="@+id/media_layout" - android:visibility="gone"> - <ImageView android:layout_width="200dp" android:layout_height="200dp" - android:id="@+id/media_image" android:layout_gravity="center_horizontal" - android:background="@android:color/holo_blue_light" - android:scaleType="center"/> - <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceMedium" - android:id="@+id/media_album" android:gravity="center" - android:layout_gravity="center_horizontal" android:visibility="gone"/> - <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceMedium" - android:id="@+id/media_track" android:gravity="center" - android:layout_gravity="center_horizontal" android:textSize="28sp"/> - <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceMedium" - android:id="@+id/media_artist" android:gravity="center" - android:textIsSelectable="false" - /> - </LinearLayout> - <LinearLayout android:orientation="vertical" android:layout_width="match_parent" - android:layout_height="wrap_content" android:layout_weight="0.63" - android:layout_gravity="center_horizontal" android:weightSum="1" - android:layout_marginTop="100dp" - android:background="@android:color/background_dark" - android:id="@+id/phone_layout" - android:visibility="gone"> - <ImageView android:layout_width="200dp" android:layout_height="200dp" - android:id="@+id/phone_contact_photo" android:layout_gravity="center" - android:background="#16161e" - android:scaleType="fitCenter"/> - <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceMedium" - android:id="@+id/phone_title" android:gravity="center" - android:layout_gravity="center_horizontal" android:textSize="28sp"/> - <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceMedium" - android:id="@+id/phone_subtitle" android:gravity="center" - android:textIsSelectable="false" - /> - </LinearLayout> - </FrameLayout> -</LinearLayout>
\ No newline at end of file diff --git a/car-cluster-demo-renderer/res/values/strings.xml b/car-cluster-demo-renderer/res/values/strings.xml deleted file mode 100644 index bd280ed3fe..0000000000 --- a/car-cluster-demo-renderer/res/values/strings.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2016 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. ---> -<resources> - <string name="app_name">ACTIVITY_ENTRY_NAME</string> - <string name="fuel_level">Fuel level</string> - - <!-- Navigation --> - <string name="turn_slight_left">Slight left</string> - <string name="turn_slight_right">Slight right</string> - <string name="turn_left">Turn left</string> - <string name="turn_right">Turn right</string> - <string name="turn_sharp_left">Sharp left turn</string> - <string name="turn_sharp_right">Sharp right turn</string> - <string name="turn_u_turn_left">Make u-turn</string> - <string name="turn_u_turn_right">Make right u-turn</string> - <string name="turn_on_ramp_left">Turn left on ramp</string> - <string name="turn_on_ramp_right">Turn right on ramp</string> - <string name="nav_event_title_format">%1$s on %2$s</string> - <string name="meters">meters</string> - - <string name="call_state_ringing">Ringing</string> - <string name="call_state_active">Active</string> -</resources> diff --git a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/CallStateMonitor.java b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/CallStateMonitor.java deleted file mode 100644 index c8b0616e6a..0000000000 --- a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/CallStateMonitor.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2016 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 android.car.cluster.demorenderer; - -import android.annotation.Nullable; -import android.car.cluster.demorenderer.PhoneBook.Contact; -import android.car.cluster.demorenderer.PhoneBook.ContactLoadedListener; -import android.car.cluster.demorenderer.PhoneBook.ContactPhotoLoadedListener; -import android.content.Context; -import android.graphics.Bitmap; -import android.telephony.TelephonyManager; -import android.util.Log; - -import java.lang.ref.WeakReference; - -/** - * Monitors call state. - */ -public class CallStateMonitor implements ContactLoadedListener, ContactPhotoLoadedListener { - private final static String TAG = CallStateMonitor.class.getSimpleName(); - - private final PhoneBook mPhoneBook; - private final TelephonyManager mTelephonyManager; - private final PhoneStateListener mListener; - private final CallStateListener mCallStateListener; - - CallStateMonitor(Context context, PhoneStateListener listener) { - Log.d(TAG, "ctor, context: " + context + ", phoneRenderer: " + listener + - ", contentResolver: " + context.getContentResolver() + - ", applicationContext: " + context.getApplicationContext()); - - mListener = listener; - mTelephonyManager = context.getSystemService(TelephonyManager.class); - mPhoneBook = new PhoneBook(context, mTelephonyManager); - mCallStateListener = new CallStateListener(this); - mTelephonyManager.listen(mCallStateListener, - android.telephony.PhoneStateListener.LISTEN_CALL_STATE); - - updateRendererPhoneStatusIfAvailable(); - } - - public void release() { - mTelephonyManager.listen(mCallStateListener, - android.telephony.PhoneStateListener.LISTEN_NONE); - } - - private void updateRendererPhoneStatusIfAvailable() { - onCallStateChanged(mTelephonyManager.getCallState(), null); - } - - private void onCallStateChanged(int state, final String number) { - Log.d(TAG, "onCallStateChanged, state:" + state + ", phoneNumber: " + number); - - // Update call state immediately on instrument cluster. - mListener.onCallStateChanged(state, PhoneBook.getFormattedNumber(number)); - - // Now fetching details asynchronously. - mPhoneBook.getContactDetailsAsync(number, this); - } - - @Override - public void onContactLoaded(String number, @Nullable Contact contact) { - if (contact != null) { - mListener.onContactDetailsUpdated(contact.getName(), contact.getType(), - mPhoneBook.isVoicemail(number)); - - mPhoneBook.getContactPictureAsync(contact.getId(), this); - } - } - - @Override - public void onPhotoLoaded(int contactId, @Nullable Bitmap photo) { - mListener.onContactPhotoUpdated(photo); - } - - public interface PhoneStateListener { - void onCallStateChanged(int state, @Nullable String number); - void onContactDetailsUpdated( - @Nullable CharSequence name, - @Nullable CharSequence typeLabel, - boolean isVoiceMail); - void onContactPhotoUpdated(Bitmap picture); - } - - private static class CallStateListener extends android.telephony.PhoneStateListener { - private final WeakReference<CallStateMonitor> mServiceRef; - - CallStateListener(CallStateMonitor service) { - mServiceRef = new WeakReference<>(service); - } - - @Override - public void onCallStateChanged(int state, String incomingNumber) { - CallStateMonitor service = mServiceRef.get(); - if (service != null) { - service.onCallStateChanged(state, incomingNumber); - } - } - } -} diff --git a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoInstrumentClusterRenderer.java b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoInstrumentClusterRenderer.java deleted file mode 100644 index 3b09be77ee..0000000000 --- a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoInstrumentClusterRenderer.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2016 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 android.car.cluster.demorenderer; - -import android.app.Presentation; -import android.car.cluster.renderer.InstrumentClusterRenderer; -import android.car.cluster.renderer.NavigationRenderer; -import android.content.Context; -import android.hardware.display.DisplayManager; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; -import android.view.Display; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -/** - * Demo implementation of {@code InstrumentClusterRenderer}. - */ -public class DemoInstrumentClusterRenderer extends InstrumentClusterRenderer { - - private final static String TAG = DemoInstrumentClusterRenderer.class.getSimpleName(); - - private DemoInstrumentClusterView mView; - private Context mContext; - private CallStateMonitor mPhoneStatusMonitor; - private MediaStateMonitor mMediaStateMonitor; - private DemoPhoneRenderer mPhoneRenderer; - private DemoMediaRenderer mMediaRenderer; - private Presentation mPresentation; - - @Override - public void onCreate(Context context) { - mContext = context; - - final Display display = getInstrumentClusterDisplay(context); - - if (display != null) { - runOnMainThread(() -> { - Log.d(TAG, "Initializing renderer in main thread."); - try { - mPresentation = new InstrumentClusterPresentation(mContext, display); - - ViewGroup rootView = (ViewGroup) LayoutInflater.from(mContext).inflate( - R.layout.instrument_cluster, null); - - mPresentation.setContentView(rootView); - View rendererView = createView(); - rootView.addView(rendererView); - } catch (Exception e) { - Log.e(TAG, e.getMessage(), e); - throw e; - } - }); - } - } - - private View createView() { - mView = new DemoInstrumentClusterView(mContext); - mPhoneRenderer = new DemoPhoneRenderer(mView); - mMediaRenderer = new DemoMediaRenderer(mView); - return mView; - } - - @Override - public void onStart() { - runOnMainThread(() -> { - Log.d(TAG, "onStart"); - mPhoneStatusMonitor = new CallStateMonitor(mContext, mPhoneRenderer); - mMediaStateMonitor = new MediaStateMonitor(mContext, mMediaRenderer); - mPresentation.show(); - }); - } - - @Override - public void onStop() { - runOnMainThread(() -> { - if (mPhoneStatusMonitor != null) { - mPhoneStatusMonitor.release(); - mPhoneStatusMonitor = null; - } - - if (mMediaStateMonitor != null) { - mMediaStateMonitor.release(); - mMediaStateMonitor = null; - } - mPhoneRenderer = null; - mMediaRenderer = null; - }); - } - - @Override - protected NavigationRenderer createNavigationRenderer() { - return ThreadSafeNavigationRenderer.createFor( - Looper.getMainLooper(), - new DemoNavigationRenderer(mView)); - } - - private static Display getInstrumentClusterDisplay(Context context) { - DisplayManager displayManager = context.getSystemService(DisplayManager.class); - Display[] displays = displayManager.getDisplays(); - - Log.d(TAG, "There are currently " + displays.length + " displays connected."); - for (Display display : displays) { - Log.d(TAG, " " + display); - } - - if (displays.length > 1) { - // TODO: assuming that secondary display is instrument cluster. Put this into settings? - return displays[1]; - } - return null; - } - - private static void runOnMainThread(Runnable runnable) { - new Handler(Looper.getMainLooper()).post(runnable); - } -} diff --git a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoInstrumentClusterView.java b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoInstrumentClusterView.java deleted file mode 100644 index 9ad691fa5b..0000000000 --- a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoInstrumentClusterView.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2016 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 android.car.cluster.demorenderer; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.content.Context; -import android.graphics.Bitmap; -import android.util.Log; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -/** - * This class is responsible for drawing the whole instrument cluster. - */ -public class DemoInstrumentClusterView extends FrameLayout { - - private final String TAG = DemoInstrumentClusterView.class.getSimpleName(); - - private TextView mSpeedView; - private TextView mEventTitleView; - private TextView mDistanceView; - private View mNavPanel; - private TextView mMediaArtistView; - private TextView mMediaAlbumView; - private TextView mMediaTrackView; - private ImageView mMediaImageView; - private View mMediaPanel; - - private View mPhonePanel; - private TextView mPhoneTitle; - private TextView mPhoneSubtitle; - private ImageView mPhoneImage; - - private final Integer mAnimationDurationMs; - - public DemoInstrumentClusterView(Context context) { - super(context); - mAnimationDurationMs = getResources().getInteger(android.R.integer.config_longAnimTime); - init(); - } - - public void setSpeed(String speed) { - Log.d(TAG, "setSpeed, meterPerSecond: " + speed); - mSpeedView.setText(speed); - } - - public void showNavigation() { - Log.d(TAG, "showNavigation"); - mEventTitleView.setText(""); - mDistanceView.setText(""); - mNavPanel.setVisibility(VISIBLE); - } - - public void hideNavigation() { - Log.d(TAG, "hideNavigation"); - mNavPanel.setVisibility(INVISIBLE); - } - - public void setNextTurn(Bitmap image, String title) { - Log.d(TAG, "setNextTurn, image: " + image + ", title: " + title); - mEventTitleView.setText(title); - } - - public void setNextTurnDistance(String distance) { - Log.d(TAG, "setNextTurnDistance, distance: " + distance); - mDistanceView.setText(distance); - } - - public void setMediaData(final CharSequence artist, final CharSequence album, - final CharSequence track, final Bitmap image) { - Log.d(TAG, "setMediaData" + " artist = " + artist + ", album: " + album + ", track: " + - track + ", bitmap: " + image); - - mMediaArtistView.setText(artist); - mMediaAlbumView.setText(album); - mMediaTrackView.setText(track); - mMediaImageView.setImageBitmap(image); - } - - private void showAnimated(final View view) { - if (view.getVisibility() == VISIBLE && view.getAlpha() > 0) { - return; - } - view.setAlpha(0); - view.setVisibility(VISIBLE); - view.animate() - .alpha(1f) - .setDuration(mAnimationDurationMs) - .setListener(null); - } - - private void hideAnimated(final View view) { - if (view.getVisibility() == GONE) { - return; - } - view.animate() - .alpha(0f) - .setDuration(mAnimationDurationMs) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - view.setVisibility(GONE); - } - }); - } - - public void showMedia() { - Log.d(TAG, "showMedia"); - showAnimated(mMediaPanel); - } - - public void hideMedia() { - Log.d(TAG, "hideMedia"); - hideAnimated(mMediaPanel); - } - - public void showPhone() { - Log.d(TAG, "showPhone"); - mPhoneSubtitle.setText(""); - mPhoneImage.setImageResource(0); // To clear previous contact photo (if any). - mPhoneTitle.setText(""); - showAnimated(mPhonePanel); - } - - public void hidePhone() { - Log.d(TAG, "hidePhone"); - hideAnimated(mPhonePanel); - } - - public void setPhoneTitle(String number) { - Log.d(TAG, "setPhoneTitle, number: " + number); - mPhoneTitle.setText(number); - } - - public void setPhoneSubtitle(String contact) { - Log.d(TAG, "setPhoneContact, contact: " + contact); - mPhoneSubtitle.setText(contact); - } - - public void setPhoneImage(Bitmap photo) { - Log.d(TAG, "setPhoneImage, photo: " + photo); - mPhoneImage.setImageBitmap(photo); - } - - private void init() { - Log.d(TAG, "init"); - View rootView = inflate(getContext(), R.layout.instrument_cluster, null); - mSpeedView = (TextView) rootView.findViewById(R.id.speed); - mEventTitleView = (TextView) rootView.findViewById(R.id.nav_event_title); - mDistanceView = (TextView) rootView.findViewById(R.id.nav_distance); - mNavPanel = rootView.findViewById(R.id.nav_layout); - - mMediaPanel = rootView.findViewById(R.id.media_layout); - mMediaArtistView = (TextView) rootView.findViewById(R.id.media_artist); - mMediaAlbumView = (TextView) rootView.findViewById(R.id.media_album); - mMediaTrackView = (TextView) rootView.findViewById(R.id.media_track); - mMediaImageView = (ImageView) rootView.findViewById(R.id.media_image); - - mPhonePanel = rootView.findViewById(R.id.phone_layout); - mPhoneImage = (ImageView) rootView.findViewById(R.id.phone_contact_photo); - mPhoneSubtitle = (TextView) rootView.findViewById(R.id.phone_subtitle); - mPhoneTitle = (TextView) rootView.findViewById(R.id.phone_title); - - setSpeed("0"); - - addView(rootView); - } -} diff --git a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoMediaRenderer.java b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoMediaRenderer.java deleted file mode 100644 index 5ee23cc934..0000000000 --- a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoMediaRenderer.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2016 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 android.car.cluster.demorenderer; - -import android.car.cluster.demorenderer.MediaStateMonitor.MediaStateListener; -import android.graphics.Bitmap; -import android.media.MediaMetadata; -import android.media.session.PlaybackState; -import android.util.Log; - -/** - * Demo of rendering media data in instrument cluster. - */ -public class DemoMediaRenderer implements MediaStateListener { - - private static final String TAG = DemoMediaRenderer.class.getSimpleName(); - - - private final DemoInstrumentClusterView mView; - - private static final String[] PREFERRED_BITMAP_ORDER = { - MediaMetadata.METADATA_KEY_ALBUM_ART, - MediaMetadata.METADATA_KEY_ART, - MediaMetadata.METADATA_KEY_DISPLAY_ICON - }; - - - public DemoMediaRenderer(DemoInstrumentClusterView view) { - mView = view; - } - - @Override - public void onPlaybackStateChanged(PlaybackState playbackState) { - Log.d(TAG, "onPlaybackStateChanged: " + playbackState); - } - - @Override - public void onMetadataChanged(MediaMetadata metadata) { - Log.d(TAG, "onMetadataChanged: " + metadata); - if (metadata != null) { - CharSequence artist = metadata.getText(MediaMetadata.METADATA_KEY_ARTIST); - CharSequence album = metadata.getText(MediaMetadata.METADATA_KEY_ALBUM); - CharSequence track = metadata.getText(MediaMetadata.METADATA_KEY_TITLE); - Bitmap bitmap = getMetadataBitmap(metadata); - - mView.setMediaData(artist, album, track, bitmap); - mView.showMedia(); - } else { - mView.setMediaData(null, null, null, null); - mView.hideMedia(); - } - } - - private Bitmap getMetadataBitmap(MediaMetadata metadata) { - // Get the best art bitmap we can find - for (int i = 0; i < PREFERRED_BITMAP_ORDER.length; i++) { - Bitmap bitmap = metadata.getBitmap(PREFERRED_BITMAP_ORDER[i]); - if (bitmap != null) { - return bitmap; - } - } - return null; - } - -} diff --git a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoNavigationRenderer.java b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoNavigationRenderer.java deleted file mode 100644 index 5588546a09..0000000000 --- a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoNavigationRenderer.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2016 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 android.car.cluster.demorenderer; - -import static android.car.navigation.CarNavigationManager.TURN_SIDE_LEFT; -import static android.car.navigation.CarNavigationManager.TURN_SIDE_RIGHT; -import static android.car.navigation.CarNavigationManager.TURN_TURN; - -import android.car.cluster.renderer.NavigationRenderer; -import android.car.navigation.CarNavigationInstrumentCluster; -import android.content.Context; -import android.graphics.Bitmap; -import android.util.Log; -import android.util.Pair; - -import java.util.HashMap; -import java.util.Map; - -/** - * Demo implementation of {@link NavigationRenderer}. - */ -public class DemoNavigationRenderer extends NavigationRenderer { - - private static final String TAG = DemoNavigationRenderer.class.getSimpleName(); - - private final DemoInstrumentClusterView mView; - private final Context mContext; - - private final static Map<Pair<Integer, Integer>, Integer> sTurns; - - static { - sTurns = new HashMap<>(); - sTurns.put(new Pair<>(TURN_TURN, TURN_SIDE_LEFT), R.string.turn_left); - sTurns.put(new Pair<>(TURN_TURN, TURN_SIDE_RIGHT), R.string.turn_right); - // TODO: add more localized strings here. - } - - DemoNavigationRenderer(DemoInstrumentClusterView view) { - mView = view; - mContext = view.getContext(); - } - - @Override - public CarNavigationInstrumentCluster getNavigationProperties() { - // TODO - return null; - } - - @Override - public void onStartNavigation() { - mView.showNavigation(); - } - - @Override - public void onStopNavigation() { - mView.hideNavigation(); - } - - @Override - public void onNextTurnChanged(int event, String road, int turnAngle, int turnNumber, - final Bitmap image, int turnSide) { - String localizedAction = getLocalizedNavigationAction(event, turnSide); - final String localizedTitle = String.format( - mContext.getString(R.string.nav_event_title_format), localizedAction, road); - - mView.setNextTurn(image, localizedTitle); - } - - @Override - public void onNextTurnDistanceChanged(final int distanceMeters, int timeSeconds) { - mView.setNextTurnDistance(toHumanReadableDistance(distanceMeters)); - } - - private String getLocalizedNavigationAction(int event, int turnSide) { - Pair<Integer, Integer> key = new Pair<>(event, turnSide); - if (sTurns.containsKey(key)) { - Integer resourceId = sTurns.get(key); - return mContext.getResources().getString(resourceId); - } else { - Log.w(TAG, "Navigation event / turn not localized: " + event + ", " + turnSide); - return String.format("Event: %d, Side: %d", event, turnSide); - } - } - - private String toHumanReadableDistance(int meters) { - // TODO: implement. - return "in " + String.valueOf(meters) + " " + mContext.getString(R.string.meters); - } -} diff --git a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoPhoneRenderer.java b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoPhoneRenderer.java deleted file mode 100644 index cc70b72c63..0000000000 --- a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoPhoneRenderer.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2016 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 android.car.cluster.demorenderer; - -import android.car.cluster.demorenderer.CallStateMonitor.PhoneStateListener; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.telephony.TelephonyManager; -import android.util.Log; - -/** - * Demo for rendering phone status in instrument cluster. - */ -public class DemoPhoneRenderer implements PhoneStateListener { - private static final String TAG = DemoPhoneRenderer.class.getSimpleName(); - - private final DemoInstrumentClusterView mView; - private final Context mContext; - - private static Bitmap sDefaultAvatar; - - private int mCurrentState; - private String mCurrentNumber; - - DemoPhoneRenderer(DemoInstrumentClusterView view) { - mView = view; - mContext = view.getContext(); - } - - @Override - public void onCallStateChanged(int state, String number) { - Log.d(TAG, "onCallStateChanged, state: " + state + ", number: " + number); - mCurrentState = state; - mCurrentNumber = PhoneBook.getFormattedNumber(number); - - if (TelephonyManager.CALL_STATE_IDLE == state) { - mView.hidePhone(); - } else { - mView.showPhone(); - setPhoneTitleWithState(mCurrentNumber); - mView.setPhoneImage(getDefaultAvatar()); - } - } - - @Override - public void onContactDetailsUpdated(CharSequence name, CharSequence typeLabel, - boolean isVoiceMail) { - Log.d(TAG, "onContactDetailsUpdated, name: " + name + ", typeLabel: " + typeLabel - + ", isVoicemail: " + isVoiceMail); - setPhoneTitleWithState(name.toString()); - mView.setPhoneSubtitle(mCurrentNumber); - } - - private void setPhoneTitleWithState(String text) { - mView.setPhoneTitle(getCallStateToDisplay(mCurrentState) + " · " + text); - } - - @Override - public void onContactPhotoUpdated(Bitmap picture) { - Log.d(TAG, "onContactPhotoUpdated, picture: " + picture); - if (picture != null) { - mView.setPhoneImage(picture); - } - } - - - private Bitmap getDefaultAvatar() { - if (sDefaultAvatar == null) { - sDefaultAvatar = BitmapFactory.decodeResource(mContext.getResources(), - R.drawable.ic_contactavatar_large_light); - } - return sDefaultAvatar; - } - - private String getCallStateToDisplay(int state) { - switch (state) { - case TelephonyManager.CALL_STATE_OFFHOOK: - return mContext.getString(R.string.call_state_active); - case TelephonyManager.CALL_STATE_RINGING: - return mContext.getString(R.string.call_state_ringing); - default: - Log.w(TAG, "Unexpected call state: " + state); - return ""; - } - } -} diff --git a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/InstrumentClusterPresentation.java b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/InstrumentClusterPresentation.java deleted file mode 100644 index 1547a246b1..0000000000 --- a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/InstrumentClusterPresentation.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2016 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 android.car.cluster.demorenderer; - -import android.app.Presentation; -import android.content.Context; -import android.view.Display; -import android.view.WindowManager; - -/** - * Presentation class. - */ -public class InstrumentClusterPresentation extends Presentation { - public InstrumentClusterPresentation(Context outerContext, Display display) { - super(outerContext, display); - getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); - } -} diff --git a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/InstrumentClusterRendererFactory.java b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/InstrumentClusterRendererFactory.java deleted file mode 100644 index 744d550929..0000000000 --- a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/InstrumentClusterRendererFactory.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2016 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 android.car.cluster.demorenderer; - -import android.car.cluster.renderer.InstrumentClusterRenderer; - -/** - * This factory class will be requested by android.car using reflection in order to get an instance - * of {@link InstrumentClusterRenderer}. - */ -@SuppressWarnings("unused") /* Used by reflection. */ -public class InstrumentClusterRendererFactory { - public static InstrumentClusterRenderer createRenderer() { - return new DemoInstrumentClusterRenderer(); - } -} diff --git a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/MediaStateMonitor.java b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/MediaStateMonitor.java deleted file mode 100644 index ada064b970..0000000000 --- a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/MediaStateMonitor.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2016 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 android.car.cluster.demorenderer; - -import android.content.Context; -import android.media.MediaMetadata; -import android.media.session.MediaController; -import android.media.session.MediaSessionManager; -import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener; -import android.media.session.PlaybackState; -import android.util.Log; - -import java.lang.ref.WeakReference; -import java.util.List; - -/** - * Reports current media status to instrument cluster renderer. - */ -public class MediaStateMonitor { - - private final static String TAG = MediaStateMonitor.class.getSimpleName(); - - private final Context mContext; - private final MediaListener mMediaListener; - private MediaController mPrimaryMediaController; - private OnActiveSessionsChangedListener mActiveSessionsChangedListener; - private MediaSessionManager mMediaSessionManager; - private MediaStateListener mListener; - - public MediaStateMonitor(Context context, MediaStateListener listener) { - mListener = listener; - mContext = context; - mMediaListener = new MediaListener(this); - mActiveSessionsChangedListener = controllers -> onActiveSessionsChanged(controllers); - mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class); - mMediaSessionManager.addOnActiveSessionsChangedListener( - mActiveSessionsChangedListener, null); - - onActiveSessionsChanged(mMediaSessionManager.getActiveSessions(null)); - } - - private void onActiveSessionsChanged(List<MediaController> controllers) { - Log.d(TAG, "onActiveSessionsChanged, controllers found: " + controllers.size()); - MediaController newPrimaryController = null; - if (controllers.size() > 0) { - newPrimaryController = controllers.get(0); - if (mPrimaryMediaController == newPrimaryController) { - // Primary media controller has not been changed. - return; - } - } - - releasePrimaryMediaController(); - - if (newPrimaryController != null) { - mPrimaryMediaController = newPrimaryController; - mPrimaryMediaController.registerCallback(mMediaListener); - } - updateRendererMediaStatusIfAvailable(); - - for (MediaController m : controllers) { - Log.d(TAG, m + ": " + m.getPackageName()); - } - } - - public void release() { - releasePrimaryMediaController(); - if (mActiveSessionsChangedListener != null) { - mMediaSessionManager.removeOnActiveSessionsChangedListener( - mActiveSessionsChangedListener); - mActiveSessionsChangedListener = null; - } - mMediaSessionManager = null; - } - - private void releasePrimaryMediaController() { - if (mPrimaryMediaController != null) { - mPrimaryMediaController.unregisterCallback(mMediaListener); - mPrimaryMediaController = null; - } - } - - private void updateRendererMediaStatusIfAvailable() { - mListener.onMetadataChanged( - mPrimaryMediaController == null ? null : mPrimaryMediaController.getMetadata()); - mListener.onPlaybackStateChanged( - mPrimaryMediaController == null - ? null : mPrimaryMediaController.getPlaybackState()); - } - - private void onPlaybackStateChanged(PlaybackState state) { - mListener.onPlaybackStateChanged(state); - } - - private void onMetadataChanged(MediaMetadata metadata) { - mListener.onMetadataChanged(metadata); - } - - public interface MediaStateListener { - void onPlaybackStateChanged(PlaybackState playbackState); - void onMetadataChanged(MediaMetadata metadata); - } - - - private static class MediaListener extends MediaController.Callback { - private final WeakReference<MediaStateMonitor> mServiceRef; - - MediaListener(MediaStateMonitor service) { - mServiceRef = new WeakReference<>(service); - } - - @Override - public void onPlaybackStateChanged(PlaybackState state) { - MediaStateMonitor service = mServiceRef.get(); - if (service != null) { - service.onPlaybackStateChanged(state); - } - } - - @Override - public void onMetadataChanged(MediaMetadata metadata) { - MediaStateMonitor service = mServiceRef.get(); - if (service != null) { - service.onMetadataChanged(metadata); - } - } - } -} diff --git a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/PhoneBook.java b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/PhoneBook.java deleted file mode 100644 index bcc1aea42d..0000000000 --- a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/PhoneBook.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright (C) 2016 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 android.car.cluster.demorenderer; - -import static android.provider.ContactsContract.Contacts.openContactPhotoInputStream; - -import android.annotation.Nullable; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.Context; -import android.content.CursorLoader; -import android.content.Loader; -import android.content.Loader.OnLoadCompleteListener; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.BitmapFactory.Options; -import android.graphics.Rect; -import android.net.Uri; -import android.os.AsyncTask; -import android.provider.ContactsContract; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.PhoneLookup; -import android.telephony.PhoneNumberUtils; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.util.Log; -import android.util.LruCache; - -import java.io.InputStream; -import java.lang.ref.WeakReference; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Locale; -import java.util.Set; - -/** - * Class that provides contact information. - */ -class PhoneBook { - - private final static String TAG = PhoneBook.class.getSimpleName(); - - private final ContentResolver mContentResolver; - private final Context mContext; - private final TelephonyManager mTelephonyManager; - private final Object mSyncContact = new Object(); - private final Object mSyncPhoto = new Object(); - - private volatile String mVoiceMail; - - private static final String[] CONTACT_ID_PROJECTION = new String[] { - PhoneLookup.DISPLAY_NAME, - PhoneLookup.TYPE, - PhoneLookup.LABEL, - PhoneLookup._ID - }; - - private HashMap<String, Contact> mContactByNumber; - private LruCache<Integer, Bitmap> mContactPhotoById; - private Set<Integer> mContactsWithoutImage; - - PhoneBook(Context context, TelephonyManager telephonyManager) { - mContentResolver = context.getContentResolver(); - mContext = context; - mTelephonyManager = telephonyManager; - } - - /** - * Formats provided number according to current locale. - * */ - public static String getFormattedNumber(String number) { - if (TextUtils.isEmpty(number)) { - return ""; - } - - String countryIso = Locale.getDefault().getCountry(); - if (countryIso == null || countryIso.length() != 2) { - countryIso = "US"; - } - String e164 = PhoneNumberUtils.formatNumberToE164(number, countryIso); - String formattedNumber = PhoneNumberUtils.formatNumber(number, e164, countryIso); - formattedNumber = TextUtils.isEmpty(formattedNumber) ? number : formattedNumber; - return formattedNumber; - } - - /** - * Loads contact details for a given phone number asynchronously. It may call listener's - * callback function immediately if there were image in the cache. - */ - public void getContactDetailsAsync(String number, ContactLoadedListener listener) { - if (number == null || number.isEmpty()) { - listener.onContactLoaded(number, null); - return; - } - - synchronized (mSyncContact) { - if (mContactByNumber == null) { - mContactByNumber = new HashMap<>(); - } else if (mContactByNumber.containsKey(number)) { - listener.onContactLoaded(number, mContactByNumber.get(number)); - return; - } - } - - fetchContactAsync(number, listener); - } - - /** - * Loads photo for a given contactId asynchronously. It may call listener's callback function - * immediately if there were image in the cache. - */ - public void getContactPictureAsync(int contactId, ContactPhotoLoadedListener listener) { - synchronized (mSyncPhoto) { - if (mContactsWithoutImage != null && mContactsWithoutImage.contains(contactId)) { - listener.onPhotoLoaded(contactId, null); - return; - } - - if (mContactPhotoById == null) { - mContactPhotoById = new LruCache<Integer, Bitmap>(4 << 20 /* 4mb */) { - @Override - protected int sizeOf(Integer key, Bitmap value) { - return value.getByteCount(); - } - }; - } else { - Bitmap photo = mContactPhotoById.get(contactId); - if (photo != null) { - listener.onPhotoLoaded(contactId, photo); - return; - } - } - } - - fetchPhotoAsync(contactId, listener); - } - - /** Returns true if given phone number is a voice mail number. */ - public boolean isVoicemail(String number) { - return !TextUtils.isEmpty(number) && number.equals(getVoiceMailNumber()); - } - - @Nullable - private String getVoiceMailNumber() { - if (mVoiceMail == null) { - mVoiceMail = mTelephonyManager.getVoiceMailNumber(); - } - - return mVoiceMail; - } - - interface ContactLoadedListener { - void onContactLoaded(String number, @Nullable Contact contact); - } - - interface ContactPhotoLoadedListener { - void onPhotoLoaded(int contactId, @Nullable Bitmap picture); - } - - private void fetchContactAsync(String number, ContactLoadedListener listener) { - CursorLoader cursorLoader = new CursorLoader(mContext); - cursorLoader.setUri(Uri.withAppendedPath( - PhoneLookup.CONTENT_FILTER_URI, - Uri.encode(number))); - cursorLoader.setProjection(CONTACT_ID_PROJECTION); - cursorLoader.registerListener(0, new LoadCompleteListener(this, number, listener)); - cursorLoader.startLoading(); - } - - private void fetchPhotoAsync(int contactId, ContactPhotoLoadedListener listener) { - LoadPhotoAsyncTask.createAndExecute(this, contactId, listener); - } - - private void cacheContactPhoto(int contactId, Bitmap bitmap) { - synchronized (mSyncPhoto) { - if (bitmap != null) { - mContactPhotoById.put(contactId, bitmap); - } else { - if (mContactsWithoutImage == null) { - mContactsWithoutImage = new HashSet<>(); - } - mContactsWithoutImage.add(contactId); - } - } - } - - static class Contact { - private final int mId; - private final String mName; - private final CharSequence mType; - private final String mNumber; - - Contact(Resources resources, String number, int id, String name, String label, int type) { - mNumber = number; - mId = id; - mName = name; - mType = Phone.getTypeLabel(resources, type, label); - } - - int getId() { - return mId; - } - - public String getName() { - return mName; - } - - public CharSequence getType() { - return mType; - } - - public String getNumber() { return mNumber; } - } - - private static class LoadPhotoAsyncTask extends AsyncTask<Void, Void, Bitmap> { - - private final WeakReference<PhoneBook> mPhoneBookRef; - private final ContactPhotoLoadedListener mListener; - private final int mContactId; - - static void createAndExecute(PhoneBook phoneBook, int contactId, - ContactPhotoLoadedListener listener) { - new LoadPhotoAsyncTask(phoneBook, contactId, listener) - .execute(); - } - - private LoadPhotoAsyncTask(PhoneBook phoneBook, int contactId, - ContactPhotoLoadedListener listener) { - mPhoneBookRef = new WeakReference<>(phoneBook); - mContactId = contactId; - mListener = listener; - } - - @Nullable - private Bitmap fetchBitmap(int contactId) { - Log.d(TAG, "fetchBitmap, contactId: " + contactId); - PhoneBook phoneBook = mPhoneBookRef.get(); - if (phoneBook == null) { - return null; - } - - Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId); - InputStream photoDataStream = openContactPhotoInputStream( - phoneBook.mContentResolver, uri, true); - Log.d(TAG, "fetchBitmap, uri: " + uri); - - Options options = new Options(); - options.inPreferQualityOverSpeed = true; - options.inScaled = false; - Rect nullPadding = null; - Bitmap photo = BitmapFactory.decodeStream(photoDataStream, nullPadding, options); - if (photo != null) { - photo.setDensity(Bitmap.DENSITY_NONE); - } - Log.d(TAG, "bitmap fetched: " + photo); - return photo; - } - - @Override - protected Bitmap doInBackground(Void... params) { - return fetchBitmap(mContactId); - } - - @Override - protected void onPostExecute(Bitmap bitmap) { - PhoneBook phoneBook = mPhoneBookRef.get(); - if (phoneBook != null) { - phoneBook.cacheContactPhoto(mContactId, bitmap); - } - mListener.onPhotoLoaded(0, bitmap); - } - } - - private static class LoadCompleteListener implements OnLoadCompleteListener<Cursor> { - private final String mNumber; - private final ContactLoadedListener mContactLoadedListener; - private final WeakReference<PhoneBook> mPhoneBookRef; - - private LoadCompleteListener(PhoneBook phoneBook, String number, - ContactLoadedListener contactLoadedListener) { - mPhoneBookRef = new WeakReference<>(phoneBook); - mNumber = number; - mContactLoadedListener = contactLoadedListener; - } - - @Override - public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) { - Log.d(TAG, "onLoadComplete, cursor: " + cursor); - PhoneBook phoneBook = mPhoneBookRef.get(); - Contact contact = null; - if (cursor != null && phoneBook != null) { - try { - if (cursor.moveToFirst()) { - int id = cursor.getInt(cursor.getColumnIndex(PhoneLookup._ID)); - String name = cursor - .getString(cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME)); - String label = cursor.getString(cursor.getColumnIndex(PhoneLookup.LABEL)); - int type = cursor.getInt(cursor.getColumnIndex(PhoneLookup.TYPE)); - Resources resources = phoneBook.mContext.getResources(); - contact = new Contact(resources, mNumber, id, name, label, type); - } - } finally { - cursor.close(); - } - - if (contact != null) { - synchronized (phoneBook.mSyncContact) { - phoneBook.mContactByNumber.put(mNumber, contact); - } - } - } - - mContactLoadedListener.onContactLoaded(mNumber, contact); - } - } -} diff --git a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/RendererHandler.java b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/RendererHandler.java deleted file mode 100644 index 6989a9c2d8..0000000000 --- a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/RendererHandler.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2016 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 android.car.cluster.demorenderer; - -import android.os.Handler; -import android.os.Looper; -import android.os.Message; - -import java.lang.ref.WeakReference; - -/** - * Abstract {@link Handler} class that holds reference to a renderer object. - */ -public abstract class RendererHandler<T> extends Handler { - - private final WeakReference<T> mRendererRef; - - RendererHandler(Looper looper, T renderer) { - super(looper); - mRendererRef = new WeakReference<>(renderer); - } - - @Override - public void handleMessage(Message msg) { - T renderer = mRendererRef.get(); - if (renderer != null) { - handleMessage(msg, renderer); - } - } - - public abstract void handleMessage(Message msg, T renderer); -} diff --git a/car-cluster-logging-renderer/AndroidManifest.xml b/car-cluster-logging-renderer/AndroidManifest.xml index 20ee7d190d..780298889e 100644 --- a/car-cluster-logging-renderer/AndroidManifest.xml +++ b/car-cluster-logging-renderer/AndroidManifest.xml @@ -17,5 +17,10 @@ package="android.car.cluster.loggingrenderer" android:versionCode="1" android:versionName="1.0"> - <application android:label="@string/app_name" android:icon="@drawable/ic_launcher"/> + <application android:label="@string/app_name" android:icon="@drawable/ic_launcher"> + <service android:name=".LoggingClusterRenderingService" + android:exported="false" + android:permission="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE" + /> + </application> </manifest> diff --git a/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingInstrumentClusterRenderer.java b/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingClusterRenderingService.java index 18757924fe..b2ac999e7b 100644 --- a/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingInstrumentClusterRenderer.java +++ b/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingClusterRenderingService.java @@ -15,37 +15,21 @@ */ package android.car.cluster.loggingrenderer; -import android.car.cluster.renderer.InstrumentClusterRenderer; +import android.car.cluster.renderer.InstrumentClusterRenderingService; import android.car.cluster.renderer.NavigationRenderer; import android.car.navigation.CarNavigationInstrumentCluster; -import android.content.Context; import android.graphics.Bitmap; import android.util.Log; /** - * Dummy implementation of {@code InstrumentClusterRenderer} that just traces all interaction. + * Dummy implementation of {@link LoggingClusterRenderingService} to log all interaction. */ -public class LoggingInstrumentClusterRenderer extends InstrumentClusterRenderer { +public class LoggingClusterRenderingService extends InstrumentClusterRenderingService { - private final static String TAG = LoggingInstrumentClusterRenderer.class.getSimpleName(); + private static final String TAG = LoggingClusterRenderingService.class.getSimpleName(); @Override - public void onCreate(Context context) { - Log.i(TAG, "onCreate, context: " + context); - } - - @Override - public void onStart() { - Log.i(TAG, "onStart"); - } - - @Override - public void onStop() { - Log.i(TAG, "onStop"); - } - - @Override - protected NavigationRenderer createNavigationRenderer() { + protected NavigationRenderer getNavigationRenderer() { NavigationRenderer navigationRenderer = new NavigationRenderer() { @Override public CarNavigationInstrumentCluster getNavigationProperties() { diff --git a/car-lib/api/current.txt b/car-lib/api/current.txt index f7d48bd5fb..a0ffa8357d 100644 --- a/car-lib/api/current.txt +++ b/car-lib/api/current.txt @@ -9,7 +9,7 @@ package android.car { method public java.lang.Object getCarManager(java.lang.String) throws android.car.CarNotConnectedException; method public boolean isConnected(); method public boolean isConnecting(); - field public static final java.lang.String APP_CONTEXT_SERVICE = "app_context"; + field public static final java.lang.String APP_FOCUS_SERVICE = "app_focus"; field public static final java.lang.String AUDIO_SERVICE = "audio"; field public static final int CONNECTION_TYPE_EMBEDDED = 5; // 0x5 field public static final java.lang.String INFO_SERVICE = "info"; @@ -19,6 +19,30 @@ package android.car { field public static final java.lang.String PERMISSION_MILEAGE = "android.car.permission.CAR_MILEAGE"; field public static final java.lang.String PERMISSION_SPEED = "android.car.permission.CAR_SPEED"; field public static final java.lang.String SENSOR_SERVICE = "sensor"; + field public static final int VERSION = 1; // 0x1 + } + + public final class CarAppFocusManager { + method public void abandonAppFocus(android.car.CarAppFocusManager.AppFocusOwnershipChangeListener, int) throws android.car.CarNotConnectedException; + method public void abandonAppFocus(android.car.CarAppFocusManager.AppFocusOwnershipChangeListener) throws android.car.CarNotConnectedException; + method public int[] getActiveAppTypes() throws android.car.CarNotConnectedException; + method public boolean isOwningFocus(android.car.CarAppFocusManager.AppFocusOwnershipChangeListener, int) throws android.car.CarNotConnectedException; + method public void registerFocusListener(android.car.CarAppFocusManager.AppFocusChangeListener, int) throws android.car.CarNotConnectedException; + method public int requestAppFocus(android.car.CarAppFocusManager.AppFocusOwnershipChangeListener, int) throws android.car.CarNotConnectedException, java.lang.SecurityException; + method public void unregisterFocusListener(android.car.CarAppFocusManager.AppFocusChangeListener, int) throws android.car.CarNotConnectedException; + method public void unregisterFocusListener(android.car.CarAppFocusManager.AppFocusChangeListener) throws android.car.CarNotConnectedException; + field public static final int APP_FOCUS_REQUEST_FAILED = 0; // 0x0 + field public static final int APP_FOCUS_REQUEST_GRANTED = 1; // 0x1 + field public static final int APP_FOCUS_TYPE_NAVIGATION = 1; // 0x1 + field public static final int APP_FOCUS_TYPE_VOICE_COMMAND = 2; // 0x2 + } + + public static abstract interface CarAppFocusManager.AppFocusChangeListener { + method public abstract void onAppFocusChange(int, boolean); + } + + public static abstract interface CarAppFocusManager.AppFocusOwnershipChangeListener { + method public abstract void onAppFocusOwnershipLoss(int); } public class CarInfoManager { diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt index b83b20614c..baa7fae570 100644 --- a/car-lib/api/system-current.txt +++ b/car-lib/api/system-current.txt @@ -9,7 +9,7 @@ package android.car { method public java.lang.Object getCarManager(java.lang.String) throws android.car.CarNotConnectedException; method public boolean isConnected(); method public boolean isConnecting(); - field public static final java.lang.String APP_CONTEXT_SERVICE = "app_context"; + field public static final java.lang.String APP_FOCUS_SERVICE = "app_focus"; field public static final java.lang.String AUDIO_SERVICE = "audio"; field public static final java.lang.String CAMERA_SERVICE = "camera"; field public static final int CONNECTION_TYPE_EMBEDDED = 5; // 0x5 @@ -31,6 +31,30 @@ package android.car { field public static final java.lang.String RADIO_SERVICE = "radio"; field public static final java.lang.String SENSOR_SERVICE = "sensor"; field public static final java.lang.String TEST_SERVICE = "car-service-test"; + field public static final int VERSION = 1; // 0x1 + } + + public final class CarAppFocusManager { + method public void abandonAppFocus(android.car.CarAppFocusManager.AppFocusOwnershipChangeListener, int) throws android.car.CarNotConnectedException; + method public void abandonAppFocus(android.car.CarAppFocusManager.AppFocusOwnershipChangeListener) throws android.car.CarNotConnectedException; + method public int[] getActiveAppTypes() throws android.car.CarNotConnectedException; + method public boolean isOwningFocus(android.car.CarAppFocusManager.AppFocusOwnershipChangeListener, int) throws android.car.CarNotConnectedException; + method public void registerFocusListener(android.car.CarAppFocusManager.AppFocusChangeListener, int) throws android.car.CarNotConnectedException; + method public int requestAppFocus(android.car.CarAppFocusManager.AppFocusOwnershipChangeListener, int) throws android.car.CarNotConnectedException, java.lang.SecurityException; + method public void unregisterFocusListener(android.car.CarAppFocusManager.AppFocusChangeListener, int) throws android.car.CarNotConnectedException; + method public void unregisterFocusListener(android.car.CarAppFocusManager.AppFocusChangeListener) throws android.car.CarNotConnectedException; + field public static final int APP_FOCUS_REQUEST_FAILED = 0; // 0x0 + field public static final int APP_FOCUS_REQUEST_GRANTED = 1; // 0x1 + field public static final int APP_FOCUS_TYPE_NAVIGATION = 1; // 0x1 + field public static final int APP_FOCUS_TYPE_VOICE_COMMAND = 2; // 0x2 + } + + public static abstract interface CarAppFocusManager.AppFocusChangeListener { + method public abstract void onAppFocusChange(int, boolean); + } + + public static abstract interface CarAppFocusManager.AppFocusOwnershipChangeListener { + method public abstract void onAppFocusOwnershipLoss(int); } public class CarInfoManager { @@ -289,6 +313,13 @@ package android.car.cluster.renderer { method public abstract void onStop(); } + public abstract class InstrumentClusterRenderingService extends android.app.Service { + ctor public InstrumentClusterRenderingService(); + method protected abstract android.car.cluster.renderer.NavigationRenderer getNavigationRenderer(); + method public android.os.IBinder onBind(android.content.Intent); + method protected void onKeyEvent(android.view.KeyEvent); + } + public abstract class NavigationRenderer { ctor public NavigationRenderer(); method public abstract android.car.navigation.CarNavigationInstrumentCluster getNavigationProperties(); @@ -337,6 +368,7 @@ package android.car.content.pm { public class CarPackageManager { method public boolean isActivityAllowedWhileDriving(java.lang.String, java.lang.String) throws android.car.CarNotConnectedException; + method public boolean isActivityBackedBySafeActivity(android.content.ComponentName) throws android.car.CarNotConnectedException; method public boolean isServiceAllowedWhileDriving(java.lang.String, java.lang.String) throws android.car.CarNotConnectedException; method public void setAppBlockingPolicy(java.lang.String, android.car.content.pm.CarAppBlockingPolicy, int) throws android.car.CarNotConnectedException, java.lang.IllegalArgumentException, java.lang.SecurityException; field public static final int FLAG_SET_POLICY_ADD = 2; // 0x2 @@ -596,8 +628,6 @@ package android.car.hardware.hvac { method public void setFloatProperty(int, int, float) throws android.car.CarNotConnectedException; method public void setIntProperty(int, int, int) throws android.car.CarNotConnectedException; method public synchronized void unregisterListener(android.car.hardware.hvac.CarHvacManager.CarHvacEventListener) throws android.car.CarNotConnectedException; - field public static final boolean DBG = true; - field public static final java.lang.String TAG = "CarHvacManager"; } public static abstract interface CarHvacManager.CarHvacEventListener { diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java index dd0757450e..39dbb0b8e2 100644 --- a/car-lib/src/android/car/Car.java +++ b/car-lib/src/android/car/Car.java @@ -52,6 +52,11 @@ import java.util.HashMap; */ public class Car { + /** + * Represent the version of Car API. + */ + public static final int VERSION = 1; + /** Service name for {@link CarSensorManager}, to be used in {@link #getCarManager(String)}. */ public static final String SENSOR_SERVICE = "sensor"; @@ -59,7 +64,7 @@ public class Car { public static final String INFO_SERVICE = "info"; /** Service name for {@link CarAppContextManager}. */ - public static final String APP_CONTEXT_SERVICE = "app_context"; + public static final String APP_FOCUS_SERVICE = "app_focus"; /** Service name for {@link CarPackageManager} */ public static final String PACKAGE_SERVICE = "package"; @@ -415,7 +420,7 @@ public class Car { * @throws CarNotConnectedException */ public Object getCarManager(String serviceName) throws CarNotConnectedException { - CarManagerBase manager = null; + CarManagerBase manager; ICar service = getICarOrThrow(); synchronized (mCarManagerLock) { manager = mServiceMap.get(serviceName); @@ -431,7 +436,7 @@ public class Car { if (manager == null) { Log.w(CarLibLog.TAG_CAR, "getCarManager could not create manager for service:" + - serviceName); + serviceName); return null; } mServiceMap.put(serviceName, manager); @@ -484,14 +489,14 @@ public class Car { case INFO_SERVICE: manager = new CarInfoManager(binder); break; - case APP_CONTEXT_SERVICE: - manager = new CarAppContextManager(binder, mLooper); + case APP_FOCUS_SERVICE: + manager = new CarAppFocusManager(binder, mLooper); break; case PACKAGE_SERVICE: manager = new CarPackageManager(binder, mContext); break; case CAR_NAVIGATION_SERVICE: - manager = new CarNavigationManager(binder, mLooper); + manager = new CarNavigationManager(binder); break; case CAMERA_SERVICE: manager = new CarCameraManager(binder, mContext); diff --git a/car-lib/src/android/car/CarAppContextManager.java b/car-lib/src/android/car/CarAppContextManager.java deleted file mode 100644 index e73952ef2a..0000000000 --- a/car-lib/src/android/car/CarAppContextManager.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright (C) 2015 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 android.car; - -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.RemoteException; - -import java.lang.ref.WeakReference; -import java.util.HashMap; -import java.util.Map; - -/** - * CarAppContextManager allows applications to set and listen for the current application context - * like active navigation or active voice command. Usually only one instance of such application - * should run in the system, and other app setting the flag for the matching app should - * lead into other app to stop. - * @hide - */ -public class CarAppContextManager implements CarManagerBase { - /** - * Listener to get notification for app getting information on app context change. - */ - public interface AppContextChangeListener { - /** - * Application context has changed. Note that {@link CarAppContextManager} instance - * causing the change will not get this notification. - * @param activeContexts - */ - void onAppContextChange(int activeContexts); - } - - /** - * Listener to get notification for app getting information on app context ownership loss. - */ - public interface AppContextOwnershipChangeListener { - /** - * Lost ownership for the context, which happens when other app has set the context. - * The app losing context should stop the action associated with the context. - * For example, navigation app currently running active navigation should stop navigation - * upon getting this for {@link CarAppContextManager#APP_CONTEXT_NAVIGATION}. - * @param context - */ - void onAppContextOwnershipLoss(int context); - } - - /** @hide */ - public static final int APP_CONTEXT_START_FLAG = 0x1; - /** - * Flag for active navigation. - */ - public static final int APP_CONTEXT_NAVIGATION = 0x1; - /** - * Flag for active voice command. - */ - public static final int APP_CONTEXT_VOICE_COMMAND = 0x2; - /** - * Update this after adding a new flag. - * @hide - */ - public static final int APP_CONTEXT_END_FLAG = 0x2; - - private final IAppContext mService; - private final Handler mHandler; - private final IAppContextListenerImpl mBinderListener; - private final Map<Integer, AppContextOwnershipChangeListener> mOwnershipListeners; - - private AppContextChangeListener mListener; - private int mContextFilter; - - /** - * @hide - */ - CarAppContextManager(IBinder service, Looper looper) { - mService = IAppContext.Stub.asInterface(service); - mHandler = new Handler(looper); - mBinderListener = new IAppContextListenerImpl(this); - mOwnershipListeners = new HashMap<>(); - } - - /** - * Register listener to monitor app context change. Only one listener can be registered and - * registering multiple times will lead into only the last listener to be active. - * @param listener - * @param contextFilter Flags of cotexts to get notification. - * @throws CarNotConnectedException - */ - public void registerContextListener(AppContextChangeListener listener, int contextFilter) - throws CarNotConnectedException { - if (listener == null) { - throw new IllegalArgumentException("null listener"); - } - synchronized(this) { - if (mListener == null || mContextFilter != contextFilter) { - try { - mService.registerContextListener(mBinderListener, contextFilter); - } catch (RemoteException e) { - throw new CarNotConnectedException(e); - } - } - mListener = listener; - mContextFilter = contextFilter; - } - } - - /** - * Unregister listener and stop listening context change events. If app has owned a context - * by {@link #setActiveContext(int)}, it will be reset to inactive state. - * @throws CarNotConnectedException - */ - public void unregisterContextListener() throws CarNotConnectedException { - synchronized(this) { - try { - mService.unregisterContextListener(mBinderListener); - } catch (RemoteException e) { - throw new CarNotConnectedException(e); - } - mListener = null; - mContextFilter = 0; - } - } - - public int getActiveAppContexts() throws CarNotConnectedException { - try { - return mService.getActiveAppContexts(); - } catch (RemoteException e) { - throw new CarNotConnectedException(e); - } - } - - public boolean isOwningContext(int context) throws CarNotConnectedException { - try { - return mService.isOwningContext(mBinderListener, context); - } catch (RemoteException e) { - throw new CarNotConnectedException(e); - } - } - - /** - * Set the given contexts as active. By setting this, the application is becoming owner - * of the context, and will get - * {@link AppContextOwnershipChangeListener#onAppContextOwnershipLoss(int)} - * if ownership is given to other app by calling this. Fore-ground app will have higher priority - * and other app cannot set the same context while owner is in fore-ground. - * Only one listener per context can be registered and - * registering multiple times will lead into only the last listener to be active. - * @param ownershipListener - * @param contexts - * @throws CarNotConnectedException - * @throws SecurityException If owner cannot be changed. - */ - public void setActiveContexts(AppContextOwnershipChangeListener ownershipListener, int contexts) - throws SecurityException, CarNotConnectedException { - if (ownershipListener == null) { - throw new IllegalArgumentException("null listener"); - } - synchronized (this) { - try { - mService.setActiveContexts(mBinderListener, contexts); - } catch (RemoteException e) { - throw new CarNotConnectedException(e); - } - for (int flag = APP_CONTEXT_START_FLAG; flag <= APP_CONTEXT_END_FLAG; flag <<= 1) { - if ((flag & contexts) != 0) { - mOwnershipListeners.put(flag, ownershipListener); - } - } - } - } - - /** - * Reset the given contexts, i.e. mark them as inactive. This also involves releasing ownership - * for the context. - * @param contexts - * @throws CarNotConnectedException - */ - public void resetActiveContexts(int contexts) throws CarNotConnectedException { - try { - mService.resetActiveContexts(mBinderListener, contexts); - } catch (RemoteException e) { - throw new CarNotConnectedException(e); - } - synchronized (this) { - for (int flag = APP_CONTEXT_START_FLAG; flag <= APP_CONTEXT_END_FLAG; flag <<= 1) { - if ((flag & contexts) != 0) { - mOwnershipListeners.remove(flag); - } - } - } - } - - @Override - public void onCarDisconnected() { - // nothing to do - } - - private void handleAppContextChange(int activeContexts) { - AppContextChangeListener listener; - int newContext; - synchronized (this) { - if (mListener == null) { - return; - } - listener = mListener; - newContext = activeContexts & mContextFilter; - } - listener.onAppContextChange(newContext); - } - - private void handleAppContextOwnershipLoss(int context) { - AppContextOwnershipChangeListener listener; - synchronized (this) { - listener = mOwnershipListeners.get(context); - if (listener == null) { - return; - } - } - listener.onAppContextOwnershipLoss(context); - } - - private static class IAppContextListenerImpl extends IAppContextListener.Stub { - - private final WeakReference<CarAppContextManager> mManager; - - private IAppContextListenerImpl(CarAppContextManager manager) { - mManager = new WeakReference<>(manager); - } - - @Override - public void onAppContextChange(final int activeContexts) { - final CarAppContextManager manager = mManager.get(); - if (manager == null) { - return; - } - manager.mHandler.post(new Runnable() { - @Override - public void run() { - manager.handleAppContextChange(activeContexts); - } - }); - } - - @Override - public void onAppContextOwnershipLoss(final int context) { - final CarAppContextManager manager = mManager.get(); - if (manager == null) { - return; - } - manager.mHandler.post(new Runnable() { - @Override - public void run() { - manager.handleAppContextOwnershipLoss(context); - } - }); - } - } -} diff --git a/car-lib/src/android/car/CarAppFocusManager.java b/car-lib/src/android/car/CarAppFocusManager.java new file mode 100644 index 0000000000..8697dd2a46 --- /dev/null +++ b/car-lib/src/android/car/CarAppFocusManager.java @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2015 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 android.car; + +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; + +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * CarAppFocusManager allows applications to set and listen for the current application focus + * like active navigation or active voice command. Usually only one instance of such application + * should run in the system, and other app setting the flag for the matching app should + * lead into other app to stop. + */ +public final class CarAppFocusManager implements CarManagerBase { + /** + * Listener to get notification for app getting information on application type status changes. + */ + public interface AppFocusChangeListener { + /** + * Application focus has changed. Note that {@link CarAppFocusManager} instance + * causing the change will not get this notification. + * @param appType + * @param active + */ + void onAppFocusChange(int appType, boolean active); + } + + /** + * Listener to get notification for app getting information on app type ownership loss. + */ + public interface AppFocusOwnershipChangeListener { + /** + * Lost ownership for the focus, which happens when other app has set the focus. + * The app losing focus should stop the action associated with the focus. + * For example, navigation app currently running active navigation should stop navigation + * upon getting this for {@link CarAppFocusManager#APP_FOCUS_TYPE_NAVIGATION}. + * @param appType + */ + void onAppFocusOwnershipLoss(int appType); + } + + /** + * Represents navigation focus. + */ + public static final int APP_FOCUS_TYPE_NAVIGATION = 1; + /** + * Represents voice command focus. + */ + public static final int APP_FOCUS_TYPE_VOICE_COMMAND = 2; + /** + * Update this after adding a new app type. + * @hide + */ + public static final int APP_FOCUS_MAX = 2; + + /** + * A failed focus change request. + */ + public static final int APP_FOCUS_REQUEST_FAILED = 0; + /** + * A successful focus change request. + */ + public static final int APP_FOCUS_REQUEST_GRANTED = 1; + + private final IAppFocus mService; + private final Handler mHandler; + private final Map<AppFocusChangeListener, IAppFocusListenerImpl> mChangeBinders = + new HashMap<>(); + private final Map<AppFocusOwnershipChangeListener, IAppFocusOwnershipListenerImpl> + mOwnershipBinders = new HashMap<>(); + + /** + * @hide + */ + CarAppFocusManager(IBinder service, Looper looper) { + mService = IAppFocus.Stub.asInterface(service); + mHandler = new Handler(looper); + } + + /** + * Register listener to monitor app focus change. + * @param listener + * @param appType Applitcaion type to get notification for. + * @throws CarNotConnectedException + */ + public void registerFocusListener(AppFocusChangeListener listener, int appType) + throws CarNotConnectedException { + if (listener == null) { + throw new IllegalArgumentException("null listener"); + } + IAppFocusListenerImpl binder; + synchronized (this) { + binder = mChangeBinders.get(listener); + if (binder == null) { + binder = new IAppFocusListenerImpl(this, listener); + mChangeBinders.put(listener, binder); + } + binder.addAppType(appType); + } + try { + mService.registerFocusListener(binder, appType); + } catch (RemoteException e) { + throw new CarNotConnectedException(e); + } + } + + /** + * Unregister listener for application type and stop listening focus change events. + * @param listener + * @param appType + * @throws CarNotConnectedException + */ + public void unregisterFocusListener(AppFocusChangeListener listener, int appType) + throws CarNotConnectedException { + IAppFocusListenerImpl binder; + synchronized (this) { + binder = mChangeBinders.get(listener); + if (binder == null) { + return; + } + } + try { + mService.unregisterFocusListener(binder, appType); + } catch (RemoteException e) { + throw new CarNotConnectedException(e); + } + synchronized (this) { + binder.removeAppType(appType); + if (!binder.hasAppTypes()) { + mChangeBinders.remove(listener); + } + + } + } + + /** + * Unregister listener and stop listening focus change events. + * @param listener + * @throws CarNotConnectedException + */ + public void unregisterFocusListener(AppFocusChangeListener listener) + throws CarNotConnectedException { + IAppFocusListenerImpl binder; + synchronized (this) { + binder = mChangeBinders.remove(listener); + if (binder == null) { + return; + } + } + try { + for (Integer appType : binder.getAppTypes()) { + mService.unregisterFocusListener(binder, appType); + } + } catch (RemoteException e) { + throw new CarNotConnectedException(e); + } + } + + /** + * Returns application types currently active in the system. + * @throws CarNotConnectedException + */ + public int[] getActiveAppTypes() throws CarNotConnectedException { + try { + return mService.getActiveAppTypes(); + } catch (RemoteException e) { + throw new CarNotConnectedException(e); + } + } + + /** + * Checks if listener is associated with active a focus + * @param listener + * @param focus + * @throws CarNotConnectedException + */ + public boolean isOwningFocus(AppFocusOwnershipChangeListener listener, int appType) + throws CarNotConnectedException { + IAppFocusOwnershipListenerImpl binder; + synchronized (this) { + binder = mOwnershipBinders.get(listener); + if (binder == null) { + return false; + } + } + try { + return mService.isOwningFocus(binder, appType); + } catch (RemoteException e) { + throw new CarNotConnectedException(e); + } + } + + /** + * Requests application focus. + * By requesting this, the application is becoming owner of the focus, and will get + * {@link AppFocusOwnershipChangeListener#onAppFocusOwnershipLoss(int)} + * if ownership is given to other app by calling this. Fore-ground app will have higher priority + * and other app cannot set the same focus while owner is in fore-ground. + * @param ownershipListener + * @param appType + * @return {@link #APP_FOCUS_REQUEST_FAILED} or {@link #APP_FOCUS_REQUEST_GRANTED} + * @throws CarNotConnectedException + * @throws SecurityException If owner cannot be changed. + */ + public int requestAppFocus(AppFocusOwnershipChangeListener ownershipListener, int appType) + throws SecurityException, CarNotConnectedException { + if (ownershipListener == null) { + throw new IllegalArgumentException("null listener"); + } + IAppFocusOwnershipListenerImpl binder; + synchronized (this) { + binder = mOwnershipBinders.get(ownershipListener); + if (binder == null) { + binder = new IAppFocusOwnershipListenerImpl(this, ownershipListener); + mOwnershipBinders.put(ownershipListener, binder); + } + binder.addAppType(appType); + } + try { + return mService.requestAppFocus(binder, appType); + } catch (RemoteException e) { + throw new CarNotConnectedException(e); + } + } + + /** + * Abandon the given focus, i.e. mark it as inactive. This also involves releasing ownership + * for the focus. + * @param ownershipListener + * @param appType + * @throws CarNotConnectedException + */ + public void abandonAppFocus(AppFocusOwnershipChangeListener ownershipListener, int appType) + throws CarNotConnectedException { + if (ownershipListener == null) { + throw new IllegalArgumentException("null listener"); + } + IAppFocusOwnershipListenerImpl binder; + synchronized (this) { + binder = mOwnershipBinders.get(ownershipListener); + if (binder == null) { + return; + } + } + try { + mService.abandonAppFocus(binder, appType); + } catch (RemoteException e) { + throw new CarNotConnectedException(e); + } + synchronized (this) { + binder.removeAppType(appType); + if (!binder.hasAppTypes()) { + mOwnershipBinders.remove(ownershipListener); + } + } + } + + /** + * Abandon all focuses, i.e. mark them as inactive. This also involves releasing ownership + * for the focus. + * @param ownershipListener + * @throws CarNotConnectedException + */ + public void abandonAppFocus(AppFocusOwnershipChangeListener ownershipListener) + throws CarNotConnectedException { + IAppFocusOwnershipListenerImpl binder; + synchronized (this) { + binder = mOwnershipBinders.remove(ownershipListener); + if (binder == null) { + return; + } + } + try { + for (Integer appType : binder.getAppTypes()) { + mService.abandonAppFocus(binder, appType); + } + } catch (RemoteException e) { + throw new CarNotConnectedException(e); + } + } + + /** @hide */ + @Override + public void onCarDisconnected() { + // nothing to do + } + + private static class IAppFocusListenerImpl extends IAppFocusListener.Stub { + + private final WeakReference<CarAppFocusManager> mManager; + private final WeakReference<AppFocusChangeListener> mListener; + private final Set<Integer> mAppTypes = new HashSet<>(); + + private IAppFocusListenerImpl(CarAppFocusManager manager, AppFocusChangeListener listener) { + mManager = new WeakReference<>(manager); + mListener = new WeakReference<>(listener); + } + + public void addAppType(int appType) { + mAppTypes.add(appType); + } + + public void removeAppType(int appType) { + mAppTypes.remove(appType); + } + + public Set<Integer> getAppTypes() { + return mAppTypes; + } + + public boolean hasAppTypes() { + return !mAppTypes.isEmpty(); + } + + @Override + public void onAppFocusChange(final int appType, final boolean active) { + final CarAppFocusManager manager = mManager.get(); + final AppFocusChangeListener listener = mListener.get(); + if (manager == null || listener == null) { + return; + } + manager.mHandler.post(new Runnable() { + @Override + public void run() { + listener.onAppFocusChange(appType, active); + } + }); + } + } + + private static class IAppFocusOwnershipListenerImpl extends IAppFocusOwnershipListener.Stub { + + private final WeakReference<CarAppFocusManager> mManager; + private final WeakReference<AppFocusOwnershipChangeListener> mListener; + private final Set<Integer> mAppTypes = new HashSet<>(); + + private IAppFocusOwnershipListenerImpl(CarAppFocusManager manager, + AppFocusOwnershipChangeListener listener) { + mManager = new WeakReference<>(manager); + mListener = new WeakReference<>(listener); + } + + public void addAppType(int appType) { + mAppTypes.add(appType); + } + + public void removeAppType(int appType) { + mAppTypes.remove(appType); + } + + public Set<Integer> getAppTypes() { + return mAppTypes; + } + + public boolean hasAppTypes() { + return !mAppTypes.isEmpty(); + } + + @Override + public void onAppFocusOwnershipLoss(final int appType) { + final CarAppFocusManager manager = mManager.get(); + final AppFocusOwnershipChangeListener listener = mListener.get(); + if (manager == null || listener == null) { + return; + } + manager.mHandler.post(new Runnable() { + @Override + public void run() { + listener.onAppFocusOwnershipLoss(appType); + } + }); + } + } +} diff --git a/car-lib/src/android/car/CarLibLog.java b/car-lib/src/android/car/CarLibLog.java index 4817a36e6a..0a78216923 100644 --- a/car-lib/src/android/car/CarLibLog.java +++ b/car-lib/src/android/car/CarLibLog.java @@ -21,4 +21,5 @@ public class CarLibLog { public static final String TAG_CAR = "CAR.L"; public static final String TAG_SENSOR = TAG_CAR + ".SENSOR"; public static final String TAG_NAV = TAG_CAR + ".NAV"; + public static final String TAG_CLUSTER = TAG_CAR + ".CLUSTER"; } diff --git a/car-lib/src/android/car/IAppContext.aidl b/car-lib/src/android/car/IAppFocus.aidl index 5f63c9b58f..1888c246ec 100644 --- a/car-lib/src/android/car/IAppContext.aidl +++ b/car-lib/src/android/car/IAppFocus.aidl @@ -16,17 +16,18 @@ package android.car; -import android.car.IAppContextListener; +import android.car.IAppFocusListener; +import android.car.IAppFocusOwnershipListener; /** @hide */ -interface IAppContext { - void registerContextListener(IAppContextListener listener, int filter) = 0; - void unregisterContextListener(IAppContextListener listener) = 1; - int getActiveAppContexts() = 2; +interface IAppFocus { + void registerFocusListener(IAppFocusListener listener, int appType) = 0; + void unregisterFocusListener(IAppFocusListener listener, int appType) = 1; + int[] getActiveAppTypes() = 2; /** listener used as a token */ - boolean isOwningContext(IAppContextListener listener, int context) = 3; + boolean isOwningFocus(IAppFocusOwnershipListener listener, int appType) = 3; /** listener used as a token */ - void setActiveContexts(IAppContextListener listener, int contexts) = 4; + int requestAppFocus(IAppFocusOwnershipListener listener, int appType) = 4; /** listener used as a token */ - void resetActiveContexts(IAppContextListener listener, int contexts) = 5; + void abandonAppFocus(IAppFocusOwnershipListener listener, int appType) = 5; } diff --git a/car-lib/src/android/car/hardware/hvac/CarHvacEvent.aidl b/car-lib/src/android/car/IAppFocusListener.aidl index 801d0d33df..b8db6ff025 100644 --- a/car-lib/src/android/car/hardware/hvac/CarHvacEvent.aidl +++ b/car-lib/src/android/car/IAppFocusListener.aidl @@ -14,7 +14,9 @@ * limitations under the License. */ -package android.car.hardware.hvac; - -parcelable CarHvacEvent; +package android.car; +/** @hide */ +oneway interface IAppFocusListener { + void onAppFocusChange(int appType, boolean active) = 0; +} diff --git a/car-lib/src/android/car/IAppContextListener.aidl b/car-lib/src/android/car/IAppFocusOwnershipListener.aidl index 0fdf103228..6ee27232fe 100644 --- a/car-lib/src/android/car/IAppContextListener.aidl +++ b/car-lib/src/android/car/IAppFocusOwnershipListener.aidl @@ -17,7 +17,6 @@ package android.car; /** @hide */ -oneway interface IAppContextListener { - void onAppContextChange(int activeContexts) = 0; - void onAppContextOwnershipLoss(int context) = 1; +oneway interface IAppFocusOwnershipListener { + void onAppFocusOwnershipLoss(int appType) = 0; } diff --git a/car-lib/src/android/car/cluster/renderer/IInstrumentCluster.aidl b/car-lib/src/android/car/cluster/renderer/IInstrumentCluster.aidl new file mode 100644 index 0000000000..9fb4b56587 --- /dev/null +++ b/car-lib/src/android/car/cluster/renderer/IInstrumentCluster.aidl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016 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 android.car.cluster.renderer; + +import android.car.cluster.renderer.IInstrumentClusterNavigation; +import android.view.KeyEvent; + +/** + * Binder API for Instrument Cluster. + * + * @hide + */ +interface IInstrumentCluster { + /** Returns {@link IInstrumentClusterNavigation} that will be passed to the Nav app */ + IInstrumentClusterNavigation getNavigationService(); + + /** Supplies Instrument Cluster Renderer with current owner of Navigation app context */ + void setNavigationContextOwner(int uid, int pid); + + /** Called when key event that was addressed to instrument cluster display has been received. */ + void onKeyEvent(in KeyEvent keyEvent); +} diff --git a/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/InstrumentClusterRendererFactory.java b/car-lib/src/android/car/cluster/renderer/IInstrumentClusterNavigation.aidl index d73ede440e..fcd453239a 100644 --- a/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/InstrumentClusterRendererFactory.java +++ b/car-lib/src/android/car/cluster/renderer/IInstrumentClusterNavigation.aidl @@ -13,17 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.car.cluster.loggingrenderer; +package android.car.cluster.renderer; -import android.car.cluster.renderer.InstrumentClusterRenderer; +import android.graphics.Bitmap; +import android.car.navigation.CarNavigationInstrumentCluster; /** - * This factory class will be requested by android.car using reflection in order to get an instance - * of {@link InstrumentClusterRenderer}. + * Binder API for Instrument Cluster Navigation. + * + * @hide */ -@SuppressWarnings("unused") /* Used by reflection. */ -public class InstrumentClusterRendererFactory { - public static InstrumentClusterRenderer createRenderer() { - return new LoggingInstrumentClusterRenderer(); - } +interface IInstrumentClusterNavigation { + void onStartNavigation(); + void onStopNavigation(); + void onNextManeuverChanged( + int event, String road, int turnAngle, int turnNumber, in Bitmap image, int turnSide); + void onNextManeuverDistanceChanged(int distanceMeters, int timeSeconds); + CarNavigationInstrumentCluster getInstrumentClusterInfo(); } diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java new file mode 100644 index 0000000000..137e6fc6c4 --- /dev/null +++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderingService.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2016 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 android.car.cluster.renderer; + +import android.annotation.CallSuper; +import android.annotation.MainThread; +import android.annotation.SystemApi; +import android.app.Service; +import android.car.CarLibLog; +import android.car.navigation.CarNavigationInstrumentCluster; +import android.content.Intent; +import android.graphics.Bitmap; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; +import android.util.Pair; +import android.view.KeyEvent; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.lang.ref.WeakReference; + +/** + * A service that used for interaction between Car Service and Instrument Cluster. Car Service may + * provide internal navigation binder interface to Navigation App and all notifications will be + * eventually land in the {@link NavigationRenderer} returned by {@link #getNavigationRenderer()}. + * + * <p>To extend this class, you must declare the service in your manifest file with + * the {@code android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE} permission + * <pre> + * <service android:name=".MyInstrumentClusterService" + * android:permission="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"> + * </service></pre> + * <p>Also, you will need to register this service in the following configuration file: + * {@code packages/services/Car/service/res/values/config.xml} + * + * @hide + */ +@SystemApi +public abstract class InstrumentClusterRenderingService extends Service { + + private static final String TAG = CarLibLog.TAG_CLUSTER; + + private RendererBinder mRendererBinder; + + @Override + @CallSuper + public IBinder onBind(Intent intent) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "onBind, intent: " + intent); + } + + if (mRendererBinder == null) { + mRendererBinder = new RendererBinder(getNavigationRenderer()); + } + + return mRendererBinder; + } + + /** Returns {@link NavigationRenderer} or null if it's not supported. */ + @MainThread + protected abstract NavigationRenderer getNavigationRenderer(); + + /** Called when key event that was addressed to instrument cluster display has been received. */ + @MainThread + protected void onKeyEvent(KeyEvent keyEvent) { + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + writer.println("**" + getClass().getSimpleName() + "**"); + writer.println("renderer binder: " + mRendererBinder); + if (mRendererBinder != null) { + writer.println("navigation renderer: " + mRendererBinder.mNavigationRenderer); + String owner = "none"; + if (mRendererBinder.mNavContextOwner != null) { + owner = "[uid: " + mRendererBinder.mNavContextOwner.first + + ", pid: " + mRendererBinder.mNavContextOwner.second + "]"; + } + writer.println("navigation focus owner: " + owner); + } + } + + private class RendererBinder extends IInstrumentCluster.Stub { + + private final NavigationRenderer mNavigationRenderer; + private final UiHandler mUiHandler; + + private volatile NavigationBinder mNavigationBinder; + private volatile Pair<Integer, Integer> mNavContextOwner; + + RendererBinder(NavigationRenderer navigationRenderer) { + mNavigationRenderer = navigationRenderer; + mUiHandler = new UiHandler(InstrumentClusterRenderingService.this); + } + + @Override + public IInstrumentClusterNavigation getNavigationService() throws RemoteException { + if (mNavigationBinder == null) { + mNavigationBinder = new NavigationBinder(mNavigationRenderer); + if (mNavContextOwner != null) { + mNavigationBinder.setNavigationContextOwner( + mNavContextOwner.first, mNavContextOwner.second); + } + } + return mNavigationBinder; + } + + @Override + public void setNavigationContextOwner(int uid, int pid) throws RemoteException { + mNavContextOwner = new Pair<>(uid, pid); + if (mNavigationBinder != null) { + mNavigationBinder.setNavigationContextOwner(uid, pid); + } + } + + @Override + public void onKeyEvent(KeyEvent keyEvent) throws RemoteException { + mUiHandler.doKeyEvent(keyEvent); + } + } + + private class NavigationBinder extends IInstrumentClusterNavigation.Stub { + + private final NavigationRenderer mNavigationRenderer; // Thread-safe navigation renderer. + + private volatile Pair<Integer, Integer> mNavContextOwner; + + NavigationBinder(NavigationRenderer navigationRenderer) { + mNavigationRenderer = ThreadSafeNavigationRenderer.createFor( + Looper.getMainLooper(), + navigationRenderer); + } + + void setNavigationContextOwner(int uid, int pid) { + mNavContextOwner = new Pair<>(uid, pid); + } + + @Override + public void onStartNavigation() throws RemoteException { + assertContextOwnership(); + mNavigationRenderer.onStartNavigation(); + } + + @Override + public void onStopNavigation() throws RemoteException { + assertContextOwnership(); + mNavigationRenderer.onStopNavigation(); + } + + @Override + public void onNextManeuverChanged(int event, String road, int turnAngle, int turnNumber, + Bitmap image, int turnSide) throws RemoteException { + assertContextOwnership(); + mNavigationRenderer.onNextTurnChanged(event, road, turnAngle, turnNumber, + image, turnSide); + } + + @Override + public void onNextManeuverDistanceChanged(int distanceMeters, int timeSeconds) + throws RemoteException { + assertContextOwnership(); + mNavigationRenderer.onNextTurnDistanceChanged(distanceMeters, timeSeconds); + } + + @Override + public CarNavigationInstrumentCluster getInstrumentClusterInfo() throws RemoteException { + return mNavigationRenderer.getNavigationProperties(); + } + + private void assertContextOwnership() { + int uid = getCallingUid(); + int pid = getCallingPid(); + + Pair<Integer, Integer> owner = mNavContextOwner; + if (owner == null || owner.first != uid || owner.second != pid) { + throw new IllegalStateException("Client (uid:" + uid + ", pid: " + pid + ") is" + + "not an owner of APP_CONTEXT_NAVIGATION"); + } + } + } + + private static class UiHandler extends Handler { + private static int KEY_EVENT = 0; + private final WeakReference<InstrumentClusterRenderingService> mRefService; + + UiHandler(InstrumentClusterRenderingService service) { + mRefService = new WeakReference<>(service); + } + + @Override + public void handleMessage(Message msg) { + InstrumentClusterRenderingService service = mRefService.get(); + if (service == null) { + return; + } + + if (msg.what == KEY_EVENT) { + service.onKeyEvent((KeyEvent) msg.obj); + } else { + throw new IllegalArgumentException("Unexpected message: " + msg); + } + } + + void doKeyEvent(KeyEvent event) { + sendMessage(obtainMessage(KEY_EVENT, event)); + } + } +} diff --git a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/ThreadSafeNavigationRenderer.java b/car-lib/src/android/car/cluster/renderer/ThreadSafeNavigationRenderer.java index e9327d8f65..1fcdbc0eb6 100644 --- a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/ThreadSafeNavigationRenderer.java +++ b/car-lib/src/android/car/cluster/renderer/ThreadSafeNavigationRenderer.java @@ -13,23 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.car.cluster.demorenderer; +package android.car.cluster.renderer; import android.annotation.Nullable; -import android.car.cluster.renderer.NavigationRenderer; import android.car.navigation.CarNavigationInstrumentCluster; import android.graphics.Bitmap; import android.os.Handler; import android.os.Looper; import android.os.Message; +import java.lang.ref.WeakReference; import java.util.concurrent.CountDownLatch; /** * A wrapper over {@link NavigationRenderer} that runs all its methods in the context of provided * looper. It is guaranteed that all calls will be invoked in order they were called. */ -public class ThreadSafeNavigationRenderer extends NavigationRenderer { +/* package */ class ThreadSafeNavigationRenderer extends NavigationRenderer { private final Handler mHandler; private final NavigationRenderer mRenderer; @@ -41,7 +41,7 @@ public class ThreadSafeNavigationRenderer extends NavigationRenderer { /** Creates thread-safe {@link NavigationRenderer}. Returns null if renderer == null */ @Nullable - public static NavigationRenderer createFor(Looper looper, NavigationRenderer renderer) { + static NavigationRenderer createFor(Looper looper, NavigationRenderer renderer) { return renderer == null ? null : new ThreadSafeNavigationRenderer(looper, renderer); } @@ -52,12 +52,17 @@ public class ThreadSafeNavigationRenderer extends NavigationRenderer { @Override public CarNavigationInstrumentCluster getNavigationProperties() { - return runAndWaitResult(mHandler, new RunnableWithResult<CarNavigationInstrumentCluster>() { - @Override - protected CarNavigationInstrumentCluster createResult() { - return mRenderer.getNavigationProperties(); - } - }); + if (mHandler.getLooper() == Looper.myLooper()) { + return mRenderer.getNavigationProperties(); + } else { + return runAndWaitResult(mHandler, + new RunnableWithResult<CarNavigationInstrumentCluster>() { + @Override + protected CarNavigationInstrumentCluster createResult() { + return mRenderer.getNavigationProperties(); + } + }); + } } @Override @@ -114,13 +119,13 @@ public class ThreadSafeNavigationRenderer extends NavigationRenderer { } private static <E> E runAndWaitResult(Handler handler, RunnableWithResult<E> runnable) { - CountDownLatch latch = new CountDownLatch(1); + final CountDownLatch latch = new CountDownLatch(1); handler.post(() -> { runnable.run(); latch.countDown(); }); try { - latch.wait(); + latch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } @@ -146,7 +151,7 @@ public class ThreadSafeNavigationRenderer extends NavigationRenderer { } } - public abstract class RunnableWithResult<T> implements Runnable { + private static abstract class RunnableWithResult<T> implements Runnable { private volatile T result; protected abstract T createResult(); @@ -160,4 +165,24 @@ public class ThreadSafeNavigationRenderer extends NavigationRenderer { return result; } } + + private static abstract class RendererHandler<T> extends Handler { + + private final WeakReference<T> mRendererRef; + + RendererHandler(Looper looper, T renderer) { + super(looper); + mRendererRef = new WeakReference<>(renderer); + } + + @Override + public void handleMessage(Message msg) { + T renderer = mRendererRef.get(); + if (renderer != null) { + handleMessage(msg, renderer); + } + } + + public abstract void handleMessage(Message msg, T renderer); + } } diff --git a/car-lib/src/android/car/content/pm/CarPackageManager.java b/car-lib/src/android/car/content/pm/CarPackageManager.java index 1cdbfc7a27..ffef31e3e1 100644 --- a/car-lib/src/android/car/content/pm/CarPackageManager.java +++ b/car-lib/src/android/car/content/pm/CarPackageManager.java @@ -21,6 +21,7 @@ import android.annotation.SystemApi; import android.car.CarApiUtil; import android.car.CarManagerBase; import android.car.CarNotConnectedException; +import android.content.ComponentName; import android.content.Context; import android.os.IBinder; import android.os.Looper; @@ -122,13 +123,40 @@ public class CarPackageManager implements CarManagerBase { } /** + * Check if finishing Activity will lead into safe Activity (=allowed Activity) to be shown. + * This can be used by unsafe activity blocking Activity to check if finishing itself can + * lead into being launched again due to unsafe activity shown. Note that checking this does not + * guarantee that blocking will not be done as driving state can change after this call is made. + * + * @param activityName + * @return true if there is a safe Activity (or car is stopped) in the back of task stack + * so that finishing the Activity will not trigger another Activity blocking. If + * the given Activity is not in foreground, then it will return true as well as + * finishing the Activity will not make any difference. + * + * @hide + */ + @SystemApi + public boolean isActivityBackedBySafeActivity(ComponentName activityName) + throws CarNotConnectedException { + try { + return mService.isActivityBackedBySafeActivity(activityName); + } catch (IllegalStateException e) { + CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); + } catch (RemoteException e) { + //ignore as CarApi will handle disconnection anyway. + } + return true; + } + + /** * Check if given activity is allowed while driving. * @param packageName * @param className * @return */ public boolean isActivityAllowedWhileDriving(String packageName, String className) - throws CarNotConnectedException{ + throws CarNotConnectedException { try { return mService.isActivityAllowedWhileDriving(packageName, className); } catch (IllegalStateException e) { diff --git a/car-lib/src/android/car/content/pm/ICarPackageManager.aidl b/car-lib/src/android/car/content/pm/ICarPackageManager.aidl index d451187f2a..306db6567c 100644 --- a/car-lib/src/android/car/content/pm/ICarPackageManager.aidl +++ b/car-lib/src/android/car/content/pm/ICarPackageManager.aidl @@ -17,10 +17,12 @@ package android.car.content.pm; import android.car.content.pm.CarAppBlockingPolicy; +import android.content.ComponentName; /** @hide */ interface ICarPackageManager { void setAppBlockingPolicy(in String packageName, in CarAppBlockingPolicy policy, int flags) = 0; boolean isActivityAllowedWhileDriving(in String packageName, in String className) = 1; boolean isServiceAllowedWhileDriving(in String packageName, in String className) = 2; + boolean isActivityBackedBySafeActivity(in ComponentName activityName) = 3; } diff --git a/car-lib/src/android/car/hardware/CarHvacManager.java b/car-lib/src/android/car/hardware/CarHvacManager.java new file mode 100644 index 0000000000..9dc53f1221 --- /dev/null +++ b/car-lib/src/android/car/hardware/CarHvacManager.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2015 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 android.car.hardware.hvac; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.car.Car; +import android.car.CarManagerBase; +import android.car.CarNotConnectedException; +import android.car.hardware.CarPropertyConfig; +import android.car.hardware.CarPropertyValue; +import android.car.hardware.property.CarPropertyEvent; +import android.car.hardware.property.CarPropertyManagerBase; +import android.car.hardware.property.CarPropertyManagerBase.CarPropertyEventListener; +import android.content.Context; +import android.os.IBinder; +import android.os.Looper; +import android.util.ArraySet; + +import java.lang.ref.WeakReference; +import java.util.Collection; +import java.util.List; + +/** + * API for controlling HVAC system in cars + * @hide + */ +@SystemApi +public class CarHvacManager implements CarManagerBase { + private final static boolean DBG = true; + private final static String TAG = "CarHvacManager"; + private final CarPropertyManagerBase mMgr; + private final ArraySet<CarHvacEventListener> mListeners = new ArraySet<>(); + private CarPropertyEventListenerToBase mListenerToBase = null; + + /** + * HVAC property IDs for get/set methods + */ + @IntDef({ + HvacPropertyId.MIRROR_DEFROSTER_ON, + HvacPropertyId.STEERING_WHEEL_TEMP, + HvacPropertyId.OUTSIDE_AIR_TEMP, + HvacPropertyId.MAX_GLOBAL_PROPERTY_ID, + HvacPropertyId.ZONED_TEMP_SETPOINT, + HvacPropertyId.ZONED_TEMP_ACTUAL, + HvacPropertyId.ZONED_TEMP_IS_FAHRENHEIT, + HvacPropertyId.ZONED_FAN_SPEED_SETPOINT, + HvacPropertyId.ZONED_FAN_SPEED_RPM, + HvacPropertyId.ZONED_FAN_POSITION_AVAILABLE, + HvacPropertyId.ZONED_FAN_POSITION, + HvacPropertyId.ZONED_SEAT_TEMP, + HvacPropertyId.ZONED_AC_ON, + HvacPropertyId.ZONED_AUTOMATIC_MODE_ON, + HvacPropertyId.ZONED_AIR_RECIRCULATION_ON, + HvacPropertyId.ZONED_MAX_AC_ON, + HvacPropertyId.ZONED_DUAL_ZONE_ON, + HvacPropertyId.ZONED_MAX_DEFROST_ON, + HvacPropertyId.WINDOW_DEFROSTER_ON, + }) + public @interface HvacPropertyId { + /** + * Global HVAC properties. There is only a single instance in a car. + * Global properties are in the range of 0-0x3FFF. + */ + /** Mirror defrosters state, bool. */ + int MIRROR_DEFROSTER_ON = 0x0001; + /** Steering wheel temp: negative values indicate cooling, positive values indicate + * heat, int. */ + int STEERING_WHEEL_TEMP = 0x0002; + /** Outside air temperature, float. */ + int OUTSIDE_AIR_TEMP = 0x0003; + + /** The maximum id that can be assigned to global (non-zoned) property. */ + int MAX_GLOBAL_PROPERTY_ID = 0x3fff; + + /** + * ZONED_* represents properties available on a per-zone basis. All zones in a car are + * not required to have the same properties. Zone specific properties start at 0x4000. + */ + /** Temperature setpoint desired by the user, in terms of F or C, depending on + * TEMP_IS_FAHRENHEIT, int */ + int ZONED_TEMP_SETPOINT = 0x4001; + /** Actual zone temperature is read only integer, in terms of F or C, int. */ + int ZONED_TEMP_ACTUAL = 0x4002; + /** Temperature is in degrees fahrenheit if this is true, bool. */ + int ZONED_TEMP_IS_FAHRENHEIT = 0x4003; + /** Fan speed setpoint is an integer from 0-n, depending on the number of fan speeds + * available. Selection determines the fan position, int. */ + int ZONED_FAN_SPEED_SETPOINT = 0x4004; + /** Actual fan speed is a read-only value, expressed in RPM, int. */ + int ZONED_FAN_SPEED_RPM = 0x4005; + /** Fan position available is a bitmask of positions available for each zone, int. */ + int ZONED_FAN_POSITION_AVAILABLE = 0x4006; + /** Current fan position setting, int. */ + int ZONED_FAN_POSITION = 0x4007; + /** Seat temperature is negative for cooling, positive for heating. Temperature is a + * setting, i.e. -3 to 3 for 3 levels of cooling and 3 levels of heating. int. */ + int ZONED_SEAT_TEMP = 0x4008; + /** Air conditioner state, bool */ + int ZONED_AC_ON = 0x4009; + /** HVAC is in automatic mode, bool. */ + int ZONED_AUTOMATIC_MODE_ON = 0x400A; + /** Air recirculation is active, bool. */ + int ZONED_AIR_RECIRCULATION_ON = 0x400B; + /** Max AC is active, bool. */ + int ZONED_MAX_AC_ON = 0x400C; + /** Dual zone is enabled, bool. */ + int ZONED_DUAL_ZONE_ON = 0x400D; + /** Max Defrost is active, bool. */ + int ZONED_MAX_DEFROST_ON = 0x400E; + /** Defroster is based off of window position, bool */ + int WINDOW_DEFROSTER_ON = 0x5001; + } + + public interface CarHvacEventListener { + /** Called when a property is updated */ + void onChangeEvent(CarPropertyValue value); + + /** Called when an error is detected with a property */ + void onErrorEvent(int propertyId, int zone); + } + + private static class CarPropertyEventListenerToBase implements CarPropertyEventListener { + private final WeakReference<CarHvacManager> mManager; + + public CarPropertyEventListenerToBase(CarHvacManager manager) { + mManager = new WeakReference<>(manager); + } + + @Override + public void onChangeEvent(CarPropertyValue value) { + CarHvacManager manager = mManager.get(); + if (manager != null) { + manager.handleOnChangeEvent(value); + } + } + + @Override + public void onErrorEvent(int propertyId, int zone) { + CarHvacManager manager = mManager.get(); + if (manager != null) { + manager.handleOnErrorEvent(propertyId, zone); + } + } + } + + void handleOnChangeEvent(CarPropertyValue value) { + Collection<CarHvacEventListener> listeners; + synchronized (this) { + listeners = new ArraySet<>(mListeners); + } + if (!listeners.isEmpty()) { + for (CarHvacEventListener l: listeners) { + l.onChangeEvent(value); + } + } + } + + void handleOnErrorEvent(int propertyId, int zone) { + Collection<CarHvacEventListener> listeners; + synchronized (this) { + listeners = new ArraySet<>(mListeners); + } + if (!listeners.isEmpty()) { + for (CarHvacEventListener l: listeners) { + l.onErrorEvent(propertyId, zone); + } + } + } + + /** + * Get an instance of the CarHvacManager. + * + * Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead. + * @hide + */ + public CarHvacManager(IBinder service, Context context, Looper looper) { + mMgr = new CarPropertyManagerBase(service, context, looper, DBG, TAG); + } + + /** Returns true if the property is a zoned type. */ + public static boolean isZonedProperty(int propertyId) { + return propertyId > HvacPropertyId.MAX_GLOBAL_PROPERTY_ID; + } + + /** Implement wrappers for contained CarPropertyManagerBase object */ + public synchronized void registerListener(CarHvacEventListener listener) throws + CarNotConnectedException { + if (mListeners.isEmpty()) { + mListenerToBase = new CarPropertyEventListenerToBase(this); + mMgr.registerListener(mListenerToBase); + } + mListeners.add(listener); + } + + public synchronized void unregisterListener(CarHvacEventListener listener) throws + CarNotConnectedException { + mListeners.remove(listener); + if (mListeners.isEmpty()) { + mMgr.unregisterListener(mListenerToBase); + mListenerToBase = null; + } + } + + public List<CarPropertyConfig> getPropertyList() throws CarNotConnectedException { + return mMgr.getPropertyList(); + } + + public boolean getBooleanProperty(int prop, int area) throws CarNotConnectedException { + return mMgr.getBooleanProperty(prop, area); + } + + public float getFloatProperty(int prop, int area) throws CarNotConnectedException { + return mMgr.getFloatProperty(prop, area); + } + + public int getIntProperty(int prop, int area) throws CarNotConnectedException { + return mMgr.getIntProperty(prop, area); + } + + public void setBooleanProperty(int prop, int area, boolean val) + throws CarNotConnectedException { + mMgr.setBooleanProperty(prop, area, val); + } + + public void setFloatProperty(int prop, int area, float val) throws CarNotConnectedException { + mMgr.setFloatProperty(prop, area, val); + } + + public void setIntProperty(int prop, int area, int val) throws CarNotConnectedException { + mMgr.setIntProperty(prop, area, val); + } + + /** @hide */ + @Override + public void onCarDisconnected() { + mMgr.onCarDisconnected(); + } +} diff --git a/car-lib/src/android/car/hardware/hvac/CarHvacManager.java b/car-lib/src/android/car/hardware/hvac/CarHvacManager.java deleted file mode 100644 index 38caf10483..0000000000 --- a/car-lib/src/android/car/hardware/hvac/CarHvacManager.java +++ /dev/null @@ -1,408 +0,0 @@ -/* - * Copyright (C) 2015 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 android.car.hardware.hvac; - -import static java.lang.Integer.toHexString; - -import android.annotation.IntDef; -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.car.Car; -import android.car.CarManagerBase; -import android.car.CarNotConnectedException; -import android.car.hardware.CarPropertyConfig; -import android.car.hardware.CarPropertyValue; -import android.content.Context; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; -import android.util.ArraySet; -import android.util.Log; - -import java.lang.ref.WeakReference; -import java.util.Collection; -import java.util.List; - -/** - * API for controlling HVAC system in cars - * @hide - */ -@SystemApi -public class CarHvacManager implements CarManagerBase { - public final static boolean DBG = true; - public final static String TAG = "CarHvacManager"; - - /** - * HVAC property IDs for get/set methods - */ - @IntDef({ - HvacPropertyId.MIRROR_DEFROSTER_ON, - HvacPropertyId.STEERING_WHEEL_TEMP, - HvacPropertyId.OUTSIDE_AIR_TEMP, - HvacPropertyId.MAX_GLOBAL_PROPERTY_ID, - HvacPropertyId.ZONED_TEMP_SETPOINT, - HvacPropertyId.ZONED_TEMP_ACTUAL, - HvacPropertyId.ZONED_TEMP_IS_FAHRENHEIT, - HvacPropertyId.ZONED_FAN_SPEED_SETPOINT, - HvacPropertyId.ZONED_FAN_SPEED_RPM, - HvacPropertyId.ZONED_FAN_POSITION_AVAILABLE, - HvacPropertyId.ZONED_FAN_POSITION, - HvacPropertyId.ZONED_SEAT_TEMP, - HvacPropertyId.ZONED_AC_ON, - HvacPropertyId.ZONED_AUTOMATIC_MODE_ON, - HvacPropertyId.ZONED_AIR_RECIRCULATION_ON, - HvacPropertyId.ZONED_MAX_AC_ON, - HvacPropertyId.ZONED_DUAL_ZONE_ON, - HvacPropertyId.ZONED_MAX_DEFROST_ON, - HvacPropertyId.WINDOW_DEFROSTER_ON, - }) - public @interface HvacPropertyId { - /** - * Global HVAC properties. There is only a single instance in a car. - * Global properties are in the range of 0-0x3FFF. - */ - /** Mirror defrosters state, bool. */ - int MIRROR_DEFROSTER_ON = 0x0001; - /** Steering wheel temp: negative values indicate cooling, positive values indicate - * heat, int. */ - int STEERING_WHEEL_TEMP = 0x0002; - /** Outside air temperature, float. */ - int OUTSIDE_AIR_TEMP = 0x0003; - - /** The maximum id that can be assigned to global (non-zoned) property. */ - int MAX_GLOBAL_PROPERTY_ID = 0x3fff; - - /** - * ZONED_* represents properties available on a per-zone basis. All zones in a car are - * not required to have the same properties. Zone specific properties start at 0x4000. - */ - /** Temperature setpoint desired by the user, in terms of F or C, depending on - * TEMP_IS_FAHRENHEIT, int */ - int ZONED_TEMP_SETPOINT = 0x4001; - /** Actual zone temperature is read only integer, in terms of F or C, int. */ - int ZONED_TEMP_ACTUAL = 0x4002; - /** Temperature is in degrees fahrenheit if this is true, bool. */ - int ZONED_TEMP_IS_FAHRENHEIT = 0x4003; - /** Fan speed setpoint is an integer from 0-n, depending on the number of fan speeds - * available. Selection determines the fan position, int. */ - int ZONED_FAN_SPEED_SETPOINT = 0x4004; - /** Actual fan speed is a read-only value, expressed in RPM, int. */ - int ZONED_FAN_SPEED_RPM = 0x4005; - /** Fan position available is a bitmask of positions available for each zone, int. */ - int ZONED_FAN_POSITION_AVAILABLE = 0x4006; - /** Current fan position setting, int. */ - int ZONED_FAN_POSITION = 0x4007; - /** Seat temperature is negative for cooling, positive for heating. Temperature is a - * setting, i.e. -3 to 3 for 3 levels of cooling and 3 levels of heating. int. */ - int ZONED_SEAT_TEMP = 0x4008; - /** Air conditioner state, bool */ - int ZONED_AC_ON = 0x4009; - /** HVAC is in automatic mode, bool. */ - int ZONED_AUTOMATIC_MODE_ON = 0x400A; - /** Air recirculation is active, bool. */ - int ZONED_AIR_RECIRCULATION_ON = 0x400B; - /** Max AC is active, bool. */ - int ZONED_MAX_AC_ON = 0x400C; - /** Dual zone is enabled, bool. */ - int ZONED_DUAL_ZONE_ON = 0x400D; - /** Max Defrost is active, bool. */ - int ZONED_MAX_DEFROST_ON = 0x400E; - /** Defroster is based off of window position, bool */ - int WINDOW_DEFROSTER_ON = 0x5001; - } - - // Constants handled in the handler (see mHandler below). - private final static int MSG_HVAC_EVENT = 0; - - /** Callback functions for HVAC events */ - public interface CarHvacEventListener { - /** Called when an HVAC property is updated */ - void onChangeEvent(final CarPropertyValue value); - - /** Called when an error is detected with a property */ - void onErrorEvent(final int propertyId, final int zone); - } - - private final ICarHvac mService; - private final ArraySet<CarHvacEventListener> mListeners = new ArraySet<>(); - private CarHvacEventListenerToService mListenerToService = null; - - private static final class EventCallbackHandler extends Handler { - WeakReference<CarHvacManager> mMgr; - - EventCallbackHandler(CarHvacManager mgr, Looper looper) { - super(looper); - mMgr = new WeakReference<>(mgr); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_HVAC_EVENT: - CarHvacManager mgr = mMgr.get(); - if (mgr != null) { - mgr.dispatchEventToClient((CarHvacEvent) msg.obj); - } - break; - default: - Log.e(TAG, "Event type not handled?" + msg); - break; - } - } - } - - private final Handler mHandler; - - private static class CarHvacEventListenerToService extends ICarHvacEventListener.Stub { - private final WeakReference<CarHvacManager> mManager; - - public CarHvacEventListenerToService(CarHvacManager manager) { - mManager = new WeakReference<>(manager); - } - - @Override - public void onEvent(CarHvacEvent event) { - CarHvacManager manager = mManager.get(); - if (manager != null) { - manager.handleEvent(event); - } - } - } - - /** - * Get an instance of the CarHvacManager. - * - * Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead. - * @hide - */ - public CarHvacManager(IBinder service, Context context, Looper looper) { - mService = ICarHvac.Stub.asInterface(service); - mHandler = new EventCallbackHandler(this, looper); - } - - /** Returns true if the property is a zoned type. */ - public static boolean isZonedProperty(int propertyId) { - return propertyId > HvacPropertyId.MAX_GLOBAL_PROPERTY_ID; - } - - /** - * Register {@link CarHvacEventListener} to get HVAC property changes - * - * @param listener Implements onEvent() for property change updates - */ - public synchronized void registerListener(CarHvacEventListener listener) - throws CarNotConnectedException { - if(mListeners.isEmpty()) { - try { - mListenerToService = new CarHvacEventListenerToService(this); - mService.registerListener(mListenerToService); - } catch (RemoteException ex) { - Log.e(TAG, "Could not connect: " + ex.toString()); - throw new CarNotConnectedException(ex); - } catch (IllegalStateException ex) { - Car.checkCarNotConnectedExceptionFromCarService(ex); - } - } - mListeners.add(listener); - } - - /** - * Unregister {@link CarHvacEventListener}. - * @param listener CarHvacEventListener to unregister - */ - public synchronized void unregisterListener(CarHvacEventListener listener) - throws CarNotConnectedException { - if (DBG) { - Log.d(TAG, "unregisterListener"); - } - try { - mService.unregisterListener(mListenerToService); - } catch (RemoteException e) { - Log.e(TAG, "Could not unregister: " + e.toString()); - throw new CarNotConnectedException(e); - - } - mListeners.remove(listener); - if(mListeners.isEmpty()) { - mListenerToService = null; - } - } - - /** - * Returns the list of HVAC properties available. - * - * @return Caller must check the property type and typecast to the appropriate subclass - * (CarHvacBooleanProperty, CarHvacFloatProperty, CarrHvacIntProperty) - */ - public List<CarPropertyConfig> getPropertyList() throws CarNotConnectedException { - List<CarPropertyConfig> carProps; - try { - carProps = mService.getHvacProperties(); - } catch (RemoteException e) { - Log.w(TAG, "Exception in getPropertyList", e); - throw new CarNotConnectedException(e); - } - return carProps; - } - - /** - * Returns value of a bool property - * - * @param prop Property ID to get - * @param area Area of the property to get - */ - public boolean getBooleanProperty(int prop, int area) throws CarNotConnectedException { - CarPropertyValue<Boolean> carProp = getProperty(Boolean.class, prop, area); - return carProp != null ? carProp.getValue() : false; - } - - /** - * Returns value of a float property - * - * @param prop Property ID to get - * @param area Area of the property to get - */ - public float getFloatProperty(int prop, int area) throws CarNotConnectedException { - CarPropertyValue<Float> carProp = getProperty(Float.class, prop, area); - return carProp != null ? carProp.getValue() : 0f; - } - - /** - * Returns value of a integer property - * - * @param prop Property ID to get - * @param area Zone of the property to get - */ - public int getIntProperty(int prop, int area) throws CarNotConnectedException { - CarPropertyValue<Integer> carProp = getProperty(Integer.class, prop, area); - return carProp != null ? carProp.getValue() : 0; - } - - @Nullable - @SuppressWarnings("unchecked") - private <E> CarPropertyValue<E> getProperty(Class<E> clazz, int prop, int area) - throws CarNotConnectedException { - if (DBG) { - Log.d(TAG, "getProperty, prop: 0x" + toHexString(prop) - + ", area: 0x" + toHexString(area) + ", clazz: " + clazz); - } - try { - CarPropertyValue<E> hvacProperty = mService.getProperty(prop, area); - if (hvacProperty != null && hvacProperty.getValue() != null) { - Class<?> actualClass = hvacProperty.getValue().getClass(); - if (actualClass != clazz) { - throw new IllegalArgumentException("Invalid property type. " - + "Expected: " + clazz + ", but was: " + actualClass); - } - } - return hvacProperty; - } catch (RemoteException e) { - Log.e(TAG, "getProperty failed with " + e.toString() - + ", propId: 0x" + toHexString(prop) + ", area: 0x" + toHexString(area), e); - throw new CarNotConnectedException(e); - } - } - - /** - * Modifies a property. If the property modification doesn't occur, an error event shall be - * generated and propagated back to the application. - * - * @param prop Property ID to modify - * @param area Area to apply the modification. - * @param val Value to set - */ - public void setBooleanProperty(int prop, int area, boolean val) - throws CarNotConnectedException { - if (DBG) { - Log.d(TAG, "setBooleanProperty: prop = " + prop + " area = " + area + " val = " + val); - } - try { - mService.setProperty(new CarPropertyValue<>(prop, area, val)); - } catch (RemoteException e) { - Log.e(TAG, "setBooleanProperty failed with " + e.toString(), e); - throw new CarNotConnectedException(e); - } - } - - public void setFloatProperty(int prop, int area, float val) throws CarNotConnectedException { - if (DBG) { - Log.d(TAG, "setFloatProperty: prop = " + prop + " area = " + area + " val = " + val); - } - try { - mService.setProperty(new CarPropertyValue<>(prop, area, val)); - } catch (RemoteException e) { - Log.e(TAG, "setBooleanProperty failed with " + e.toString(), e); - throw new CarNotConnectedException(e); - } - } - - public void setIntProperty(int prop, int area, int val) throws CarNotConnectedException { - if (DBG) { - Log.d(TAG, "setIntProperty: prop = " + prop + " area = " + area + " val = " + val); - } - try { - mService.setProperty(new CarPropertyValue<>(prop, area, val)); - } catch (RemoteException e) { - Log.e(TAG, "setIntProperty failed with " + e.toString(), e); - throw new CarNotConnectedException(e); - } - } - - private void dispatchEventToClient(CarHvacEvent event) { - Collection<CarHvacEventListener> listeners; - synchronized (this) { - listeners = mListeners; - } - if (!listeners.isEmpty()) { - CarPropertyValue hvacProperty = event.getCarPropertyValue(); - switch(event.getEventType()) { - case CarHvacEvent.HVAC_EVENT_PROPERTY_CHANGE: - for (CarHvacEventListener l: listeners) { - l.onChangeEvent(hvacProperty); - } - break; - case CarHvacEvent.HVAC_EVENT_ERROR: - for (CarHvacEventListener l: listeners) { - l.onErrorEvent(hvacProperty.getPropertyId(), hvacProperty.getAreaId()); - } - break; - default: - throw new IllegalArgumentException(); - } - } else { - Log.e(TAG, "Listener died, not dispatching event."); - } - } - - private void handleEvent(CarHvacEvent event) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_HVAC_EVENT, event)); - } - - /** @hide */ - @Override - public void onCarDisconnected() { - for(CarHvacEventListener l: mListeners) { - try { - unregisterListener(l); - } catch (CarNotConnectedException e) { - // Ignore, car is disconnecting. - } - } - } -} diff --git a/service/src/com/android/car/cluster/InstrumentClusterPresentation.java b/car-lib/src/android/car/hardware/property/CarPropertyEvent.aidl index 53c73f8fc5..82e3de9b6b 100644 --- a/service/src/com/android/car/cluster/InstrumentClusterPresentation.java +++ b/car-lib/src/android/car/hardware/property/CarPropertyEvent.aidl @@ -13,19 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.car.cluster; -import android.app.Presentation; -import android.content.Context; -import android.view.Display; -import android.view.WindowManager; +package android.car.hardware.property; + +parcelable CarPropertyEvent; -/** - * Presentation class. - */ -public class InstrumentClusterPresentation extends Presentation { - public InstrumentClusterPresentation(Context outerContext, Display display) { - super(outerContext, display); - getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); - } -} diff --git a/car-lib/src/android/car/hardware/hvac/CarHvacEvent.java b/car-lib/src/android/car/hardware/property/CarPropertyEvent.java index d243c80b52..638fbf2e2b 100644 --- a/car-lib/src/android/car/hardware/hvac/CarHvacEvent.java +++ b/car-lib/src/android/car/hardware/property/CarPropertyEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2016 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. @@ -14,16 +14,16 @@ * limitations under the License. */ -package android.car.hardware.hvac; +package android.car.hardware.property; import android.car.hardware.CarPropertyValue; import android.os.Parcel; import android.os.Parcelable; /** @hide */ -public class CarHvacEvent implements Parcelable { - public static final int HVAC_EVENT_PROPERTY_CHANGE = 0; - public static final int HVAC_EVENT_ERROR = 1; +public class CarPropertyEvent implements Parcelable { + public static final int PROPERTY_EVENT_PROPERTY_CHANGE = 0; + public static final int PROPERTY_EVENT_ERROR = 1; /** * EventType of this message @@ -54,33 +54,33 @@ public class CarHvacEvent implements Parcelable { dest.writeParcelable(mCarPropertyValue, flags); } - public static final Parcelable.Creator<CarHvacEvent> CREATOR - = new Parcelable.Creator<CarHvacEvent>() { - public CarHvacEvent createFromParcel(Parcel in) { - return new CarHvacEvent(in); + public static final Parcelable.Creator<CarPropertyEvent> CREATOR + = new Parcelable.Creator<CarPropertyEvent>() { + public CarPropertyEvent createFromParcel(Parcel in) { + return new CarPropertyEvent(in); } - public CarHvacEvent[] newArray(int size) { - return new CarHvacEvent[size]; + public CarPropertyEvent[] newArray(int size) { + return new CarPropertyEvent[size]; } }; /** - * Constructor for {@link CarHvacEvent}. + * Constructor for {@link CarPropertyEvent}. */ - public CarHvacEvent(int eventType, CarPropertyValue<?> carHvacProperty) { + public CarPropertyEvent(int eventType, CarPropertyValue<?> carPropertyValue) { mEventType = eventType; - mCarPropertyValue = carHvacProperty; + mCarPropertyValue = carPropertyValue; } - private CarHvacEvent(Parcel in) { + private CarPropertyEvent(Parcel in) { mEventType = in.readInt(); mCarPropertyValue = in.readParcelable(CarPropertyValue.class.getClassLoader()); } @Override public String toString() { - return "CarHvacEvent{" + + return "CarPropertyEvent{" + "mEventType=" + mEventType + ", mCarPropertyValue=" + mCarPropertyValue + '}'; diff --git a/car-lib/src/android/car/hardware/property/CarPropertyManagerBase.java b/car-lib/src/android/car/hardware/property/CarPropertyManagerBase.java new file mode 100644 index 0000000000..08514423d7 --- /dev/null +++ b/car-lib/src/android/car/hardware/property/CarPropertyManagerBase.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2016 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 android.car.hardware.property; + +import static java.lang.Integer.toHexString; + +import android.annotation.Nullable; +import android.car.Car; +import android.car.CarNotConnectedException; +import android.car.hardware.CarPropertyConfig; +import android.car.hardware.CarPropertyValue; +import android.content.Context; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.ArraySet; +import android.util.EventLog; +import android.util.Log; + +import java.lang.ref.WeakReference; +import java.util.Collection; +import java.util.List; + +/** + * API for creating Car*Manager + * @hide + */ +public class CarPropertyManagerBase { + private final boolean mDbg; + private final Handler mHandler; + private final ArraySet<CarPropertyEventListener> mListeners = new ArraySet<>(); + private CarPropertyEventListenerToService mListenerToService = null; + private final ICarProperty mService; + private final String mTag; + + /** Callback functions for property events */ + public interface CarPropertyEventListener { + /** Called when a property is updated */ + void onChangeEvent(CarPropertyValue value); + + /** Called when an error is detected with a property */ + void onErrorEvent(int propertyId, int zone); + } + + private final static class EventCallbackHandler extends Handler { + /** Constants handled in the handler */ + private static final int MSG_GENERIC_EVENT = 0; + + private final WeakReference<CarPropertyManagerBase> mMgr; + + EventCallbackHandler(CarPropertyManagerBase mgr, Looper looper) { + super(looper); + mMgr = new WeakReference<>(mgr); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_GENERIC_EVENT: + CarPropertyManagerBase mgr = mMgr.get(); + if (mgr != null) { + mgr.dispatchEventToClient((CarPropertyEvent) msg.obj); + } + break; + default: + Log.e("EventtCallbackHandler", "Event type not handled: " + msg); + break; + } + } + } + + private static class CarPropertyEventListenerToService extends ICarPropertyEventListener.Stub { + private final WeakReference<CarPropertyManagerBase> mManager; + + public CarPropertyEventListenerToService( + CarPropertyManagerBase manager) { + mManager = new WeakReference<>(manager); + } + + @Override + public void onEvent(CarPropertyEvent event) { + CarPropertyManagerBase manager = mManager.get(); + if (manager != null) { + manager.handleEvent(event); + } + } + } + + /** + * Get an instance of the CarPropertyManagerBase. + */ + public CarPropertyManagerBase(IBinder service, Context context, Looper looper, boolean dbg, + String tag) { + mDbg = dbg; + mTag = tag; + mService = ICarProperty.Stub.asInterface(service); + mHandler = new EventCallbackHandler(this, looper); + } + + /** + * Register {@link CarPropertyEventListener} to get property changes + * + * @param listener Implements onEvent() for property change updates + */ + public synchronized void registerListener(CarPropertyEventListener listener) + throws CarNotConnectedException { + if(mListeners.isEmpty()) { + try { + mListenerToService = new CarPropertyEventListenerToService(this); + mService.registerListener(mListenerToService); + } catch (RemoteException ex) { + Log.e(mTag, "Could not connect: ", ex); + throw new CarNotConnectedException(ex); + } catch (IllegalStateException ex) { + Car.checkCarNotConnectedExceptionFromCarService(ex); + } + } + mListeners.add(listener); + } + + /** + * Unregister {@link CarPropertyEventListener}. + * @param listener CarPropertyEventListener to unregister + */ + public synchronized void unregisterListener(CarPropertyEventListener listener) + throws CarNotConnectedException { + if (mDbg) { + Log.d(mTag, "unregisterListener"); + } + mListeners.remove(listener); + if(mListeners.isEmpty()) { + try { + mService.unregisterListener(mListenerToService); + } catch (RemoteException ex) { + Log.e(mTag, "Could not unregister: ", ex); + throw new CarNotConnectedException(ex); + } + mListenerToService = null; + } + } + + /** + * Returns the list of properties available. + * + * @return Caller must check the property type and typecast to the appropriate subclass + * (CarPropertyBooleanProperty, CarPropertyFloatProperty, CarrPropertyIntProperty) + */ + public List<CarPropertyConfig> getPropertyList() throws CarNotConnectedException { + try { + return mService.getPropertyList(); + } catch (RemoteException e) { + Log.w(mTag, "Exception in getPropertyList", e); + throw new CarNotConnectedException(e); + } + } + + /** + * Returns value of a bool property + * + * @param prop Property ID to get + * @param area Area of the property to get + */ + public boolean getBooleanProperty(int prop, int area) throws CarNotConnectedException { + CarPropertyValue<Boolean> carProp = getProperty(Boolean.class, prop, area); + return carProp != null ? carProp.getValue() : false; + } + + /** + * Returns value of a float property + * + * @param prop Property ID to get + * @param area Area of the property to get + */ + public float getFloatProperty(int prop, int area) throws CarNotConnectedException { + CarPropertyValue<Float> carProp = getProperty(Float.class, prop, area); + return carProp != null ? carProp.getValue() : 0f; + } + + /** + * Returns value of a integer property + * + * @param prop Property ID to get + * @param area Zone of the property to get + */ + public int getIntProperty(int prop, int area) throws CarNotConnectedException { + CarPropertyValue<Integer> carProp = getProperty(Integer.class, prop, area); + return carProp != null ? carProp.getValue() : 0; + } + + @Nullable + @SuppressWarnings("unchecked") + private <E> CarPropertyValue<E> getProperty(Class<E> clazz, int propId, int area) + throws CarNotConnectedException { + if (mDbg) { + Log.d(mTag, "getProperty, propId: 0x" + toHexString(propId) + + ", area: 0x" + toHexString(area) + ", clazz: " + clazz); + } + try { + CarPropertyValue<E> propVal = mService.getProperty(propId, area); + if (propVal != null && propVal.getValue() != null) { + Class<?> actualClass = propVal.getValue().getClass(); + if (actualClass != clazz) { + throw new IllegalArgumentException("Invalid property type. " + "Expected: " + + clazz + ", but was: " + actualClass); + } + } + return propVal; + } catch (RemoteException e) { + Log.e(mTag, "getProperty failed with " + e.toString() + + ", propId: 0x" + toHexString(propId) + ", area: 0x" + toHexString(area), e); + throw new CarNotConnectedException(e); + } + } + + /** + * Modifies a property. If the property modification doesn't occur, an error event shall be + * generated and propagated back to the application. + * + * @param prop Property ID to modify + * @param area Area to apply the modification. + * @param val Value to set + */ + public void setBooleanProperty(int prop, int area, boolean val) + throws CarNotConnectedException { + if (mDbg) { + Log.d(mTag, "setBooleanProperty: prop = " + prop + " area = " + area + " val = " + val); + } + try { + mService.setProperty(new CarPropertyValue<>(prop, area, val)); + } catch (RemoteException e) { + Log.e(mTag, "setBooleanProperty failed with " + e.toString(), e); + throw new CarNotConnectedException(e); + } + } + + public void setFloatProperty(int prop, int area, float val) throws CarNotConnectedException { + if (mDbg) { + Log.d(mTag, "setFloatProperty: prop = " + prop + " area = " + area + " val = " + val); + } + try { + mService.setProperty(new CarPropertyValue<>(prop, area, val)); + } catch (RemoteException e) { + Log.e(mTag, "setBooleanProperty failed with " + e.toString(), e); + throw new CarNotConnectedException(e); + } + } + + public void setIntProperty(int prop, int area, int val) throws CarNotConnectedException { + if (mDbg) { + Log.d(mTag, "setIntProperty: prop = " + prop + " area = " + area + " val = " + val); + } + try { + mService.setProperty(new CarPropertyValue<>(prop, area, val)); + } catch (RemoteException e) { + Log.e(mTag, "setIntProperty failed with " + e.toString(), e); + throw new CarNotConnectedException(e); + } + } + + private synchronized void dispatchEventToClient(CarPropertyEvent event) { + Collection<CarPropertyEventListener> listeners; + synchronized (this) { + listeners = new ArraySet<>(mListeners); + } + if (!listeners.isEmpty()) { + CarPropertyValue propVal = event.getCarPropertyValue(); + switch(event.getEventType()) { + case CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE: + for (CarPropertyEventListener l: listeners) { + l.onChangeEvent(propVal); + } + break; + case CarPropertyEvent.PROPERTY_EVENT_ERROR: + for (CarPropertyEventListener l: listeners) { + l.onErrorEvent(propVal.getPropertyId(), propVal.getAreaId()); + } + break; + default: + throw new IllegalArgumentException(); + } + } else { + Log.e(mTag, "Listener died, not dispatching event."); + } + } + + private void handleEvent(CarPropertyEvent event) { + mHandler.sendMessage(mHandler.obtainMessage(EventCallbackHandler.MSG_GENERIC_EVENT, event)); + } + + public void onCarDisconnected() { + for(CarPropertyEventListener l: mListeners) { + try { + unregisterListener(l); + } catch (CarNotConnectedException e) { + // Ignore, car is disconnecting. + } + } + } +} diff --git a/car-lib/src/android/car/hardware/hvac/ICarHvac.aidl b/car-lib/src/android/car/hardware/property/ICarProperty.aidl index 46c3917816..da0065f0c1 100644 --- a/car-lib/src/android/car/hardware/hvac/ICarHvac.aidl +++ b/car-lib/src/android/car/hardware/property/ICarProperty.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2016 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. @@ -14,20 +14,22 @@ * limitations under the License. */ -package android.car.hardware.hvac; +package android.car.hardware.property; import android.car.hardware.CarPropertyConfig; import android.car.hardware.CarPropertyValue; -import android.car.hardware.hvac.ICarHvacEventListener; +import android.car.hardware.property.ICarPropertyEventListener; -/** @hide */ -interface ICarHvac { +/** + * @hide + */ +interface ICarProperty { - void registerListener(in ICarHvacEventListener listener) = 0; + void registerListener(in ICarPropertyEventListener listener) = 0; - void unregisterListener(in ICarHvacEventListener listener) = 1; + void unregisterListener(in ICarPropertyEventListener listener) = 1; - List<CarPropertyConfig> getHvacProperties() = 2; + List<CarPropertyConfig> getPropertyList() = 2; CarPropertyValue getProperty(int prop, int zone) = 3; diff --git a/car-lib/src/android/car/hardware/hvac/ICarHvacEventListener.aidl b/car-lib/src/android/car/hardware/property/ICarPropertyEventListener.aidl index 1d5b99dd5c..b0d57fcab5 100644 --- a/car-lib/src/android/car/hardware/hvac/ICarHvacEventListener.aidl +++ b/car-lib/src/android/car/hardware/property/ICarPropertyEventListener.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2016 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. @@ -14,20 +14,20 @@ * limitations under the License. */ -package android.car.hardware.hvac; +package android.car.hardware.property; -import android.car.hardware.hvac.CarHvacEvent; +import android.car.hardware.property.CarPropertyEvent; /** - * Binder callback for CarHvacEventListener. + * Binder callback for CarPropertyEventListener. * This is generated per each CarClient. * @hide */ -oneway interface ICarHvacEventListener { +oneway interface ICarPropertyEventListener { /** * Called when an event is triggered in response to one of the calls (such as on tune) or * asynchronously (such as on announcement). */ - void onEvent(in CarHvacEvent event) = 0; + void onEvent(in CarPropertyEvent event) = 0; } diff --git a/car-lib/src/android/car/media/CarAudioManager.java b/car-lib/src/android/car/media/CarAudioManager.java index 4dd8be46b2..2d205620a6 100644 --- a/car-lib/src/android/car/media/CarAudioManager.java +++ b/car-lib/src/android/car/media/CarAudioManager.java @@ -77,18 +77,48 @@ public class CarAudioManager implements CarManagerBase { * Audio usage for playing safety alert. */ public static final int CAR_AUDIO_USAGE_SYSTEM_SAFETY_ALERT = 9; + /** + * Audio usage for external audio usage. + * @hide + */ + public static final int CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE = 10; /** @hide */ - public static final int CAR_AUDIO_USAGE_MAX = CAR_AUDIO_USAGE_SYSTEM_SAFETY_ALERT; + public static final int CAR_AUDIO_USAGE_MAX = CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE; /** @hide */ @IntDef({CAR_AUDIO_USAGE_DEFAULT, CAR_AUDIO_USAGE_MUSIC, CAR_AUDIO_USAGE_RADIO, CAR_AUDIO_USAGE_NAVIGATION_GUIDANCE, CAR_AUDIO_USAGE_VOICE_CALL, CAR_AUDIO_USAGE_VOICE_COMMAND, CAR_AUDIO_USAGE_ALARM, CAR_AUDIO_USAGE_NOTIFICATION, - CAR_AUDIO_USAGE_SYSTEM_SOUND, CAR_AUDIO_USAGE_SYSTEM_SAFETY_ALERT}) + CAR_AUDIO_USAGE_SYSTEM_SOUND, CAR_AUDIO_USAGE_SYSTEM_SAFETY_ALERT, + CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE}) @Retention(RetentionPolicy.SOURCE) public @interface CarAudioUsage {} + /** @hide */ + public static final String CAR_RADIO_TYPE_AM_FM = "RADIO_AM_FM"; + /** @hide */ + public static final String CAR_RADIO_TYPE_AM_FM_HD = "RADIO_AM_FM_HD"; + /** @hide */ + public static final String CAR_RADIO_TYPE_DAB = "RADIO_DAB"; + /** @hide */ + public static final String CAR_RADIO_TYPE_SATELLITE = "RADIO_SATELLITE"; + + /** @hide */ + public static final String CAR_EXTERNAL_SOURCE_TYPE_CD_DVD = "CD_DVD"; + /** @hide */ + public static final String CAR_EXTERNAL_SOURCE_TYPE_AUX_IN0 = "AUX_IN0"; + /** @hide */ + public static final String CAR_EXTERNAL_SOURCE_TYPE_AUX_IN1 = "AUX_IN1"; + /** @hide */ + public static final String CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE = "EXT_NAV_GUIDANCE"; + /** @hide */ + public static final String CAR_EXTERNAL_SOURCE_TYPE_EXT_VOICE_CALL = "EXT_VOICE_CALL"; + /** @hide */ + public static final String CAR_EXTERNAL_SOURCE_TYPE_EXT_VOICE_COMMAND = "EXT_VOICE_COMMAND"; + /** @hide */ + public static final String CAR_EXTERNAL_SOURCE_TYPE_EXT_SAFETY_ALERT = "EXT_SAFETY_ALERT"; + private final ICarAudio mService; private final AudioManager mAudioManager; @@ -101,9 +131,79 @@ public class CarAudioManager implements CarManagerBase { try { return mService.getAudioAttributesForCarUsage(carUsage); } catch (RemoteException e) { - AudioAttributes.Builder builder = new AudioAttributes.Builder(); - return builder.setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN). - setUsage(AudioAttributes.USAGE_UNKNOWN).build(); + return createAudioAttributes(AudioAttributes.CONTENT_TYPE_UNKNOWN, + AudioAttributes.USAGE_UNKNOWN); + } + } + + /** + * Get AudioAttributes for radio. This is necessary when there are multiple types of radio + * in system. + * + * @param radioType String specifying the desired radio type. Should use only what is listed in + * {@link #getSupportedRadioTypes()}. + * @return + * @throws IllegalArgumentException If not supported type is passed. + * + * @hide + */ + public AudioAttributes getAudioAttributesForRadio(String radioType) + throws IllegalArgumentException { + try { + return mService.getAudioAttributesForRadio(radioType); + } catch (RemoteException e) { + return createAudioAttributes(AudioAttributes.CONTENT_TYPE_UNKNOWN, + AudioAttributes.USAGE_UNKNOWN); + } + } + + /** + * Get AudioAttributes for external audio source. + * + * @param externalSourceType String specifying the desired source type. Should use only what is + * listed in {@link #getSupportedExternalSourceTypes()}. + * @return + * @throws IllegalArgumentException If not supported type is passed. + * + * @hide + */ + public AudioAttributes getAudioAttributesForExternalSource(String externalSourceType) + throws IllegalArgumentException { + try { + return mService.getAudioAttributesForExternalSource(externalSourceType); + } catch (RemoteException e) { + return createAudioAttributes(AudioAttributes.CONTENT_TYPE_UNKNOWN, + AudioAttributes.USAGE_UNKNOWN); + } + } + + /** + * List all supported external audio sources. + * + * @return + * + * @hide + */ + public String[] getSupportedExternalSourceTypes() { + try { + return mService.getSupportedExternalSourceTypes(); + } catch (RemoteException e) { + return null; + } + } + + /** + * List all supported radio sources. + * + * @return + * + * @hide + */ + public String[] getSupportedRadioTypes() { + try { + return mService.getSupportedRadioTypes(); + } catch (RemoteException e) { + return null; } } @@ -271,7 +371,6 @@ public class CarAudioManager implements CarManagerBase { @Override public void onCarDisconnected() { - // TODO Auto-generated method stub } /** @hide */ @@ -279,4 +378,9 @@ public class CarAudioManager implements CarManagerBase { mService = ICarAudio.Stub.asInterface(service); mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); } + + private AudioAttributes createAudioAttributes(int contentType, int usage) { + AudioAttributes.Builder builder = new AudioAttributes.Builder(); + return builder.setContentType(contentType).setUsage(usage).build(); + } } diff --git a/car-lib/src/android/car/media/ICarAudio.aidl b/car-lib/src/android/car/media/ICarAudio.aidl index b4e1283572..1c07796db6 100644 --- a/car-lib/src/android/car/media/ICarAudio.aidl +++ b/car-lib/src/android/car/media/ICarAudio.aidl @@ -34,4 +34,8 @@ interface ICarAudio { int getStreamVolume(int streamType) = 5; boolean isMediaMuted() = 6; boolean setMediaMute(boolean mute) = 7; + AudioAttributes getAudioAttributesForRadio(in String radioType) = 8; + AudioAttributes getAudioAttributesForExternalSource(in String externalSourceType) = 9; + String[] getSupportedExternalSourceTypes() = 10; + String[] getSupportedRadioTypes() = 11; } diff --git a/car-lib/src/android/car/navigation/CarNavigationManager.java b/car-lib/src/android/car/navigation/CarNavigationManager.java index d1df559470..064c9e934c 100644 --- a/car-lib/src/android/car/navigation/CarNavigationManager.java +++ b/car-lib/src/android/car/navigation/CarNavigationManager.java @@ -19,34 +19,18 @@ import android.car.CarApiUtil; import android.car.CarLibLog; import android.car.CarManagerBase; import android.car.CarNotConnectedException; +import android.car.cluster.renderer.IInstrumentClusterNavigation; import android.graphics.Bitmap; -import android.os.Handler; -import android.os.Handler.Callback; import android.os.IBinder; -import android.os.Looper; -import android.os.Message; import android.os.RemoteException; import android.util.Log; -import java.lang.ref.WeakReference; - /** * API for providing navigation status for instrument cluster. * @hide */ public class CarNavigationManager implements CarManagerBase { - /** - * Listener navigation related events. - * Callbacks are called in the Looper context. - */ - public interface CarNavigationListener { - /** Instrument Cluster started in navigation mode */ - void onInstrumentClusterStart(CarNavigationInstrumentCluster instrumentCluster); - /** Instrument cluster ended */ - void onInstrumentClusterStop(); - } - /** Navigation status */ public static final int STATUS_UNAVAILABLE = 0; public static final int STATUS_ACTIVE = 1; @@ -80,38 +64,15 @@ public class CarNavigationManager implements CarManagerBase { private static final String TAG = CarLibLog.TAG_NAV; - private final ICarNavigation mService; - private ICarNavigationEventListenerImpl mICarNavigationEventListenerImpl; - private CarNavigationListener mCarNavigationListener; - private CarNavigationInstrumentCluster mInstrumentCluster; - private final Handler mHandler; - private final Callback mHandlerCallback = new Callback() { - @Override - public boolean handleMessage(Message msg) { - Log.d(TAG, "handleMessage, listener: " + mCarNavigationListener + ", msg.what: " + - msg.what); - if (mCarNavigationListener != null) { - switch (msg.what) { - case START: - Log.d(TAG, "mCarNavigationListener.onInstrumentClusterStart()"); - mCarNavigationListener.onInstrumentClusterStart(mInstrumentCluster); - break; - case STOP: - mCarNavigationListener.onInstrumentClusterStop(); - break; - } - } - return true; - } - }; + private final IInstrumentClusterNavigation mService; + /** * Only for CarServiceLoader * @hide */ - public CarNavigationManager(IBinder service, Looper looper) { - mHandler = new Handler(looper, mHandlerCallback); - mService = ICarNavigation.Stub.asInterface(service); + public CarNavigationManager(IBinder service) { + mService = IInstrumentClusterNavigation.Stub.asInterface(service); } /** @@ -121,7 +82,11 @@ public class CarNavigationManager implements CarManagerBase { */ public boolean sendNavigationStatus(int status) throws CarNotConnectedException { try { - mService.sendNavigationStatus(status); + if (status == STATUS_ACTIVE) { + mService.onStartNavigation(); + } else { + mService.onStopNavigation(); + } } catch (IllegalStateException e) { CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); } catch (RemoteException e) { @@ -159,7 +124,7 @@ public class CarNavigationManager implements CarManagerBase { public boolean sendNavigationTurnEvent(int event, String road, int turnAngle, int turnNumber, Bitmap image, int turnSide) throws CarNotConnectedException { try { - mService.sendNavigationTurnEvent(event, road, turnAngle, turnNumber, image, turnSide); + mService.onNextManeuverChanged(event, road, turnAngle, turnNumber, image, turnSide); } catch (IllegalStateException e) { CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); } catch (RemoteException e) { @@ -180,7 +145,7 @@ public class CarNavigationManager implements CarManagerBase { public boolean sendNavigationTurnDistanceEvent(int distanceMeters, int timeSeconds) throws CarNotConnectedException { try { - mService.sendNavigationTurnDistanceEvent(distanceMeters, timeSeconds); + mService.onNextManeuverDistanceChanged(distanceMeters, timeSeconds); } catch (IllegalStateException e) { CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); } catch (RemoteException e) { @@ -191,56 +156,23 @@ public class CarNavigationManager implements CarManagerBase { } public boolean isInstrumentClusterSupported() throws CarNotConnectedException { - try { - return mService.isInstrumentClusterSupported(); - } catch (IllegalStateException e) { - CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); - } catch (RemoteException e) { - handleCarServiceRemoteExceptionAndThrow(e); - } - return false; + return mService != null; } @Override public void onCarDisconnected() { Log.d(TAG, "onCarDisconnected"); - unregisterListener(); } - /** - * @param listener {@link CarNavigationListener} to be registered, replacing any existing - * listeners. - * @throws CarNotConnectedException - */ - public void registerListener(CarNavigationListener listener) + /** Returns navigation features of instrument cluster */ + public CarNavigationInstrumentCluster getInstrumentClusterInfo() throws CarNotConnectedException { - mCarNavigationListener = listener; - if (mICarNavigationEventListenerImpl == null) { - mICarNavigationEventListenerImpl = - new ICarNavigationEventListenerImpl(this); - try { - mService.registerEventListener(mICarNavigationEventListenerImpl); - } catch (IllegalStateException e) { - CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); - } catch (RemoteException e) { - handleCarServiceRemoteExceptionAndThrow(e); - } - } - } - - /** - * Unregisters {@link CarNavigationListener}. - */ - public void unregisterListener() { try { - mService.unregisterEventListener(mICarNavigationEventListenerImpl); - } catch (IllegalStateException e) { - // ignore + return mService.getInstrumentClusterInfo(); } catch (RemoteException e) { - // do not throw exception as this can happen during tearing down. - handleCarServiceRemoteException(e); + handleCarServiceRemoteExceptionAndThrow(e); } - mCarNavigationListener = null; + return null; } private void handleCarServiceRemoteExceptionAndThrow(RemoteException e) @@ -253,38 +185,4 @@ public class CarNavigationManager implements CarManagerBase { Log.w(TAG, "RemoteException from car service:" + e.getMessage()); // nothing to do for now } - - private void handleOnStart(CarNavigationInstrumentCluster instrumentCluster) { - Log.d(TAG, "onStart(" + instrumentCluster + ")"); - mInstrumentCluster = new CarNavigationInstrumentCluster(instrumentCluster); - mHandler.sendMessage(mHandler.obtainMessage(START)); - } - - private void handleOnStop() { - mHandler.sendMessage(mHandler.obtainMessage(STOP)); - } - - private static class ICarNavigationEventListenerImpl extends ICarNavigationEventListener.Stub { - private final WeakReference<CarNavigationManager> mManager; - - public ICarNavigationEventListenerImpl(CarNavigationManager manager) { - mManager = new WeakReference<>(manager); - } - - @Override - public void onInstrumentClusterStart(CarNavigationInstrumentCluster instrumentClusterInfo) { - CarNavigationManager manager = mManager.get(); - if (manager != null) { - manager.handleOnStart(instrumentClusterInfo); - } - } - - @Override - public void onInstrumentClusterStop() { - CarNavigationManager manager = mManager.get(); - if (manager != null) { - manager.handleOnStop(); - } - } - } } diff --git a/car-support-lib/api/current.txt b/car-support-lib/api/current.txt index 0313f333a3..18d3ee310a 100644 --- a/car-support-lib/api/current.txt +++ b/car-support-lib/api/current.txt @@ -12,7 +12,7 @@ package android.support.car { method public boolean isConnecting(); method public void registerCarConnectionListener(android.support.car.CarConnectionListener) throws android.support.car.CarNotConnectedException, java.lang.IllegalStateException; method public void unregisterCarConnectionListener(android.support.car.CarConnectionListener); - field public static final java.lang.String APP_CONTEXT_SERVICE = "app_context"; + field public static final java.lang.String APP_FOCUS_SERVICE = "app_focus"; field public static final java.lang.String AUDIO_SERVICE = "audio"; field public static final int CONNECTION_TYPE_ADB_EMULATOR = 4; // 0x4 field public static final int CONNECTION_TYPE_EMBEDDED = 5; // 0x5 @@ -30,6 +30,30 @@ package android.support.car { field public static final java.lang.String SENSOR_SERVICE = "sensor"; } + public abstract class CarAppFocusManager { + ctor public CarAppFocusManager(); + method public abstract void abandonAppFocus(android.support.car.CarAppFocusManager.AppFocusOwnershipChangeListener, int) throws android.support.car.CarNotConnectedException; + method public abstract void abandonAppFocus(android.support.car.CarAppFocusManager.AppFocusOwnershipChangeListener) throws android.support.car.CarNotConnectedException; + method public abstract int[] getActiveAppTypes() throws android.support.car.CarNotConnectedException; + method public abstract boolean isOwningFocus(android.support.car.CarAppFocusManager.AppFocusOwnershipChangeListener, int) throws android.support.car.CarNotConnectedException; + method public abstract void registerFocusListener(android.support.car.CarAppFocusManager.AppFocusChangeListener, int) throws android.support.car.CarNotConnectedException; + method public abstract int requestAppFocus(android.support.car.CarAppFocusManager.AppFocusOwnershipChangeListener, int) throws android.support.car.CarNotConnectedException, java.lang.IllegalStateException, java.lang.SecurityException; + method public abstract void unregisterFocusListener(android.support.car.CarAppFocusManager.AppFocusChangeListener, int) throws android.support.car.CarNotConnectedException; + method public abstract void unregisterFocusListener(android.support.car.CarAppFocusManager.AppFocusChangeListener) throws android.support.car.CarNotConnectedException; + field public static final int APP_FOCUS_REQUEST_FAILED = 0; // 0x0 + field public static final int APP_FOCUS_REQUEST_GRANTED = 1; // 0x1 + field public static final int APP_FOCUS_TYPE_NAVIGATION = 1; // 0x1 + field public static final int APP_FOCUS_TYPE_VOICE_COMMAND = 2; // 0x2 + } + + public static abstract interface CarAppFocusManager.AppFocusChangeListener { + method public abstract void onAppFocusChange(int, boolean); + } + + public static abstract interface CarAppFocusManager.AppFocusOwnershipChangeListener { + method public abstract void onAppFocusOwnershipLoss(int); + } + public abstract interface CarConnectionListener { method public abstract void onConnected(int); method public abstract void onDisconnected(); @@ -647,11 +671,11 @@ package android.support.car.media { public abstract class CarAudioManager { ctor public CarAudioManager(); method public abstract int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes); - method public abstract android.support.car.media.CarAudioRecord createCarAudioRecord(int) throws java.lang.SecurityException; + method public abstract android.support.car.media.CarAudioRecord createCarAudioRecord(int) throws android.support.car.CarNotConnectedException, android.support.car.CarNotSupportedException, java.lang.SecurityException; method public abstract android.media.AudioAttributes getAudioAttributesForCarUsage(int); method public abstract android.media.AudioFormat getAudioRecordAudioFormat(); - method public abstract int getAudioRecordMaxBufferSize(); - method public abstract int getAudioRecordMinBufferSize(); + method public abstract int getAudioRecordMaxBufferSize() throws android.support.car.CarNotConnectedException, android.support.car.CarNotSupportedException; + method public abstract int getAudioRecordMinBufferSize() throws android.support.car.CarNotConnectedException, android.support.car.CarNotSupportedException; method public abstract boolean isMediaMuted() throws android.support.car.CarNotConnectedException; method public abstract int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException; field public static final int CAR_AUDIO_USAGE_ALARM = 6; // 0x6 @@ -671,9 +695,9 @@ package android.support.car.media { method public abstract int getBufferSize(); method public abstract int getRecordingState(); method public abstract int getState(); - method public abstract int read(byte[], int, int) throws java.lang.IllegalStateException; + method public abstract int read(byte[], int, int) throws android.support.car.CarNotConnectedException, java.lang.IllegalStateException; method public abstract void release(); - method public abstract void startRecording(); + method public abstract void startRecording() throws android.support.car.CarNotConnectedException; method public abstract void stop(); } diff --git a/car-support-lib/src/android/support/car/Car.java b/car-support-lib/src/android/support/car/Car.java index d628e858a9..259c369ef8 100644 --- a/car-support-lib/src/android/support/car/Car.java +++ b/car-support-lib/src/android/support/car/Car.java @@ -16,17 +16,17 @@ package android.support.car; -import android.support.annotation.IntDef; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.os.Handler; import android.os.Looper; +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.car.content.pm.CarPackageManager; import android.support.car.hardware.CarSensorManager; -import android.support.car.navigation.CarNavigationManager; +import android.support.car.navigation.CarNavigationStatusManager; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -49,8 +49,8 @@ public class Car { /** Service name for {@link CarInfoManager}, to be used in {@link #getCarManager(String)}. */ public static final String INFO_SERVICE = "info"; - /** Service name for {@link CarAppContextManager}. */ - public static final String APP_CONTEXT_SERVICE = "app_context"; + /** Service name for {@link CarAppFocusManager}. */ + public static final String APP_FOCUS_SERVICE = "app_focus"; /** Service name for {@link CarPackageManager} */ public static final String PACKAGE_SERVICE = "package"; @@ -58,7 +58,7 @@ public class Car { /** Service name for {@link CarAudioManager} */ public static final String AUDIO_SERVICE = "audio"; /** - * Service name for {@link CarNavigationManager} + * Service name for {@link CarNavigationStatusManager} * @hide */ public static final String CAR_NAVIGATION_SERVICE = "car_navigation_service"; diff --git a/car-support-lib/src/android/support/car/CarAppContextManager.java b/car-support-lib/src/android/support/car/CarAppContextManager.java deleted file mode 100644 index 6768e380a9..0000000000 --- a/car-support-lib/src/android/support/car/CarAppContextManager.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2015 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 android.support.car; - -/** - * CarAppContextManager allows applications to set and listen for the current application context - * like active navigation or active voice command. Usually only one instance of such application - * should run in the system, and other app setting the flag for the matching app should - * lead into other app to stop. - * @hide - */ -public abstract class CarAppContextManager implements CarManagerBase { - /** - * Listener to get notification for app getting information on app context change. - */ - public interface AppContextChangeListener { - /** - * Application context has changed. Note that {@link CarAppContextManager} instance - * causing the change will not get this notification. - * @param activeContexts - */ - void onAppContextChange(int activeContexts); - } - - /** - * Listener to get notification for app getting information on app context ownership loss. - */ - public interface AppContextOwnershipChangeListener { - /** - * Lost ownership for the context, which happens when other app has set the context. - * The app losing context should stop the action associated with the context. - * For example, navigation app currently running active navigation should stop navigation - * upon getting this for {@link CarAppContextManager#APP_CONTEXT_NAVIGATION}. - * @param context - */ - void onAppContextOwnershipLoss(int context); - } - - /** @hide */ - public static final int APP_CONTEXT_START_FLAG = 0x1; - /** - * Flag for active navigation. - */ - public static final int APP_CONTEXT_NAVIGATION = 0x1; - /** - * Flag for active voice command. - */ - public static final int APP_CONTEXT_VOICE_COMMAND = 0x2; - /** - * Update this after adding a new flag. - * @hide - */ - public static final int APP_CONTEXT_END_FLAG = 0x2; - - /** - * Register listener to monitor app context change. Only one listener can be registered and - * registering multiple times will lead into only the last listener to be active. - * @param listener - * @param contextFilter Flags of contexts to get notification. - * @throws CarNotConnectedException - */ - public abstract void registerContextListener(AppContextChangeListener listener, - int contextFilter) throws CarNotConnectedException; - - /** - * Unregister listener and stop listening context change events. If app has owned a context - * by {@link #setActiveContext(int)}, it will be reset to inactive state. - * @throws CarNotConnectedException - */ - public abstract void unregisterContextListener() throws CarNotConnectedException; - - /** - * Retrieve currently active contexts. - * @return - * @throws CarNotConnectedException - */ - public abstract int getActiveAppContexts() throws CarNotConnectedException; - - /** - * Check if the current process is owning the given context. - * @param context - * @return - * @throws CarNotConnectedException - */ - public abstract boolean isOwningContext(int context) throws CarNotConnectedException; - - /** - * Set the given contexts as active. By setting this, the application is becoming owner - * of the context, and will get {@link AppContextChangeListener#onAppContextOwnershipLoss(int)} - * if ownership is given to other app by calling this. Fore-ground app will have higher priority - * and other app cannot set the same context while owner is in fore-ground. - * Before calling this, {@link #registerContextListener(AppContextChangeListener, int)} should - * be called first. Otherwise, it will throw IllegalStateException - * @param contexts - * @throws IllegalStateException If listener was not registered. - * @throws SecurityException If owner cannot be changed. - * @throws CarNotConnectedException - */ - public abstract void setActiveContexts(AppContextOwnershipChangeListener ownershipListener, - int contexts) throws IllegalStateException, SecurityException, CarNotConnectedException; - - /** - * Reset the given contexts, i.e. mark them as inactive. This also involves releasing ownership - * for the context. - * @param contexts - * @throws CarNotConnectedException - */ - public abstract void resetActiveContexts(int contexts) throws CarNotConnectedException; -} diff --git a/car-support-lib/src/android/support/car/CarAppContextManagerEmbedded.java b/car-support-lib/src/android/support/car/CarAppContextManagerEmbedded.java deleted file mode 100644 index cd145ac37b..0000000000 --- a/car-support-lib/src/android/support/car/CarAppContextManagerEmbedded.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2016 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 android.support.car; - -import java.util.HashMap; -import java.util.Map; - -/** - * Implementation of {@link CarAppContextManager} for embedded. - * @hide - */ -public class CarAppContextManagerEmbedded extends CarAppContextManager { - - private final android.car.CarAppContextManager mManager; - private AppContextChangeListenerProxy mListener; - private final Map<Integer, AppContextOwnershipChangeListenerProxy> mOwnershipListeners; - - /** - * @hide - */ - CarAppContextManagerEmbedded(Object manager) { - mManager = (android.car.CarAppContextManager) manager; - mOwnershipListeners = new HashMap<>(); - } - - @Override - public void registerContextListener(AppContextChangeListener listener, int contextFilter) - throws CarNotConnectedException { - if (listener == null) { - throw new IllegalArgumentException("null listener"); - } - AppContextChangeListenerProxy proxy = new AppContextChangeListenerProxy(listener); - synchronized(this) { - mListener = proxy; - } - try { - mManager.registerContextListener(proxy, contextFilter); - } catch (android.car.CarNotConnectedException e) { - throw new CarNotConnectedException(e); - } - } - - @Override - public void unregisterContextListener() throws CarNotConnectedException { - synchronized(this) { - mListener = null; - } - try { - mManager.unregisterContextListener(); - } catch (android.car.CarNotConnectedException e) { - throw new CarNotConnectedException(e); - } - - } - - @Override - public int getActiveAppContexts() throws CarNotConnectedException { - try { - return mManager.getActiveAppContexts(); - } catch (android.car.CarNotConnectedException e) { - throw new CarNotConnectedException(e); - } - } - - @Override - public boolean isOwningContext(int context) throws CarNotConnectedException { - try { - return mManager.isOwningContext(context); - } catch (android.car.CarNotConnectedException e) { - throw new CarNotConnectedException(e); - } - } - - @Override - public void setActiveContexts(AppContextOwnershipChangeListener ownershipListener, - int contexts) throws IllegalStateException, SecurityException, - CarNotConnectedException { - if (ownershipListener == null) { - throw new IllegalArgumentException("null listener"); - } - AppContextOwnershipChangeListenerProxy proxy = - new AppContextOwnershipChangeListenerProxy(ownershipListener); - synchronized(this) { - for (int flag = APP_CONTEXT_START_FLAG; flag <= APP_CONTEXT_END_FLAG; flag <<= 1) { - if ((flag & contexts) != 0) { - mOwnershipListeners.put(flag, proxy); - } - } - } - try{ - mManager.setActiveContexts(proxy, contexts); - } catch (android.car.CarNotConnectedException e) { - throw new CarNotConnectedException(e); - } - } - - @Override - public void resetActiveContexts(int contexts) throws CarNotConnectedException { - try { - mManager.resetActiveContexts(contexts); - } catch (android.car.CarNotConnectedException e) { - throw new CarNotConnectedException(e); - } - synchronized (this) { - for (int flag = APP_CONTEXT_START_FLAG; flag <= APP_CONTEXT_END_FLAG; flag <<= 1) { - if ((flag & contexts) != 0) { - mOwnershipListeners.remove(flag); - } - } - } - } - - @Override - public void onCarDisconnected() { - // nothing to do - } - - private static class AppContextChangeListenerProxy implements - android.car.CarAppContextManager.AppContextChangeListener { - - private final AppContextChangeListener mListener; - - public AppContextChangeListenerProxy(AppContextChangeListener listener) { - mListener = listener; - } - - @Override - public void onAppContextChange(int activeContexts) { - mListener.onAppContextChange(activeContexts); - } - } - - private static class AppContextOwnershipChangeListenerProxy implements - android.car.CarAppContextManager.AppContextOwnershipChangeListener { - - private final AppContextOwnershipChangeListener mListener; - - public AppContextOwnershipChangeListenerProxy(AppContextOwnershipChangeListener listener) { - mListener = listener; - } - - @Override - public void onAppContextOwnershipLoss(int context) { - mListener.onAppContextOwnershipLoss(context); - } - } -} diff --git a/car-support-lib/src/android/support/car/CarAppFocusManager.java b/car-support-lib/src/android/support/car/CarAppFocusManager.java new file mode 100644 index 0000000000..f5c8679fb2 --- /dev/null +++ b/car-support-lib/src/android/support/car/CarAppFocusManager.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2015 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 android.support.car; + +/** + * CarAppFocusManager allows applications to set and listen for the current application focus + * like active navigation or active voice command. Usually only one instance of such application + * should run in the system, and other app setting the flag for the matching app should + * lead into other app to stop. + */ +public abstract class CarAppFocusManager implements CarManagerBase { + /** + * Listener to get notification for app getting information on application type status changes. + */ + public interface AppFocusChangeListener { + /** + * Application focus has changed. Note that {@link CarAppFocusManager} instance + * causing the change will not get this notification. + * @param appType Application type that got status changed. + * @param active Active state: true - active, false - inactive. + */ + void onAppFocusChange(int appType, boolean active); + } + + /** + * Listener to get notification for app getting information on app type ownership loss. + */ + public interface AppFocusOwnershipChangeListener { + /** + * Lost ownership for the focus, which happens when other app has set the focus. + * The app losing focus should stop the action associated with the focus. + * For example, navigation app currently running active navigation should stop navigation + * upon getting this for {@link CarAppFocusManager#APP_FOCUS_TYPE_NAVIGATION}. + * @param appType + */ + void onAppFocusOwnershipLoss(int appType); + } + + /** + * Represents navigation focus. + */ + public static final int APP_FOCUS_TYPE_NAVIGATION = 1; + /** + * Represents voice command focus. + */ + public static final int APP_FOCUS_TYPE_VOICE_COMMAND = 2; + /** + * Update this after adding a new app type. + * @hide + */ + public static final int APP_FOCUS_TYPE_MAX = 2; + + /** + * A failed focus change request. + */ + public static final int APP_FOCUS_REQUEST_FAILED = 0; + /** + * A successful focus change request. + */ + public static final int APP_FOCUS_REQUEST_GRANTED = 1; + + /** + * Register listener to monitor app focus change. + * Multiple listeners can be registered for a single focus and the same listener can be used + * for multiple focuses. + * @param listener Listener to register for focus events. + * @param appType Application type to get notification for. + * @throws CarNotConnectedException + */ + public abstract void registerFocusListener(AppFocusChangeListener listener, int appType) + throws CarNotConnectedException; + + /** + * Unregister listener for app type and stop listening focus change events. + * @param listener Listener to unregister from focus events. + * @param appType Application type to get notification for. + * @throws CarNotConnectedException + */ + public abstract void unregisterFocusListener(AppFocusChangeListener listener, int appType) + throws CarNotConnectedException; + + /** + * Unregister listener for all app types and stop listening focus change events. + * @param listener Listener to unregister from focus events. + * @throws CarNotConnectedException + */ + public abstract void unregisterFocusListener(AppFocusChangeListener listener) + throws CarNotConnectedException; + + /** + * Retrieve currently active application types. + * @return Currently active application types. + * @throws CarNotConnectedException + */ + public abstract int[] getActiveAppTypes() throws CarNotConnectedException; + + /** + * Check if the current process owns the given focus. + * @param listener Listener that was used to request ownership. + * @param appType Application type. + * @return True if current listener owns focus for application type. + * @throws CarNotConnectedException + */ + public abstract boolean isOwningFocus(AppFocusOwnershipChangeListener listener, int appType) + throws CarNotConnectedException; + + /** + * Requests application focus. + * By requesting this, the app gains the focus for this appType. + * {@link AppFocusOwnershipChangeListener#onAppFocusOwnershipLoss(int)} will be sent to + * the app that currently holds focus. + * Fore-ground app will have higher priority and other app cannot set the same focus while + * owner is in fore-ground. + * @param ownershipListener Ownership listener to request app focus for. Cannot be null. + * @param appType Application type to request focus for. + * @return {@link #APP_FOCUS_REQUEST_FAILED} or {@link #APP_FOCUS_REQUEST_GRANTED} + * @throws IllegalStateException If listener was not registered. + * @throws SecurityException If owner cannot be changed. + * @throws CarNotConnectedException + */ + public abstract int requestAppFocus(AppFocusOwnershipChangeListener ownershipListener, + int appType) throws IllegalStateException, SecurityException, CarNotConnectedException; + + /** + * Abandon the given focus, i.e. mark it as inactive. + * @param ownershipListener Ownership listener to abandon app focus for. Cannot be null. + * @param appType Application type to abandon focus for. + * @throws CarNotConnectedException + */ + public abstract void abandonAppFocus(AppFocusOwnershipChangeListener ownershipListener, + int appType) throws CarNotConnectedException; + + /** + * Abandon all focuses, i.e. mark thme as inactive. + * @param ownershipListener Ownership listener to abandon focus for. Cannot be null. + * @throws CarNotConnectedException + */ + public abstract void abandonAppFocus(AppFocusOwnershipChangeListener ownershipListener) + throws CarNotConnectedException; +} diff --git a/car-support-lib/src/android/support/car/CarAppFocusManagerEmbedded.java b/car-support-lib/src/android/support/car/CarAppFocusManagerEmbedded.java new file mode 100644 index 0000000000..e6a99416b0 --- /dev/null +++ b/car-support-lib/src/android/support/car/CarAppFocusManagerEmbedded.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2016 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 android.support.car; + +import java.util.HashMap; +import java.util.Map; + +/** + * Implementation of {@link CarAppFocusManager} for embedded. + * @hide + */ +public class CarAppFocusManagerEmbedded extends CarAppFocusManager { + + private final android.car.CarAppFocusManager mManager; + + private final Map<AppFocusChangeListener, AppFocusChangeListenerProxy> + mChangeListeners = new HashMap<>(); + private final Map<AppFocusOwnershipChangeListener, AppFocusOwnershipChangeListenerProxy> + mOwnershipListeners = new HashMap<>(); + + /** + * @hide + */ + CarAppFocusManagerEmbedded(Object manager) { + mManager = (android.car.CarAppFocusManager) manager; + } + + @Override + public void registerFocusListener(AppFocusChangeListener listener, int appType) + throws CarNotConnectedException { + if (listener == null) { + throw new IllegalArgumentException("null listener"); + } + AppFocusChangeListenerProxy proxy; + synchronized (this) { + proxy = mChangeListeners.get(listener); + if (proxy == null) { + proxy = new AppFocusChangeListenerProxy(listener); + mChangeListeners.put(listener, proxy); + } + } + try { + mManager.registerFocusListener(proxy, appType); + } catch (android.car.CarNotConnectedException e) { + throw new CarNotConnectedException(e); + } + } + + @Override + public void unregisterFocusListener(AppFocusChangeListener listener, int appType) + throws CarNotConnectedException { + AppFocusChangeListenerProxy proxy; + synchronized (this) { + proxy = mChangeListeners.get(listener); + if (proxy == null) { + return; + } + } + try { + mManager.unregisterFocusListener(proxy, appType); + } catch (android.car.CarNotConnectedException e) { + throw new CarNotConnectedException(e); + } + } + + @Override + public void unregisterFocusListener(AppFocusChangeListener listener) + throws CarNotConnectedException { + AppFocusChangeListenerProxy proxy; + synchronized (this) { + proxy = mChangeListeners.remove(listener); + if (proxy == null) { + return; + } + } + try { + mManager.unregisterFocusListener(proxy); + } catch (android.car.CarNotConnectedException e) { + throw new CarNotConnectedException(e); + } + } + + @Override + public int[] getActiveAppTypes() throws CarNotConnectedException { + try { + return mManager.getActiveAppTypes(); + } catch (android.car.CarNotConnectedException e) { + throw new CarNotConnectedException(e); + } + } + + @Override + public boolean isOwningFocus(AppFocusOwnershipChangeListener listener, int appType) + throws CarNotConnectedException { + AppFocusOwnershipChangeListenerProxy proxy; + synchronized (this) { + proxy = mOwnershipListeners.get(listener); + if (proxy == null) { + return false; + } + } + try { + return mManager.isOwningFocus(proxy, appType); + } catch (android.car.CarNotConnectedException e) { + throw new CarNotConnectedException(e); + } + } + + @Override + public int requestAppFocus(AppFocusOwnershipChangeListener ownershipListener, int appType) + throws IllegalStateException, SecurityException, CarNotConnectedException { + if (ownershipListener == null) { + throw new IllegalArgumentException("null listener"); + } + AppFocusOwnershipChangeListenerProxy proxy; + synchronized (this) { + proxy = mOwnershipListeners.get(ownershipListener); + if (proxy == null) { + proxy = new AppFocusOwnershipChangeListenerProxy(ownershipListener); + mOwnershipListeners.put(ownershipListener, proxy); + } + } + try { + return mManager.requestAppFocus(proxy, appType); + } catch (android.car.CarNotConnectedException e) { + throw new CarNotConnectedException(e); + } + } + + @Override + public void abandonAppFocus(AppFocusOwnershipChangeListener ownershipListener, int appType) + throws CarNotConnectedException { + if (ownershipListener == null) { + throw new IllegalArgumentException("null listener"); + } + AppFocusOwnershipChangeListenerProxy proxy; + synchronized (this) { + proxy = mOwnershipListeners.get(ownershipListener); + if (proxy == null) { + return; + } + } + try { + mManager.abandonAppFocus(proxy, appType); + } catch (android.car.CarNotConnectedException e) { + throw new CarNotConnectedException(e); + } + } + + @Override + public void abandonAppFocus(AppFocusOwnershipChangeListener ownershipListener) + throws CarNotConnectedException { + if (ownershipListener == null) { + throw new IllegalArgumentException("null listener"); + } + AppFocusOwnershipChangeListenerProxy proxy; + synchronized (this) { + proxy = mOwnershipListeners.get(ownershipListener); + if (proxy == null) { + return; + } + } + try { + mManager.abandonAppFocus(proxy); + } catch (android.car.CarNotConnectedException e) { + throw new CarNotConnectedException(e); + } + } + + @Override + public void onCarDisconnected() { + // nothing to do + } + + private static class AppFocusChangeListenerProxy implements + android.car.CarAppFocusManager.AppFocusChangeListener { + + private final AppFocusChangeListener mListener; + + AppFocusChangeListenerProxy(AppFocusChangeListener listener) { + mListener = listener; + } + + @Override + public void onAppFocusChange(int appType, boolean active) { + mListener.onAppFocusChange(appType, active); + } + } + + private static class AppFocusOwnershipChangeListenerProxy implements + android.car.CarAppFocusManager.AppFocusOwnershipChangeListener { + + private final AppFocusOwnershipChangeListener mListener; + + AppFocusOwnershipChangeListenerProxy(AppFocusOwnershipChangeListener listener) { + mListener = listener; + } + + @Override + public void onAppFocusOwnershipLoss(int focus) { + mListener.onAppFocusOwnershipLoss(focus); + } + } +} diff --git a/car-support-lib/src/android/support/car/CarServiceLoaderEmbedded.java b/car-support-lib/src/android/support/car/CarServiceLoaderEmbedded.java index f22b95a218..84a2911785 100644 --- a/car-support-lib/src/android/support/car/CarServiceLoaderEmbedded.java +++ b/car-support-lib/src/android/support/car/CarServiceLoaderEmbedded.java @@ -26,7 +26,7 @@ import android.os.Message; import android.support.car.content.pm.CarPackageManagerEmbedded; import android.support.car.hardware.CarSensorManagerEmbedded; import android.support.car.media.CarAudioManagerEmbedded; -import android.support.car.navigation.CarNavigationManagerEmbedded; +import android.support.car.navigation.CarNavigationStatusManagerEmbedded; import java.util.LinkedList; @@ -121,12 +121,16 @@ public class CarServiceLoaderEmbedded extends CarServiceLoader { return new CarSensorManagerEmbedded(manager, getContext()); case Car.INFO_SERVICE: return new CarInfoManagerEmbedded(manager); - case Car.APP_CONTEXT_SERVICE: - return new CarAppContextManagerEmbedded(manager); + case Car.APP_FOCUS_SERVICE: + return new CarAppFocusManagerEmbedded(manager); case Car.PACKAGE_SERVICE: return new CarPackageManagerEmbedded(manager); case Car.CAR_NAVIGATION_SERVICE: - return new CarNavigationManagerEmbedded(manager); + try { + return new CarNavigationStatusManagerEmbedded(manager); + } catch (CarNotSupportedException e) { + return null; + } default: return manager; } diff --git a/car-support-lib/src/android/support/car/media/CarAudioManager.java b/car-support-lib/src/android/support/car/media/CarAudioManager.java index 864f5ac49c..16809c1d15 100644 --- a/car-support-lib/src/android/support/car/media/CarAudioManager.java +++ b/car-support-lib/src/android/support/car/media/CarAudioManager.java @@ -23,6 +23,7 @@ import android.support.annotation.IntDef; import android.support.annotation.RequiresPermission; import android.support.car.CarManagerBase; import android.support.car.CarNotConnectedException; +import android.support.car.CarNotSupportedException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -119,30 +120,37 @@ public abstract class CarAudioManager implements CarManagerBase { /** * Get minimum buffer size for {@link CarAudioRecord}. + * * @return buffer size in bytes. */ - public abstract int getAudioRecordMinBufferSize(); + public abstract int getAudioRecordMinBufferSize() + throws CarNotConnectedException, CarNotSupportedException; /** * Get maximum buffer size for {@link CarAudioRecord}. + * * @return buffer size in bytes. */ - public abstract int getAudioRecordMaxBufferSize(); + public abstract int getAudioRecordMaxBufferSize() + throws CarNotConnectedException, CarNotSupportedException; /** * Create a {@link CarAudioRecord} for the current {@link CarAudioManager}. There can be - * multiple instances of {@link CarAudioRecord}. - * This requires {@link android.Manifest.permission#RECORD_AUDIO} permission. - * @param bufferSize It should be a multiple of minimum buffer size acquired from - * {@link #getAudioRecordMinBufferSize()}. This cannot exceed - * {@link #getAudioRecordMaxBufferSize()}. + * multiple instances of {@link CarAudioRecord}. This requires {@link + * android.Manifest.permission#RECORD_AUDIO} permission. + * + * @param bufferSize It should be a multiple of minimum buffer size acquired from {@link + * #getAudioRecordMinBufferSize()}. This cannot exceed {@link #getAudioRecordMaxBufferSize()}. + * * @return {@link CarAudioRecord} instance for the given stream. * @throws IllegalArgumentException if passed parameter like bufferSize is wrong. * @throws SecurityException if client does not have - * {@link android.Manifest.permission#RECORD_AUDIO} permission. + * {@link android.Manifest.permission#RECORD_AUDIO} + * permission. */ @RequiresPermission(Manifest.permission.RECORD_AUDIO) - public abstract CarAudioRecord createCarAudioRecord(int bufferSize) throws SecurityException; + public abstract CarAudioRecord createCarAudioRecord(int bufferSize) + throws SecurityException, CarNotConnectedException, CarNotSupportedException; /** * Check if media audio is muted or not. This will include music and radio. Any application diff --git a/car-support-lib/src/android/support/car/media/CarAudioRecord.java b/car-support-lib/src/android/support/car/media/CarAudioRecord.java index 5e8e945526..98d3535dce 100644 --- a/car-support-lib/src/android/support/car/media/CarAudioRecord.java +++ b/car-support-lib/src/android/support/car/media/CarAudioRecord.java @@ -15,6 +15,8 @@ */ package android.support.car.media; +import android.support.car.CarNotConnectedException; + /** * CarAudioRecord allows apps to use microphone. */ @@ -28,7 +30,7 @@ public interface CarAudioRecord { /** * Start audio recording. */ - void startRecording(); + void startRecording() throws CarNotConnectedException; /** * Stop audio recording. Calling stop multiple times will be a safe operation. @@ -60,5 +62,5 @@ public interface CarAudioRecord { * @throws IllegalStateException if audio recording was not started. */ int read(byte[] audioData, int offsetInBytes, int sizeInBytes) - throws IllegalStateException; + throws IllegalStateException, CarNotConnectedException; } diff --git a/car-support-lib/src/android/support/car/navigation/CarNavigationManager.java b/car-support-lib/src/android/support/car/navigation/CarNavigationStatusManager.java index d723bc0719..0142fc5987 100644 --- a/car-support-lib/src/android/support/car/navigation/CarNavigationManager.java +++ b/car-support-lib/src/android/support/car/navigation/CarNavigationStatusManager.java @@ -23,7 +23,7 @@ import android.support.car.CarNotConnectedException; * API for providing navigation status for instrument cluster. * @hide */ -public abstract class CarNavigationManager implements CarManagerBase { +public interface CarNavigationStatusManager extends CarManagerBase { /** * Listener navigation related events. @@ -64,12 +64,27 @@ public abstract class CarNavigationManager implements CarManagerBase { public static final int TURN_SIDE_RIGHT = 2; public static final int TURN_SIDE_UNSPECIFIED = 3; + + /** + * Distance units for use in {@link #sendNavigationTurnDistanceEvent(int, int, int, int)}. + * DISTANCE_KILOMETERS_P1 and DISTANCE_MILES_P1 are the same as their respective + * units, except they require that the head unit display at least 1 digit after the + * decimal (e.g. 2.0). + */ + public static final int DISTANCE_METERS = 1; + public static final int DISTANCE_KILOMETERS = 2; + public static final int DISTANCE_KILOMETERS_P1 = 3; + public static final int DISTANCE_MILES = 4; + public static final int DISTANCE_MILES_P1 = 5; + public static final int DISTANCE_FEET = 6; + public static final int DISTANCE_YARDS = 7; + /** * @param status new instrument cluster navigation status. * @return true if successful. * @throws CarNotConnectedException */ - public abstract boolean sendNavigationStatus(int status) throws CarNotConnectedException; + boolean sendNavigationStatus(int status) throws CarNotConnectedException; /** * Sends a Navigation Next Step event to the car. @@ -88,9 +103,9 @@ public abstract class CarNavigationManager implements CarManagerBase { * used for event type {@link #TURN_ROUNDABOUT_ENTER_AND_EXIT}. -1 if unused. * @param turnNumber turn number, counting around from the roundabout entry to the exit. Only * used for event type {@link #TURN_ROUNDABOUT_ENTER_AND_EXIT}. -1 if unused. - * @param image image to be shown in the instrument cluster (PNG format). Null if instrument - * cluster type is {@link #INSTRUMENT_CLUSTER_TYPE_ENUM}, or if - * the image parameters are malformed (length or width non-positive, or illegal + * @param image image to be shown in the instrument cluster. Null if instrument + * cluster type is {@link CarNavigationInstrumentCluster.ClusterType#IMAGE_CODES_ONLY}, + * or if the image parameters are malformed (length or width non-positive, or illegal * imageColorDepthBits) in the initial NavigationStatusService call. * @param turnSide turn side ({@link #TURN_SIDE_LEFT}, {@link #TURN_SIDE_RIGHT} or * {@link #TURN_SIDE_UNSPECIFIED}). @@ -98,32 +113,34 @@ public abstract class CarNavigationManager implements CarManagerBase { * @throws CarNotConnectedException * */ - public abstract boolean sendNavigationTurnEvent(int event, String road, int turnAngle, + boolean sendNavigationTurnEvent(int event, String road, int turnAngle, int turnNumber, Bitmap image, int turnSide) throws CarNotConnectedException; /** * Sends a Navigation Next Step Distance event to the car. * - * @param distanceMeters Distance to next event in meters. - * @param timeSeconds Time to next event in seconds. + * @param distanceMeters distance to next event in meters. + * @param timeSeconds time to next event in seconds. + * @param displayDistanceMillis distance to the next event formatted as it will be displayed + * by the calling app, in milli-units. For example, 1.25 should be supplied as 1250 + * @param displayDistanceUnit the unit type to use on of the DISTANCE_* types defined in this + * file. * @return true if successful. * @throws CarNotConnectedException */ - public abstract boolean sendNavigationTurnDistanceEvent(int distanceMeters, int timeSeconds) - throws CarNotConnectedException; - - public abstract boolean isInstrumentClusterSupported() throws CarNotConnectedException; + boolean sendNavigationTurnDistanceEvent(int distanceMeters, int timeSeconds, + int displayDistanceMillis, int displayDistanceUnit) throws CarNotConnectedException; /** * @param listener {@link CarNavigationListener} to be registered, replacing any existing * listeners. * @throws CarNotConnectedException */ - public abstract void registerListener(CarNavigationListener listener) + void registerListener(CarNavigationListener listener) throws CarNotConnectedException; /** * Unregisters {@link CarNavigationListener}. */ - public abstract void unregisterListener(); + void unregisterListener(); } diff --git a/car-support-lib/src/android/support/car/navigation/CarNavigationManagerEmbedded.java b/car-support-lib/src/android/support/car/navigation/CarNavigationStatusManagerEmbedded.java index 65b52113a3..32ded94a11 100644 --- a/car-support-lib/src/android/support/car/navigation/CarNavigationManagerEmbedded.java +++ b/car-support-lib/src/android/support/car/navigation/CarNavigationStatusManagerEmbedded.java @@ -17,17 +17,25 @@ package android.support.car.navigation; import android.graphics.Bitmap; import android.support.car.CarNotConnectedException; +import android.support.car.CarNotSupportedException; /** * @hide */ -public class CarNavigationManagerEmbedded extends CarNavigationManager { +public class CarNavigationStatusManagerEmbedded implements CarNavigationStatusManager { private final android.car.navigation.CarNavigationManager mManager; - private CarNavigationListenerProxy mListener; - public CarNavigationManagerEmbedded(Object manager) { + public CarNavigationStatusManagerEmbedded(Object manager) + throws CarNotSupportedException, CarNotConnectedException { mManager = (android.car.navigation.CarNavigationManager) manager; + try { + if (!mManager.isInstrumentClusterSupported()){ + throw new CarNotSupportedException(); + } + } catch (android.car.CarNotConnectedException e) { + throw new CarNotConnectedException(e); + } } /** @@ -56,8 +64,8 @@ public class CarNavigationManagerEmbedded extends CarNavigationManager { } @Override - public boolean sendNavigationTurnDistanceEvent(int distanceMeters, int timeSeconds) - throws CarNotConnectedException { + public boolean sendNavigationTurnDistanceEvent(int distanceMeters, int timeSeconds, + int displayDistanceMillis, int displayDistanceUnit) throws CarNotConnectedException { try { return mManager.sendNavigationTurnDistanceEvent(distanceMeters, timeSeconds); } catch (android.car.CarNotConnectedException e) { @@ -66,29 +74,22 @@ public class CarNavigationManagerEmbedded extends CarNavigationManager { } @Override - public boolean isInstrumentClusterSupported() throws CarNotConnectedException { - try { - return mManager.isInstrumentClusterSupported(); - } catch (android.car.CarNotConnectedException e) { - throw new CarNotConnectedException(e); - } - } - - @Override public void onCarDisconnected() { //nothing to do } + /** + * In this implementation we just immediately call {@code listener#onInstrumentClusterStart} as + * we expect instrument cluster to be working all the time. + * + * @throws CarNotConnectedException + */ @Override public void registerListener(CarNavigationListener listener) throws CarNotConnectedException { - CarNavigationListenerProxy proxy = null; - synchronized (this) { - proxy = new CarNavigationListenerProxy(listener); - mListener = proxy; - } + try { - mManager.registerListener(proxy); + listener.onInstrumentClusterStart(convert(mManager.getInstrumentClusterInfo())); } catch (android.car.CarNotConnectedException e) { throw new CarNotConnectedException(e); } @@ -96,10 +97,7 @@ public class CarNavigationManagerEmbedded extends CarNavigationManager { @Override public void unregisterListener() { - synchronized (this) { - mListener = null; - } - mManager.unregisterListener(); + // Nothing to do. } private static CarNavigationInstrumentCluster convert( @@ -110,25 +108,4 @@ public class CarNavigationManagerEmbedded extends CarNavigationManager { return new CarNavigationInstrumentCluster(ic.getMinIntervalMs(), ic.getType(), ic.getImageWidth(), ic.getImageHeight(), ic.getImageColorDepthBits()); } - - private static class CarNavigationListenerProxy implements - android.car.navigation.CarNavigationManager.CarNavigationListener { - - private final CarNavigationListener mListener; - - private CarNavigationListenerProxy(CarNavigationListener listener) { - mListener = listener; - } - - @Override - public void onInstrumentClusterStart( - android.car.navigation.CarNavigationInstrumentCluster instrumentCluster) { - mListener.onInstrumentClusterStart(convert(instrumentCluster)); - } - - @Override - public void onInstrumentClusterStop() { - mListener.onInstrumentClusterStop(); - } - } } diff --git a/car-systemtest-lib/src/android/car/test/VehicleHalEmulator.java b/car-systemtest-lib/src/android/car/test/VehicleHalEmulator.java index fdb81074c4..afe3f4c210 100644 --- a/car-systemtest-lib/src/android/car/test/VehicleHalEmulator.java +++ b/car-systemtest-lib/src/android/car/test/VehicleHalEmulator.java @@ -202,6 +202,7 @@ public class VehicleHalEmulator { switch (property) { case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME: case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS: + case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT: case VehicleNetworkConsts.VEHICLE_PROPERTY_AP_POWER_STATE: case VehicleNetworkConsts.VEHICLE_PROPERTY_AP_POWER_BOOTUP_REASON: continue; diff --git a/car_product/build/car.mk b/car_product/build/car.mk index cf7b9a4b3d..aa8a597cbe 100644 --- a/car_product/build/car.mk +++ b/car_product/build/car.mk @@ -18,7 +18,6 @@ PRODUCT_PACKAGES += \ Bluetooth \ - BluetoothMidiService \ bt-map-service \ OneTimeInitializer \ Provision \ diff --git a/car_product/build/car_base.mk b/car_product/build/car_base.mk index 590a7e7b94..d192a81581 100644 --- a/car_product/build/car_base.mk +++ b/car_product/build/car_base.mk @@ -26,7 +26,6 @@ PRODUCT_PACKAGES += \ BasicDreams \ CaptivePortalLogin \ CertInstaller \ - DeskClock \ DocumentsUI \ DownloadProviderUi \ FusedLocation \ diff --git a/car_product/overlay/frameworks/base/core/res/res/values/config.xml b/car_product/overlay/frameworks/base/core/res/res/values/config.xml index 141550ba56..584961a1a6 100644 --- a/car_product/overlay/frameworks/base/core/res/res/values/config.xml +++ b/car_product/overlay/frameworks/base/core/res/res/values/config.xml @@ -41,6 +41,8 @@ <!-- Allow smart unlock immediately after boot because the user shouldn't have to enter a pin code to unlock their car head unit. --> <bool name="config_strongAuthRequiredOnBoot">false</bool> + <!-- Show Navigation Bar --> + <bool name="config_showNavigationBar">true</bool> <integer name="config_jobSchedulerInactivityIdleThreshold">0</integer> <integer name="config_jobSchedulerIdleWindowSlop">0</integer> diff --git a/car_product/sepolicy/atfwd.te b/car_product/sepolicy/atfwd.te deleted file mode 100644 index f9c5eb1b0c..0000000000 --- a/car_product/sepolicy/atfwd.te +++ /dev/null @@ -1,14 +0,0 @@ -type atfwd, domain; -type atfwd_exec, exec_type, file_type; - -init_daemon_domain(atfwd) - -# Creates/Talks to qmuxd via the qmux_radio socket. -qmux_socket(atfwd) - -# Set radio.atfwd.* properties. -set_prop(atfwd, radio_atfwd_prop) - -userdebug_or_eng(` - allow atfwd diag_device:chr_file rw_file_perms; -') diff --git a/car_product/sepolicy/bluetooth.te b/car_product/sepolicy/bluetooth.te deleted file mode 100644 index f93e040eca..0000000000 --- a/car_product/sepolicy/bluetooth.te +++ /dev/null @@ -1,18 +0,0 @@ -# Allow access to wc_transport.* properties. -set_prop(bluetooth, wc_transport_prop) - -# Allow access to /dev/ttyHS0 -allow bluetooth serial_device:chr_file rw_file_perms; - -# Connect to start_hci_filter service. -allow bluetooth start_hci_filter:unix_stream_socket connectto; - -# Allow access to /persist/.bt_nv.bin. -allow bluetooth persist_file:file rw_file_perms; - -# Allow access to /bt_firmware files. -allow bluetooth bt_firmware_file:file r_file_perms; - -# Allow access to bt_power sysfs nodes. -r_dir_file(bluetooth, sysfs_bt_power); -allow bluetooth sysfs_bt_power:file w_file_perms; diff --git a/car_product/sepolicy/can.te b/car_product/sepolicy/can.te deleted file mode 100644 index e18b839bec..0000000000 --- a/car_product/sepolicy/can.te +++ /dev/null @@ -1,18 +0,0 @@ -# CAN service -type can, domain; -type can_exec, exec_type, file_type; - -# Started by init -init_daemon_domain(can) - -allow can self:capability net_admin; - -allow can self:netlink_route_socket nlmsg_write; - -allow can shell_exec:file r_file_perms; - -# Allow execution of /system/bin/ip. -allow can system_file:file rx_file_perms; - -# Allow can operations -allow can self:capability { net_raw }; diff --git a/car_product/sepolicy/config_bluetooth.te b/car_product/sepolicy/config_bluetooth.te deleted file mode 100644 index f0c8789589..0000000000 --- a/car_product/sepolicy/config_bluetooth.te +++ /dev/null @@ -1,22 +0,0 @@ -# config_bluetooth service -type config_bluetooth, domain; -type config_bluetooth_exec, exec_type, file_type; - -# Started by init -init_daemon_domain(config_bluetooth) - -# Set bluetooth.* and qualcomm.bluetooth.* properties. -set_prop(config_bluetooth, bluetooth_prop); - -# Allow execution of /system/bin/btnvtool. -allow config_bluetooth btnvtool_exec:file rx_file_perms; - -# Allow access to /persist/.bt_nv.bin. -allow config_bluetooth persist_file:dir w_dir_perms; -allow config_bluetooth persist_file:file create_file_perms; - -allow config_bluetooth shell_exec:file r_file_perms; - -allow config_bluetooth toolbox_exec:file rx_file_perms; - -allow config_bluetooth sysfs:file r_file_perms; diff --git a/car_product/sepolicy/device.te b/car_product/sepolicy/device.te deleted file mode 100644 index 38d26183d1..0000000000 --- a/car_product/sepolicy/device.te +++ /dev/null @@ -1,14 +0,0 @@ -# Define the logging device type -type diag_device, dev_type, mlstrustedobject; - -#device type for gss device nodes, ie /dev/gss -type gss_device, dev_type; - -type hsic_device, dev_type; -type latency_device, dev_type; -type modem_block_device, dev_type; -type persist_block_device, dev_type; -type qmuxd_socket, dev_type; -type smem_log_device, dev_type; -type ssd_block_device, dev_type; -type wcnss_device, dev_type; diff --git a/car_product/sepolicy/domain.te b/car_product/sepolicy/domain.te index 67cbce6937..bb451f322e 100644 --- a/car_product/sepolicy/domain.te +++ b/car_product/sepolicy/domain.te @@ -1,48 +1,3 @@ # Ignore personality-8 denials. -dontaudit adbd kernel:system module_request; -dontaudit atfwd kernel:system module_request; -dontaudit bootanim kernel:system module_request; -dontaudit can kernel:system module_request; -dontaudit config_bluetooth kernel:system module_request; -dontaudit debuggerd kernel:system module_request; -dontaudit drmserver kernel:system module_request; -dontaudit fsck kernel:system module_request; -dontaudit gatekeeperd kernel:system module_request; -dontaudit healthd kernel:system module_request; -dontaudit init kernel:system module_request; -dontaudit installd kernel:system module_request; -dontaudit irsc_util kernel:system module_request; -dontaudit keystore kernel:system module_request; -dontaudit lmkd kernel:system module_request; -dontaudit logd kernel:system module_request; -dontaudit mm-pp-daemon kernel:system module_request; -dontaudit modem-sh kernel:system module_request; -dontaudit mpdecision kernel:system module_request; -dontaudit netd kernel:system module_request; -dontaudit netmgrd kernel:system module_request; -dontaudit qcom-c_core-sh kernel:system module_request; -dontaudit qcom-c_main-sh kernel:system module_request; -dontaudit qcom-post-boot kernel:system module_request; -dontaudit qcom-sh kernel:system module_request; -dontaudit qcom-usb-sh kernel:system module_request; -dontaudit qmuxd kernel:system module_request; -dontaudit rmt_storage kernel:system module_request; -dontaudit sdcardd kernel:system module_request; -dontaudit servicemanager kernel:system module_request; -dontaudit shell kernel:system module_request; -dontaudit start_hci_filter kernel:system module_request; -dontaudit surfaceflinger kernel:system module_request; -dontaudit thermal-engine kernel:system module_request; -dontaudit time_daemon kernel:system module_request; -dontaudit tzdatacheck kernel:system module_request; -dontaudit ueventd kernel:system module_request; -dontaudit untrusted_app kernel:system module_request; -dontaudit usf-post-boot kernel:system module_request; -dontaudit vns kernel:system module_request; -dontaudit vold kernel:system module_request; -dontaudit wcnss_service kernel:system module_request; -dontaudit zygote kernel:system module_request; +dontaudit domain kernel:system module_request; -userdebug_or_eng(` - dontaudit perfprofd kernel:system module_request; -') diff --git a/car_product/sepolicy/file.te b/car_product/sepolicy/file.te deleted file mode 100644 index 2aaa844c70..0000000000 --- a/car_product/sepolicy/file.te +++ /dev/null @@ -1,21 +0,0 @@ -type btnvtool_exec, exec_type, file_type; - -# Bluetooth firmware file types -type bt_firmware_file, contextmount_type, fs_type; - -# Default type for anything under /firmware. -type firmware_file, contextmount_type, fs_type; - -type mpdecision_socket, file_type; -type perfd_data_file, file_type, data_file_type; -type persist_file, file_type; -type pps_socket, file_type; -type sysfs_bt_power, sysfs_type, fs_type; -type sysfs_dcvs, sysfs_type, fs_type; -type sysfs_hsic_modem_wait, sysfs_type, fs_type; -type sysfs_mpdecision, sysfs_type, fs_type; -type sysfs_rpm_resources, sysfs_type, fs_type; -type sysfs_smd_open_timeout, sysfs_type, fs_type; -type sysfs_usb, sysfs_type, fs_type; -type thermal_socket, file_type; -type time_data_file, file_type, data_file_type; diff --git a/car_product/sepolicy/file_contexts b/car_product/sepolicy/file_contexts index 55b32761e5..e95a012c09 100644 --- a/car_product/sepolicy/file_contexts +++ b/car_product/sepolicy/file_contexts @@ -1,97 +1,9 @@ -################################### -# Data files -# -/data/misc/perfd(/.*)? u:object_r:perfd_data_file:s0 -/data/system/perfd(/.*)? u:object_r:perfd_data_file:s0 -/data/time(/.*)? u:object_r:time_data_file:s0 - -################################### -# Dev nodes -# -/dev/cpu_dma_latency u:object_r:latency_device:s0 -/dev/diag u:object_r:diag_device:s0 -/dev/kgsl-3d0 u:object_r:gpu_device:s0 -/dev/gss u:object_r:gss_device:s0 -/dev/hsicctl[0-3] u:object_r:hsic_device:s0 -/dev/mdp_arb u:object_r:graphics_device:s0 -/dev/media([0-9])+ u:object_r:video_device:s0 -/dev/msm_acdb u:object_r:audio_device:s0 -/dev/msm_camera(/.*)? u:object_r:video_device:s0 -/dev/msm_rotator u:object_r:video_device:s0 -/dev/msm_vidc_.* u:object_r:video_device:s0 -/dev/smem_log u:object_r:smem_log_device:s0 -/dev/socket/mpdecision u:object_r:mpdecision_socket:s0 -/dev/socket/pps u:object_r:pps_socket:s0 -/dev/socket/thermal-recv-client u:object_r:thermal_socket:s0 -/dev/socket/thermal-send-client u:object_r:thermal_socket:s0 -/dev/socket/qmux_radio(/.*)? u:object_r:qmuxd_socket:s0 -/dev/ttyHS[0-9]* u:object_r:serial_device:s0 -/dev/v4l-subdev.* u:object_r:video_device:s0 -/dev/wcnss_ctrl u:object_r:wcnss_device:s0 -/dev/wcnss_wlan u:object_r:wcnss_device:s0 - -################################### -# Dev block nodes -# -/dev/block/platform/msm_sdcc\.1/by-name/boot u:object_r:boot_block_device:s0 -/dev/block/platform/msm_sdcc\.1/by-name/cache u:object_r:cache_block_device:s0 -/dev/block/platform/msm_sdcc\.1/by-name/fsg u:object_r:modem_block_device:s0 -/dev/block/platform/msm_sdcc\.1/by-name/modemst1 u:object_r:modem_block_device:s0 -/dev/block/platform/msm_sdcc\.1/by-name/modemst2 u:object_r:modem_block_device:s0 -/dev/block/platform/msm_sdcc\.1/by-name/persist u:object_r:persist_block_device:s0 -/dev/block/platform/msm_sdcc\.1/by-name/recovery u:object_r:recovery_block_device:s0 -/dev/block/platform/msm_sdcc\.1/by-name/ssd u:object_r:ssd_block_device:s0 -/dev/block/platform/msm_sdcc\.1/by-name/system u:object_r:system_block_device:s0 -/dev/block/platform/msm_sdcc\.1/by-name/userdata u:object_r:userdata_block_device:s0 -/dev/block/mmcblk0 u:object_r:root_block_device:s0 - -################################### -# Persist files -# -/persist(/.*)? u:object_r:persist_file:s0 ################################### # System files # -/system/bin/ATFWD-daemon u:object_r:atfwd_exec:s0 -/system/bin/btnvtool u:object_r:btnvtool_exec:s0 -/system/bin/init\.class_main\.sh u:object_r:qcom-c_main-sh_exec:s0 -/system/bin/init\.qcom\.sh u:object_r:qcom-sh_exec:s0 -/system/bin/init\.qcom\.bt\.sh u:object_r:config_bluetooth_exec:s0 -/system/bin/init\.qcom\.class_core\.sh u:object_r:qcom-c_core-sh_exec:s0 -/system/bin/init\.qcom\.modem_links\.sh u:object_r:modem-sh_exec:s0 -/system/bin/init\.qcom\.post_boot\.sh u:object_r:qcom-post-boot_exec:s0 -/system/bin/init\.qcom\.usb\.sh u:object_r:qcom-usb-sh_exec:s0 -/system/bin/init\.qti\.can\.sh u:object_r:can_exec:s0 -/system/bin/usf_post_boot\.sh u:object_r:usf-post-boot_exec:s0 -/system/bin/irsc_util u:object_r:irsc_util_exec:s0 -/system/bin/mm-pp-daemon u:object_r:mm-pp-daemon_exec:s0 -/system/bin/mpdecision u:object_r:mpdecision_exec:s0 -/system/bin/netmgrd u:object_r:netmgrd_exec:s0 -/system/bin/qmuxd u:object_r:qmuxd_exec:s0 -/system/bin/rmt_storage u:object_r:rmt_storage_exec:s0 -/system/bin/thermal-engine u:object_r:thermal-engine_exec:s0 -/system/bin/time_daemon u:object_r:time_daemon_exec:s0 /system/bin/vehicle_network_service u:object_r:vns_exec:s0 -/system/bin/wcnss_filter u:object_r:start_hci_filter_exec:s0 -/system/bin/wcnss_service u:object_r:wcnss_service_exec:s0 -/system/bin/wpa_cli u:object_r:wcnss_service_exec:s0 ################################### -# sysfs files -# -/sys/class/thermal(/.*)? u:object_r:sysfs_thermal:s0 -/sys/class/android_usb(/.*)? u:object_r:sysfs_usb:s0 -/sys/devices/virtual/thermal(/.*)? u:object_r:sysfs_thermal:s0 -/sys/module/msm_dcvs(/.*)? u:object_r:sysfs_dcvs:s0 -/sys/module/msm_mpdecision(/.*)? u:object_r:sysfs_mpdecision:s0 -/sys/module/msm_thermal(/.*)? u:object_r:sysfs_thermal:s0 -/sys/module/pm_8x60(/.*)? u:object_r:sysfs_devices_system_cpu:s0 -/sys/module/rmnet_usb(/.*)? u:object_r:sysfs_usb:s0 -/sys/module/rpm_resources(/.*)? u:object_r:sysfs_rpm_resources:s0 -/sys/module/spm_v2(/.*)? u:object_r:sysfs_thermal:s0 -/sys/devices/platform/bt_power(/.*)? u:object_r:sysfs_bt_power:s0 -/sys/devices/platform/msm_hsusb\.[1-4](/.*)? u:object_r:sysfs_usb:s0 -/sys/devices/virtual/android_usb(/.*)? u:object_r:sysfs_usb:s0 -/sys/devices/virtual/hsicctl/hsicctl[0-9]+/modem_wait u:object_r:sysfs_hsic_modem_wait:s0 -/sys/devices/virtual/smdpkt/smdcntl[0-9]+/open_timeout u:object_r:sysfs_smd_open_timeout:s0 + + diff --git a/car_product/sepolicy/init.te b/car_product/sepolicy/init.te index c909543071..a80ab0e917 100644 --- a/car_product/sepolicy/init.te +++ b/car_product/sepolicy/init.te @@ -1,4 +1,2 @@ # Allow legacy sdcard for creating directory symlinks allow init tmpfs:lnk_file create_file_perms; - -r_dir_file(init, sysfs_usb); diff --git a/car_product/sepolicy/irsc_util.te b/car_product/sepolicy/irsc_util.te deleted file mode 100644 index 148134eea7..0000000000 --- a/car_product/sepolicy/irsc_util.te +++ /dev/null @@ -1,6 +0,0 @@ -type irsc_util, domain; -type irsc_util_exec, exec_type, file_type; - -init_daemon_domain(irsc_util) - -allow irsc_util self:socket create_socket_perms; diff --git a/car_product/sepolicy/mm-pp-daemon.te b/car_product/sepolicy/mm-pp-daemon.te deleted file mode 100644 index c463509151..0000000000 --- a/car_product/sepolicy/mm-pp-daemon.te +++ /dev/null @@ -1,13 +0,0 @@ -type mm-pp-daemon, domain; -type mm-pp-daemon_exec, exec_type, file_type; - -init_daemon_domain(mm-pp-daemon) - -# Need to use fb ioctls to communicate with kernel -allow mm-pp-daemon graphics_device:chr_file rw_file_perms; - -# Allow socket calls in mm-pp-daemon -allow mm-pp-daemon pps_socket:sock_file rw_file_perms; - -# Set hw.cabl.* properties. -set_prop(mm-pp-daemon, hw_cabl_prop) diff --git a/car_product/sepolicy/modem-sh.te b/car_product/sepolicy/modem-sh.te deleted file mode 100644 index 0a06dc9570..0000000000 --- a/car_product/sepolicy/modem-sh.te +++ /dev/null @@ -1,8 +0,0 @@ -# modem-sh service -type modem-sh, domain; -type modem-sh_exec, exec_type, file_type; - -# Started by init -init_daemon_domain(modem-sh) - -allow modem-sh shell_exec:file r_file_perms; diff --git a/car_product/sepolicy/mpdecision.te b/car_product/sepolicy/mpdecision.te deleted file mode 100644 index 432f4ee034..0000000000 --- a/car_product/sepolicy/mpdecision.te +++ /dev/null @@ -1,36 +0,0 @@ -# MpDecision service -type mpdecision, domain; -type mpdecision_exec, exec_type, file_type; - -init_daemon_domain(mpdecision) - -allow mpdecision self:capability { net_admin fsetid }; - -allow mpdecision self:netlink_kobject_uevent_socket create_socket_perms; - -# Access to /dev/cpu_dma_latency. -allow mpdecision latency_device:chr_file w_file_perms; - -# Create and access to /dev/socket/mpdecision -allow mpdecision mpdecision_socket:sock_file rw_file_perms; - -# Access to /sys/devices/system/cpu/*. -allow mpdecision sysfs_devices_system_cpu:file rw_file_perms; - -# Access to sysfs_thermal nodes. -allow mpdecision sysfs_thermal:dir r_dir_perms; -allow mpdecision sysfs_thermal:file r_file_perms; - -# Access to mpctl data files and sockets. -allow mpdecision perfd_data_file:dir w_dir_perms; -allow mpdecision perfd_data_file:file create_file_perms; -allow mpdecision perfd_data_file:sock_file create_file_perms; - -# Access to some dynamically generated files under /sys/devices/system/cpu/. -allow mpdecision sysfs:file write; - -allow mpdecision self:capability dac_override; - -allow mpdecision sysfs:file r_file_perms; - -allow mpdecision proc:file rw_file_perms; diff --git a/car_product/sepolicy/netmgrd.te b/car_product/sepolicy/netmgrd.te deleted file mode 100644 index d87f6905e2..0000000000 --- a/car_product/sepolicy/netmgrd.te +++ /dev/null @@ -1,28 +0,0 @@ -type netmgrd, domain; -type netmgrd_exec, exec_type, file_type; - -init_daemon_domain(netmgrd) -net_domain(netmgrd) - -# Creates/Talks to qmuxd via the qmux_radio socket. -qmux_socket(netmgrd) - -# Allow writing of ipv6 network properties -allow netmgrd proc_net:file w_file_perms; - -# Allow netmgrd operations -allow netmgrd self:capability { net_admin net_raw fsetid }; - -# Allow execution of /system/bin/sh. -allow netmgrd shell_exec:file rx_file_perms; - -# Allow execution of /system/bin/*. -allow netmgrd system_file:file rx_file_perms; - -allow netmgrd toolbox_exec:file rx_file_perms; - -userdebug_or_eng(` - allow netmgrd diag_device:chr_file rw_file_perms; -') - -dontaudit netmgrd self:capability sys_module; diff --git a/car_product/sepolicy/priv_app.te b/car_product/sepolicy/priv_app.te new file mode 100644 index 0000000000..5ecd537775 --- /dev/null +++ b/car_product/sepolicy/priv_app.te @@ -0,0 +1 @@ +get_prop(priv_app, opengles_prop) diff --git a/car_product/sepolicy/property.te b/car_product/sepolicy/property.te index 6282856d0c..0f4e53faf6 100644 --- a/car_product/sepolicy/property.te +++ b/car_product/sepolicy/property.te @@ -1,10 +1,6 @@ -type ctl_mpdecision_prop, property_type; -type ctl_netmgrd_prop, property_type; -type ctl_qmuxd_prop, property_type; -type ctl_quipc_igsn_prop, property_type; -type ctl_quipc_main_prop, property_type; -type ctl_thermal-engine_prop, property_type; type hw_cabl_prop, property_type; -type wc_transport_prop, property_type; type wlan_driver_prop, property_type; -type radio_atfwd_prop, property_type; + +type opengles_prop, property_type; + +type car_prop, property_type; diff --git a/car_product/sepolicy/property_contexts b/car_product/sepolicy/property_contexts index 4afc2ac473..67b90f6764 100644 --- a/car_product/sepolicy/property_contexts +++ b/car_product/sepolicy/property_contexts @@ -1,11 +1,5 @@ -ctl.mpdecision u:object_r:ctl_mpdecision_prop:s0 -ctl.netmgrd u:object_r:ctl_netmgrd_prop:s0 -ctl.qmuxd u:object_r:ctl_qmuxd_prop:s0 -ctl.quipc_igsn u:object_r:ctl_quipc_igsn_prop:s0 -ctl.quipc_main u:object_r:ctl_quipc_main_prop:s0 -ctl.thermal-engine u:object_r:ctl_thermal-engine_prop:s0 hw.cabl. u:object_r:hw_cabl_prop:s0 -qualcomm.bluetooth. u:object_r:bluetooth_prop:s0 -radio.atfwd. u:object_r:radio_atfwd_prop:s0 -wc_transport. u:object_r:wc_transport_prop:s0 wlan.driver. u:object_r:wlan_driver_prop:s0 + +boot.car_service_created u:object_r:car_prop:s0 +ro.opengles. u:object_r:opengles_prop:s0
\ No newline at end of file diff --git a/car_product/sepolicy/qcom-c_core-sh.te b/car_product/sepolicy/qcom-c_core-sh.te deleted file mode 100644 index 689282010b..0000000000 --- a/car_product/sepolicy/qcom-c_core-sh.te +++ /dev/null @@ -1,15 +0,0 @@ -# qcom-c_core-sh service -type qcom-c_core-sh, domain; -type qcom-c_core-sh_exec, exec_type, file_type; - -# Started by init -init_daemon_domain(qcom-c_core-sh) - -# Set ctl.console properties. -set_prop(qcom-c_core-sh, ctl_console_prop) - -allow qcom-c_core-sh shell_exec:file r_file_perms; - -allow qcom-c_core-sh toolbox_exec:file rx_file_perms; - -allow qcom-c_core-sh sysfs:file r_file_perms; diff --git a/car_product/sepolicy/qcom-c_main-sh.te b/car_product/sepolicy/qcom-c_main-sh.te deleted file mode 100644 index c8a1eed493..0000000000 --- a/car_product/sepolicy/qcom-c_main-sh.te +++ /dev/null @@ -1,16 +0,0 @@ -# qcom-c_main-sh service -type qcom-c_main-sh, domain; -type qcom-c_main-sh_exec, exec_type, file_type; - -# Started by init -init_daemon_domain(qcom-c_main-sh) - -# Set ctl.qmuxd property. -set_prop(qcom-c_main-sh, ctl_qmuxd_prop) - -# Set ctl.netmgrd property. -set_prop(qcom-c_main-sh, ctl_netmgrd_prop) - -allow qcom-c_main-sh shell_exec:file r_file_perms; - -allow qcom-c_main-sh toolbox_exec:file rx_file_perms; diff --git a/car_product/sepolicy/qcom-post-boot.te b/car_product/sepolicy/qcom-post-boot.te deleted file mode 100644 index 3d54b843aa..0000000000 --- a/car_product/sepolicy/qcom-post-boot.te +++ /dev/null @@ -1,50 +0,0 @@ -# qcom-post-boot service -type qcom-post-boot, domain; -type qcom-post-boot_exec, exec_type, file_type; - -# Started by init -init_daemon_domain(qcom-post-boot) - -# Set ctl.thermal-engine property. -set_prop(qcom-post-boot, ctl_thermal-engine_prop); - -# Set ctl.mpdecision property. -set_prop(qcom-post-boot, ctl_mpdecision_prop); - -# Allow access to /dev/ttyHS0. -allow qcom-post-boot serial_device:chr_file { getattr setattr }; - -allow qcom-post-boot shell_exec:file r_file_perms; - -# Write access to thermal related sysfs nodes. -allow qcom-post-boot sysfs_thermal:dir search; -allow qcom-post-boot sysfs_thermal:file w_file_perms; - -# Access to /sys/module/rpm_resources/*. -allow qcom-post-boot sysfs_rpm_resources:dir search; -allow qcom-post-boot sysfs_rpm_resources:file w_file_perms; - -# Write access to mpdecision related sysfs nodes. -allow qcom-post-boot sysfs_mpdecision:dir search; -allow qcom-post-boot sysfs_mpdecision:file { rw_file_perms setattr }; - -# Access to /sys/module/msm_dcvs/*. -allow qcom-post-boot sysfs_dcvs:dir search; -allow qcom-post-boot sysfs_dcvs:file { rw_file_perms setattr }; - -# Chown /sys/devices/platform/bt_power/*. -allow qcom-post-boot sysfs_bt_power:dir search; -allow qcom-post-boot sysfs_bt_power:file { getattr setattr }; - -# Write access to /sys/devices/system/cpu/*. -allow qcom-post-boot sysfs_devices_system_cpu:file { w_file_perms setattr }; - -# Write access to dynamically generated files under /sys/devices/system/cpufreq/ondemand/*. -allow qcom-post-boot sysfs:file { w_file_perms setattr }; - -# Allow changing the owner of the above sysfs nodes. -allow qcom-post-boot self:capability { fowner chown fsetid }; - -allow qcom-post-boot sysfs:file r_file_perms; - -allow qcom-post-boot toolbox_exec:file rx_file_perms; diff --git a/car_product/sepolicy/qcom-sh.te b/car_product/sepolicy/qcom-sh.te deleted file mode 100644 index 1ae6c4fb3b..0000000000 --- a/car_product/sepolicy/qcom-sh.te +++ /dev/null @@ -1,21 +0,0 @@ -# qcom-sh service -type qcom-sh, domain; -type qcom-sh_exec, exec_type, file_type; - -# Started by init -init_daemon_domain(qcom-sh) - -# Set ctl.quipc_* property. -set_prop(qcom-sh, ctl_quipc_igsn_prop) -set_prop(qcom-sh, ctl_quipc_main_prop) - -allow qcom-sh self:capability net_admin; - -# Allow writing of ipv6 network properties -allow qcom-sh proc_net:file w_file_perms; - -allow qcom-sh shell_exec:file r_file_perms; - -allow qcom-sh toolbox_exec:file rx_file_perms; - -allow qcom-sh sysfs:file r_file_perms; diff --git a/car_product/sepolicy/qcom-usb-sh.te b/car_product/sepolicy/qcom-usb-sh.te deleted file mode 100644 index 8d637821b5..0000000000 --- a/car_product/sepolicy/qcom-usb-sh.te +++ /dev/null @@ -1,29 +0,0 @@ -# qcom-usb-sh service -type qcom-usb-sh, domain; -type qcom-usb-sh_exec, exec_type, file_type; - -# Started by init -init_daemon_domain(qcom-usb-sh) - -# Allow changing the owner of /sys/devices/virtual/hsicctl/hsicctl0/modem_wait. -allow qcom-usb-sh sysfs_hsic_modem_wait:file { getattr setattr }; - -# qcom.usb.sh needs to restore the context of /sys/devices/virtual/hsicctl/hsicctl0/modem_wait -# as it may not be properly labeled when accessed. -allow qcom-usb-sh sysfs:file relabelfrom; -allow qcom-usb-sh sysfs_hsic_modem_wait:file relabelto; - -# Follow links from /sys/class/android_usb/* to /sys/devices/virtual/android_usb/*. -allow qcom-usb-sh sysfs_usb:lnk_file read; - -# Allow write access to USB related sysfs nodes. -allow qcom-usb-sh sysfs_usb:dir search; -allow qcom-usb-sh sysfs_usb:file w_file_perms; - -allow qcom-usb-sh self:capability chown; - -allow qcom-usb-sh shell_exec:file r_file_perms; - -allow qcom-usb-sh toolbox_exec:file rx_file_perms; - -allow qcom-usb-sh rootfs:file r_file_perms; diff --git a/car_product/sepolicy/qmuxd.te b/car_product/sepolicy/qmuxd.te deleted file mode 100644 index e0b6dabd0e..0000000000 --- a/car_product/sepolicy/qmuxd.te +++ /dev/null @@ -1,22 +0,0 @@ -# qmuxd daemon -type qmuxd, domain; -type qmuxd_exec, exec_type, file_type; - -init_daemon_domain(qmuxd) -net_domain(qmuxd) - -# Allow access to /dev/hsicctl*. -allow qmuxd hsic_device:chr_file rw_file_perms; - -# Allow access to /sys/devices/virtual/smdpkt/smdcntl*/open_timeout. -allow qmuxd sysfs_smd_open_timeout:file w_file_perms; - -# Allow access to /sys/devices/virtual/hsicctl/hsicctl*/modem_wait. -allow qmuxd sysfs_hsic_modem_wait:file w_file_perms; - -userdebug_or_eng(` - allow qmuxd diag_device:chr_file rw_file_perms; -') - -# Allow qmuxd to have the CAP_BLOCK_SUSPEND capability -wakelock_use(qmuxd) diff --git a/car_product/sepolicy/rild.te b/car_product/sepolicy/rild.te deleted file mode 100644 index ea450be2ef..0000000000 --- a/car_product/sepolicy/rild.te +++ /dev/null @@ -1,6 +0,0 @@ -# Creates/Talks to qmuxd via the qmux_radio socket. -qmux_socket(rild); - -userdebug_or_eng(` - allow rild diag_device:chr_file rw_file_perms; -') diff --git a/car_product/sepolicy/rmt_storage.te b/car_product/sepolicy/rmt_storage.te deleted file mode 100644 index d67a2cee3e..0000000000 --- a/car_product/sepolicy/rmt_storage.te +++ /dev/null @@ -1,32 +0,0 @@ -# rmt_storage daemon -type rmt_storage, domain; -type rmt_storage_exec, exec_type, file_type; - -init_daemon_domain(rmt_storage) - -# Drop (user, group) to (nobody, nobody) -allow rmt_storage self:capability { setuid setgid }; - -# Opens and reads /dev/block/mmcblk0. -allow rmt_storage root_block_device:blk_file r_file_perms; - -# Allow access to /dev/uio0. -allow rmt_storage uio_device:chr_file rw_file_perms; - -# Allow access to /dev/smem_log. -allow rmt_storage smem_log_device:chr_file rw_file_perms; - -# Allow access to modem related block devices. -allow rmt_storage modem_block_device:blk_file rw_file_perms; - -# Allow access to SSD related block devices. -allow rmt_storage ssd_block_device:blk_file rw_file_perms; - -allow rmt_storage self:socket create_socket_perms; - -allow rmt_storage sysfs:file r_file_perms; - -allow rmt_storage sysfs:dir r_dir_perms; - -# Wake lock access. -wakelock_use(rmt_storage) diff --git a/car_product/sepolicy/service.te b/car_product/sepolicy/service.te new file mode 100644 index 0000000000..b92ed0aa81 --- /dev/null +++ b/car_product/sepolicy/service.te @@ -0,0 +1 @@ +type car_service, service_manager_type; diff --git a/car_product/sepolicy/service_contexts b/car_product/sepolicy/service_contexts new file mode 100644 index 0000000000..63aff466c0 --- /dev/null +++ b/car_product/sepolicy/service_contexts @@ -0,0 +1 @@ +com.android.car.vehiclenetwork.IVehicleNetwork u:object_r:car_service:s0 diff --git a/car_product/sepolicy/start_hci_filter.te b/car_product/sepolicy/start_hci_filter.te deleted file mode 100644 index 95ce9e406d..0000000000 --- a/car_product/sepolicy/start_hci_filter.te +++ /dev/null @@ -1,10 +0,0 @@ -type start_hci_filter, domain; -type start_hci_filter_exec, exec_type, file_type; - -init_daemon_domain(start_hci_filter); - -# Allow access to /dev/ttyHS0 -allow start_hci_filter serial_device:chr_file rw_file_perms; - -# Allow access to wc_transport.* properties. -set_prop(start_hci_filter, wc_transport_prop) diff --git a/car_product/sepolicy/system_app.te b/car_product/sepolicy/system_app.te index aaa99c23f4..5dffcf2569 100644 --- a/car_product/sepolicy/system_app.te +++ b/car_product/sepolicy/system_app.te @@ -1 +1,3 @@ binder_call(system_app, vns); + +set_prop(system_app, car_prop) diff --git a/car_product/sepolicy/system_server.te b/car_product/sepolicy/system_server.te index 507f8ca8d0..4e0da641b3 100644 --- a/car_product/sepolicy/system_server.te +++ b/car_product/sepolicy/system_server.te @@ -1,17 +1,6 @@ # Set wlan.driver.* properties. set_prop(system_server, wlan_driver_prop) -# Creates/Talks to qmuxd via the qmux_radio socket. -qmux_socket(system_server) - -# For gss -allow system_server gss_device:chr_file rw_file_perms; - -userdebug_or_eng(` - allow system_server diag_device:chr_file rw_file_perms; -') - -r_dir_file(system_server, sysfs_usb) -allow system_server sysfs_usb:file w_file_perms; +get_prop(system_server opengles_prop) dontaudit system_server self:capability sys_module; diff --git a/car_product/sepolicy/te_macros b/car_product/sepolicy/te_macros deleted file mode 100644 index 485bfb7d93..0000000000 --- a/car_product/sepolicy/te_macros +++ /dev/null @@ -1,11 +0,0 @@ -##################################### -# qmux_socket(clientdomain) -# Allow client domain to connecto and send -# via a local socket to the qmux domain. -# Also allow the client domain to remove -# its own socket. -define(`qmux_socket', ` -allow $1 qmuxd_socket:dir create_dir_perms; -unix_socket_connect($1, qmuxd, qmuxd) -allow $1 qmuxd_socket:sock_file { read getattr write setattr create unlink }; -') diff --git a/car_product/sepolicy/thermal-engine.te b/car_product/sepolicy/thermal-engine.te deleted file mode 100644 index aa9224fc2b..0000000000 --- a/car_product/sepolicy/thermal-engine.te +++ /dev/null @@ -1,38 +0,0 @@ -# Thermal-engine daemon -type thermal-engine, domain; -type thermal-engine_exec, exec_type, file_type; - -init_daemon_domain(thermal-engine) - -userdebug_or_eng(` - allow thermal-engine diag_device:chr_file rw_file_perms; -') - -allow thermal-engine self:capability { net_admin fsetid }; - -allow thermal-engine self:netlink_kobject_uevent_socket create_socket_perms; - -# Allow access to /dev/smem_log. -allow thermal-engine smem_log_device:chr_file rw_file_perms; - -# Access to /dev/socket/thermal-.* -allow thermal-engine thermal_socket:sock_file rw_file_perms; - -# Access to /dev/socket/mpdecision. -unix_socket_connect(thermal-engine, mpdecision, mpdecision); - -# Allow access to /dev/uio0. -#allow rmt_storage uio_device:chr_file rw_file_perms; - -# Write access to thermal related sysfs nodes. -r_dir_file(thermal-engine, sysfs_thermal) -allow thermal-engine sysfs_thermal:file w_file_perms; - -# Creates/Talks to qmuxd via the qmux_radio socket. -qmux_socket(thermal-engine); - -allow thermal-engine self:socket create_socket_perms; - -allow thermal-engine sysfs_thermal:file r_file_perms; - -allow thermal-engine sysfs_thermal:dir r_dir_perms; diff --git a/car_product/sepolicy/time_daemon.te b/car_product/sepolicy/time_daemon.te deleted file mode 100644 index 782da98f0f..0000000000 --- a/car_product/sepolicy/time_daemon.te +++ /dev/null @@ -1,19 +0,0 @@ -# Policies for time daemon -type time_daemon, domain; -type time_daemon_exec, exec_type, file_type; - -init_daemon_domain(time_daemon) - -# Allow access to /dev/smem_log. -allow time_daemon smem_log_device:chr_file rw_file_perms; - -# Add rules for access permissions -allow time_daemon rtc_device:chr_file r_file_perms; -allow time_daemon alarm_device:chr_file rw_file_perms; - -# Allo access to /data/time/*. -allow time_daemon time_data_file:file r_file_perms; - -allow time_daemon self:socket create_socket_perms; - -allow time_daemon self:capability { setuid setgid }; diff --git a/car_product/sepolicy/ueventd.te b/car_product/sepolicy/ueventd.te deleted file mode 100644 index f898e27641..0000000000 --- a/car_product/sepolicy/ueventd.te +++ /dev/null @@ -1,11 +0,0 @@ -# Allow read access to firmware related files. -r_dir_file(ueventd, firmware_file); - -# Write access to thermal related sysfs nodes. -allow ueventd sysfs_thermal:file w_file_perms; - -# Allow write access to usb related sysfs nodes. -allow ueventd sysfs_usb:file w_file_perms; - -# Allow write access to bt_power sysfs nodes. -allow ueventd sysfs_bt_power:file w_file_perms; diff --git a/car_product/sepolicy/usf-post-boot.te b/car_product/sepolicy/usf-post-boot.te deleted file mode 100644 index e21f583965..0000000000 --- a/car_product/sepolicy/usf-post-boot.te +++ /dev/null @@ -1,8 +0,0 @@ -# usf-post-boot service -type usf-post-boot, domain; -type usf-post-boot_exec, exec_type, file_type; - -# Started by init -init_daemon_domain(usf-post-boot) - -allow usf-post-boot shell_exec:file r_file_perms; diff --git a/car_product/sepolicy/vehicle_network_service.te b/car_product/sepolicy/vehicle_network_service.te new file mode 100644 index 0000000000..cef863a3f7 --- /dev/null +++ b/car_product/sepolicy/vehicle_network_service.te @@ -0,0 +1,4 @@ +type vehicle_network_service_exec, exec_type, file_type; +type vehicle_network_service, domain; + +init_daemon_domain(vehicle_network_service) diff --git a/car_product/sepolicy/vns.te b/car_product/sepolicy/vns.te index ff5f1f8d62..6d663f8b03 100644 --- a/car_product/sepolicy/vns.te +++ b/car_product/sepolicy/vns.te @@ -2,6 +2,10 @@ type vns, domain; type vns_exec, exec_type, file_type; +allow vns system_app:binder { call }; +allow vns car_service:service_manager { add }; +allow vns priv_app:binder { call }; + init_daemon_domain(vns) binder_use(vns); diff --git a/car_product/sepolicy/wcnss_service.te b/car_product/sepolicy/wcnss_service.te deleted file mode 100644 index fb97c43f9a..0000000000 --- a/car_product/sepolicy/wcnss_service.te +++ /dev/null @@ -1,17 +0,0 @@ -# WCNSS service -type wcnss_service, domain; -type wcnss_service_exec, exec_type, file_type; - -init_daemon_domain(wcnss_service) -net_domain(wcnss_service) - -unix_socket_connect(wcnss_service, property, init) - -# Allow creation and modification of wifi data files. -allow wcnss_service wifi_data_file:file create_file_perms; - -# Allow modifications of /dev/wcnss_* devices. -allow wcnss_service wcnss_device:chr_file rw_file_perms; - -# Set wlan.driver.* properties. -set_prop(wcnss_service, wlan_driver_prop) diff --git a/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetwork.java b/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetwork.java index f7ef2c4449..a54282a1da 100644 --- a/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetwork.java +++ b/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetwork.java @@ -216,6 +216,12 @@ public class VehicleNetwork { setProperty(v); } + public void setStringProperty(int property, String value) + throws IllegalArgumentException, ServiceSpecificException { + VehiclePropValue v = VehiclePropValueUtil.createStringValue(property, value, 0); + setProperty(v); + } + /** * Set zoned boolean type property * diff --git a/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java b/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java index cd7531e677..4cbbeeaf9d 100644 --- a/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java +++ b/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java @@ -50,6 +50,7 @@ public static final int VEHICLE_PROPERTY_HVAC_MAX_DEFROST_ON = 0x00000507; public static final int VEHICLE_PROPERTY_HVAC_RECIRC_ON = 0x00000508; public static final int VEHICLE_PROPERTY_HVAC_DUAL_ON = 0x00000509; public static final int VEHICLE_PROPERTY_HVAC_AUTO_ON = 0x0000050A; +public static final int VEHICLE_PROPERTY_HVAC_SEAT_TEMPERATURE = 0x0000050B; public static final int VEHICLE_PROPERTY_HVAC_POWER_ON = 0x00000510; public static final int VEHICLE_PROPERTY_ENV_OUTSIDE_TEMPERATURE = 0x00000703; public static final int VEHICLE_PROPERTY_ENV_CABIN_TEMPERATURE = 0x00000704; @@ -59,6 +60,7 @@ public static final int VEHICLE_PROPERTY_AUDIO_VOLUME = 0x00000901; public static final int VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT = 0x00000902; public static final int VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY = 0x00000903; public static final int VEHICLE_PROPERTY_AUDIO_HW_VARIANT = 0x00000904; +public static final int VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT = 0x00000905; public static final int VEHICLE_PROPERTY_AP_POWER_STATE = 0x00000A00; public static final int VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS = 0x00000A01; public static final int VEHICLE_PROPERTY_AP_POWER_BOOTUP_REASON = 0x00000A02; @@ -66,9 +68,48 @@ public static final int VEHICLE_PROPERTY_HW_KEY_INPUT = 0x00000A10; public static final int VEHICLE_PROPERTY_INSTRUMENT_CLUSTER_INFO = 0x00000A20; public static final int VEHICLE_PROPERTY_UNIX_TIME = 0x00000A30; public static final int VEHICLE_PROPERTY_CURRENT_TIME_IN_SECONDS = 0x00000A31; -public static final int VEHICLE_PROPERTY_SEAT_TEMPERATURE = 0x00000B00; -public static final int VEHICLE_PROPERTY_SEAT_MEMORY_SELECT = 0x00000B01; -public static final int VEHICLE_PROPERTY_SEAT_MEMORY_SET = 0x00000B02; +public static final int VEHICLE_PROPERTY_DOOR_POS = 0x00000B00; +public static final int VEHICLE_PROPERTY_DOOR_MOVE = 0x00000B01; +public static final int VEHICLE_PROPERTY_DOOR_LOCK = 0x00000B02; +public static final int VEHICLE_PROPERTY_MIRROR_Z_POS = 0x00000B40; +public static final int VEHICLE_PROPERTY_MIRROR_Z_MOVE = 0x00000B41; +public static final int VEHICLE_PROPERTY_MIRROR_Y_POS = 0x00000B42; +public static final int VEHICLE_PROPERTY_MIRROR_Y_MOVE = 0x00000B43; +public static final int VEHICLE_PROPERTY_MIRROR_LOCK = 0x00000B44; +public static final int VEHICLE_PROPERTY_MIRROR_HEAT = 0x00000B45; +public static final int VEHICLE_PROPERTY_MIRROR_FOLD = 0x00000B46; +public static final int VEHICLE_PROPERTY_SEAT_MEMORY_SELECT = 0x00000B80; +public static final int VEHICLE_PROPERTY_SEAT_MEMORY_SET = 0x00000B81; +public static final int VEHICLE_PROPERTY_SEAT_BELT_BUCKLED = 0x00000B82; +public static final int VEHICLE_PROPERTY_SEAT_BELT_HEIGHT_POS = 0x00000B83; +public static final int VEHICLE_PROPERTY_SEAT_BELT_HEIGHT_MOVE = 0x00000B84; +public static final int VEHICLE_PROPERTY_SEAT_FORE_AFT_POS = 0x00000B85; +public static final int VEHICLE_PROPERTY_SEAT_FORE_AFT_MOVE = 0x00000B86; +public static final int VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_1_POS = 0x00000B87; +public static final int VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_1_MOVE = 0x00000B88; +public static final int VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_2_POS = 0x00000B89; +public static final int VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_2_MOVE = 0x00000B8A; +public static final int VEHICLE_PROPERTY_SEAT_HEIGHT_POS = 0x00000B8B; +public static final int VEHICLE_PROPERTY_SEAT_HEIGHT_MOVE = 0x00000B8C; +public static final int VEHICLE_PROPERTY_SEAT_DEPTH_POS = 0x00000B8D; +public static final int VEHICLE_PROPERTY_SEAT_DEPTH_MOVE = 0x00000B8E; +public static final int VEHICLE_PROPERTY_SEAT_TILT_POS = 0x00000B8F; +public static final int VEHICLE_PROPERTY_SEAT_TILT_MOVE = 0x00000B90; +public static final int VEHICLE_PROPERTY_SEAT_LUMBAR_FORE_AFT_POS = 0x00000B91; +public static final int VEHICLE_PROPERTY_SEAT_LUMBAR_FORE_AFT_MOVE = 0x00000B92; +public static final int VEHICLE_PROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_POS = 0x00000B93; +public static final int VEHICLE_PROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_MOVE = 0x00000B94; +public static final int VEHICLE_PROPERTY_SEAT_HEADREST_HEIGHT_POS = 0x00000B95; +public static final int VEHICLE_PROPERTY_SEAT_HEADREST_HEIGHT_MOVE = 0x00000B96; +public static final int VEHICLE_PROPERTY_SEAT_HEADREST_ANGLE_POS = 0x00000B97; +public static final int VEHICLE_PROPERTY_SEAT_HEADREST_ANGLE_MOVE = 0x00000B98; +public static final int VEHICLE_PROPERTY_SEAT_HEADREST_FORE_AFT_POS = 0x00000B99; +public static final int VEHICLE_PROPERTY_SEAT_HEADREST_FORE_AFT_MOVE = 0x00000B9A; +public static final int VEHICLE_PROPERTY_WINDOW_POS = 0x00000BC0; +public static final int VEHICLE_PROPERTY_WINDOW_MOVE = 0x00000BC1; +public static final int VEHICLE_PROPERTY_WINDOW_VENT_POS = 0x00000BC2; +public static final int VEHICLE_PROPERTY_WINDOW_VENT_MOVE = 0x00000BC3; +public static final int VEHICLE_PROPERTY_WINDOW_LOCK = 0x00000BC4; public static final int VEHICLE_PROPERTY_CUSTOM_START = 0x70000000; public static final int VEHICLE_PROPERTY_CUSTOM_END = 0x73ffffff; public static final int VEHICLE_PROPERTY_INTERNAL_START = 0x74000000; @@ -103,6 +144,7 @@ case VEHICLE_PROPERTY_HVAC_MAX_DEFROST_ON: return VehicleValueType.VEHICLE_VALUE case VEHICLE_PROPERTY_HVAC_RECIRC_ON: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_BOOLEAN; case VEHICLE_PROPERTY_HVAC_DUAL_ON: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_BOOLEAN; case VEHICLE_PROPERTY_HVAC_AUTO_ON: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_BOOLEAN; +case VEHICLE_PROPERTY_HVAC_SEAT_TEMPERATURE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; case VEHICLE_PROPERTY_HVAC_POWER_ON: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_BOOLEAN; case VEHICLE_PROPERTY_ENV_OUTSIDE_TEMPERATURE: return VehicleValueType.VEHICLE_VALUE_TYPE_FLOAT; case VEHICLE_PROPERTY_ENV_CABIN_TEMPERATURE: return VehicleValueType.VEHICLE_VALUE_TYPE_FLOAT; @@ -112,6 +154,7 @@ case VEHICLE_PROPERTY_AUDIO_VOLUME: return VehicleValueType.VEHICLE_VALUE_TYPE_I case VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2; case VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2; case VEHICLE_PROPERTY_AUDIO_HW_VARIANT: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32; +case VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC4; case VEHICLE_PROPERTY_AP_POWER_STATE: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2; case VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32; case VEHICLE_PROPERTY_AP_POWER_BOOTUP_REASON: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32; @@ -119,9 +162,48 @@ case VEHICLE_PROPERTY_HW_KEY_INPUT: return VehicleValueType.VEHICLE_VALUE_TYPE_I case VEHICLE_PROPERTY_INSTRUMENT_CLUSTER_INFO: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC4; case VEHICLE_PROPERTY_UNIX_TIME: return VehicleValueType.VEHICLE_VALUE_TYPE_INT64; case VEHICLE_PROPERTY_CURRENT_TIME_IN_SECONDS: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32; -case VEHICLE_PROPERTY_SEAT_TEMPERATURE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_DOOR_POS: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_DOOR_MOVE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_DOOR_LOCK: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_BOOLEAN; +case VEHICLE_PROPERTY_MIRROR_Z_POS: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_MIRROR_Z_MOVE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_MIRROR_Y_POS: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_MIRROR_Y_MOVE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_MIRROR_LOCK: return VehicleValueType.VEHICLE_VALUE_TYPE_BOOLEAN; +case VEHICLE_PROPERTY_MIRROR_HEAT: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32; +case VEHICLE_PROPERTY_MIRROR_FOLD: return VehicleValueType.VEHICLE_VALUE_TYPE_BOOLEAN; case VEHICLE_PROPERTY_SEAT_MEMORY_SELECT: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; case VEHICLE_PROPERTY_SEAT_MEMORY_SET: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_BELT_BUCKLED: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_BOOLEAN; +case VEHICLE_PROPERTY_SEAT_BELT_HEIGHT_POS: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_BELT_HEIGHT_MOVE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_FORE_AFT_POS: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_FORE_AFT_MOVE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_1_POS: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_1_MOVE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_2_POS: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_2_MOVE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_HEIGHT_POS: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_HEIGHT_MOVE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_DEPTH_POS: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_DEPTH_MOVE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_TILT_POS: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_TILT_MOVE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_LUMBAR_FORE_AFT_POS: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_LUMBAR_FORE_AFT_MOVE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_POS: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_MOVE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_HEADREST_HEIGHT_POS: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_HEADREST_HEIGHT_MOVE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_HEADREST_ANGLE_POS: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_HEADREST_ANGLE_MOVE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_HEADREST_FORE_AFT_POS: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_SEAT_HEADREST_FORE_AFT_MOVE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_WINDOW_POS: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_WINDOW_MOVE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_WINDOW_VENT_POS: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_WINDOW_VENT_MOVE: return VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32; +case VEHICLE_PROPERTY_WINDOW_LOCK: return VehicleValueType.VEHICLE_VALUE_TYPE_BOOLEAN; case VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: return VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2; default: return VehicleValueType.VEHICLE_VALUE_TYPE_SHOUD_NOT_USE; } @@ -156,6 +238,7 @@ case VEHICLE_PROPERTY_HVAC_MAX_DEFROST_ON: return "VEHICLE_PROPERTY_HVAC_MAX_DEF case VEHICLE_PROPERTY_HVAC_RECIRC_ON: return "VEHICLE_PROPERTY_HVAC_RECIRC_ON"; case VEHICLE_PROPERTY_HVAC_DUAL_ON: return "VEHICLE_PROPERTY_HVAC_DUAL_ON"; case VEHICLE_PROPERTY_HVAC_AUTO_ON: return "VEHICLE_PROPERTY_HVAC_AUTO_ON"; +case VEHICLE_PROPERTY_HVAC_SEAT_TEMPERATURE: return "VEHICLE_PROPERTY_HVAC_SEAT_TEMPERATURE"; case VEHICLE_PROPERTY_HVAC_POWER_ON: return "VEHICLE_PROPERTY_HVAC_POWER_ON"; case VEHICLE_PROPERTY_ENV_OUTSIDE_TEMPERATURE: return "VEHICLE_PROPERTY_ENV_OUTSIDE_TEMPERATURE"; case VEHICLE_PROPERTY_ENV_CABIN_TEMPERATURE: return "VEHICLE_PROPERTY_ENV_CABIN_TEMPERATURE"; @@ -165,6 +248,7 @@ case VEHICLE_PROPERTY_AUDIO_VOLUME: return "VEHICLE_PROPERTY_AUDIO_VOLUME"; case VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT: return "VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT"; case VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY: return "VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY"; case VEHICLE_PROPERTY_AUDIO_HW_VARIANT: return "VEHICLE_PROPERTY_AUDIO_HW_VARIANT"; +case VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT: return "VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT"; case VEHICLE_PROPERTY_AP_POWER_STATE: return "VEHICLE_PROPERTY_AP_POWER_STATE"; case VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS: return "VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS"; case VEHICLE_PROPERTY_AP_POWER_BOOTUP_REASON: return "VEHICLE_PROPERTY_AP_POWER_BOOTUP_REASON"; @@ -172,9 +256,48 @@ case VEHICLE_PROPERTY_HW_KEY_INPUT: return "VEHICLE_PROPERTY_HW_KEY_INPUT"; case VEHICLE_PROPERTY_INSTRUMENT_CLUSTER_INFO: return "VEHICLE_PROPERTY_INSTRUMENT_CLUSTER_INFO"; case VEHICLE_PROPERTY_UNIX_TIME: return "VEHICLE_PROPERTY_UNIX_TIME"; case VEHICLE_PROPERTY_CURRENT_TIME_IN_SECONDS: return "VEHICLE_PROPERTY_CURRENT_TIME_IN_SECONDS"; -case VEHICLE_PROPERTY_SEAT_TEMPERATURE: return "VEHICLE_PROPERTY_SEAT_TEMPERATURE"; +case VEHICLE_PROPERTY_DOOR_POS: return "VEHICLE_PROPERTY_DOOR_POS"; +case VEHICLE_PROPERTY_DOOR_MOVE: return "VEHICLE_PROPERTY_DOOR_MOVE"; +case VEHICLE_PROPERTY_DOOR_LOCK: return "VEHICLE_PROPERTY_DOOR_LOCK"; +case VEHICLE_PROPERTY_MIRROR_Z_POS: return "VEHICLE_PROPERTY_MIRROR_Z_POS"; +case VEHICLE_PROPERTY_MIRROR_Z_MOVE: return "VEHICLE_PROPERTY_MIRROR_Z_MOVE"; +case VEHICLE_PROPERTY_MIRROR_Y_POS: return "VEHICLE_PROPERTY_MIRROR_Y_POS"; +case VEHICLE_PROPERTY_MIRROR_Y_MOVE: return "VEHICLE_PROPERTY_MIRROR_Y_MOVE"; +case VEHICLE_PROPERTY_MIRROR_LOCK: return "VEHICLE_PROPERTY_MIRROR_LOCK"; +case VEHICLE_PROPERTY_MIRROR_HEAT: return "VEHICLE_PROPERTY_MIRROR_HEAT"; +case VEHICLE_PROPERTY_MIRROR_FOLD: return "VEHICLE_PROPERTY_MIRROR_FOLD"; case VEHICLE_PROPERTY_SEAT_MEMORY_SELECT: return "VEHICLE_PROPERTY_SEAT_MEMORY_SELECT"; case VEHICLE_PROPERTY_SEAT_MEMORY_SET: return "VEHICLE_PROPERTY_SEAT_MEMORY_SET"; +case VEHICLE_PROPERTY_SEAT_BELT_BUCKLED: return "VEHICLE_PROPERTY_SEAT_BELT_BUCKLED"; +case VEHICLE_PROPERTY_SEAT_BELT_HEIGHT_POS: return "VEHICLE_PROPERTY_SEAT_BELT_HEIGHT_POS"; +case VEHICLE_PROPERTY_SEAT_BELT_HEIGHT_MOVE: return "VEHICLE_PROPERTY_SEAT_BELT_HEIGHT_MOVE"; +case VEHICLE_PROPERTY_SEAT_FORE_AFT_POS: return "VEHICLE_PROPERTY_SEAT_FORE_AFT_POS"; +case VEHICLE_PROPERTY_SEAT_FORE_AFT_MOVE: return "VEHICLE_PROPERTY_SEAT_FORE_AFT_MOVE"; +case VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_1_POS: return "VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_1_POS"; +case VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_1_MOVE: return "VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_1_MOVE"; +case VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_2_POS: return "VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_2_POS"; +case VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_2_MOVE: return "VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_2_MOVE"; +case VEHICLE_PROPERTY_SEAT_HEIGHT_POS: return "VEHICLE_PROPERTY_SEAT_HEIGHT_POS"; +case VEHICLE_PROPERTY_SEAT_HEIGHT_MOVE: return "VEHICLE_PROPERTY_SEAT_HEIGHT_MOVE"; +case VEHICLE_PROPERTY_SEAT_DEPTH_POS: return "VEHICLE_PROPERTY_SEAT_DEPTH_POS"; +case VEHICLE_PROPERTY_SEAT_DEPTH_MOVE: return "VEHICLE_PROPERTY_SEAT_DEPTH_MOVE"; +case VEHICLE_PROPERTY_SEAT_TILT_POS: return "VEHICLE_PROPERTY_SEAT_TILT_POS"; +case VEHICLE_PROPERTY_SEAT_TILT_MOVE: return "VEHICLE_PROPERTY_SEAT_TILT_MOVE"; +case VEHICLE_PROPERTY_SEAT_LUMBAR_FORE_AFT_POS: return "VEHICLE_PROPERTY_SEAT_LUMBAR_FORE_AFT_POS"; +case VEHICLE_PROPERTY_SEAT_LUMBAR_FORE_AFT_MOVE: return "VEHICLE_PROPERTY_SEAT_LUMBAR_FORE_AFT_MOVE"; +case VEHICLE_PROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_POS: return "VEHICLE_PROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_POS"; +case VEHICLE_PROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_MOVE: return "VEHICLE_PROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_MOVE"; +case VEHICLE_PROPERTY_SEAT_HEADREST_HEIGHT_POS: return "VEHICLE_PROPERTY_SEAT_HEADREST_HEIGHT_POS"; +case VEHICLE_PROPERTY_SEAT_HEADREST_HEIGHT_MOVE: return "VEHICLE_PROPERTY_SEAT_HEADREST_HEIGHT_MOVE"; +case VEHICLE_PROPERTY_SEAT_HEADREST_ANGLE_POS: return "VEHICLE_PROPERTY_SEAT_HEADREST_ANGLE_POS"; +case VEHICLE_PROPERTY_SEAT_HEADREST_ANGLE_MOVE: return "VEHICLE_PROPERTY_SEAT_HEADREST_ANGLE_MOVE"; +case VEHICLE_PROPERTY_SEAT_HEADREST_FORE_AFT_POS: return "VEHICLE_PROPERTY_SEAT_HEADREST_FORE_AFT_POS"; +case VEHICLE_PROPERTY_SEAT_HEADREST_FORE_AFT_MOVE: return "VEHICLE_PROPERTY_SEAT_HEADREST_FORE_AFT_MOVE"; +case VEHICLE_PROPERTY_WINDOW_POS: return "VEHICLE_PROPERTY_WINDOW_POS"; +case VEHICLE_PROPERTY_WINDOW_MOVE: return "VEHICLE_PROPERTY_WINDOW_MOVE"; +case VEHICLE_PROPERTY_WINDOW_VENT_POS: return "VEHICLE_PROPERTY_WINDOW_VENT_POS"; +case VEHICLE_PROPERTY_WINDOW_VENT_MOVE: return "VEHICLE_PROPERTY_WINDOW_VENT_MOVE"; +case VEHICLE_PROPERTY_WINDOW_LOCK: return "VEHICLE_PROPERTY_WINDOW_LOCK"; case VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: return "VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE"; default: return "UNKNOWN_PROPERTY"; } @@ -209,6 +332,7 @@ case VEHICLE_PROPERTY_HVAC_MAX_DEFROST_ON: return new int[] { VehiclePropChangeM case VEHICLE_PROPERTY_HVAC_RECIRC_ON: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; case VEHICLE_PROPERTY_HVAC_DUAL_ON: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; case VEHICLE_PROPERTY_HVAC_AUTO_ON: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_HVAC_SEAT_TEMPERATURE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; case VEHICLE_PROPERTY_HVAC_POWER_ON: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; case VEHICLE_PROPERTY_ENV_OUTSIDE_TEMPERATURE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE , VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_CONTINUOUS }; case VEHICLE_PROPERTY_ENV_CABIN_TEMPERATURE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE , VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_CONTINUOUS }; @@ -218,6 +342,7 @@ case VEHICLE_PROPERTY_AUDIO_VOLUME: return new int[] { VehiclePropChangeMode.VEH case VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; case VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; case VEHICLE_PROPERTY_AUDIO_HW_VARIANT: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_STATIC }; +case VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; case VEHICLE_PROPERTY_AP_POWER_STATE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; case VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; case VEHICLE_PROPERTY_AP_POWER_BOOTUP_REASON: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_STATIC }; @@ -225,9 +350,48 @@ case VEHICLE_PROPERTY_HW_KEY_INPUT: return new int[] { VehiclePropChangeMode.VEH case VEHICLE_PROPERTY_INSTRUMENT_CLUSTER_INFO: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; case VEHICLE_PROPERTY_UNIX_TIME: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_SET }; case VEHICLE_PROPERTY_CURRENT_TIME_IN_SECONDS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_SET }; -case VEHICLE_PROPERTY_SEAT_TEMPERATURE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_DOOR_POS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_DOOR_MOVE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_DOOR_LOCK: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_MIRROR_Z_POS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_MIRROR_Z_MOVE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_MIRROR_Y_POS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_MIRROR_Y_MOVE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_MIRROR_LOCK: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_MIRROR_HEAT: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_MIRROR_FOLD: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; case VEHICLE_PROPERTY_SEAT_MEMORY_SELECT: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; case VEHICLE_PROPERTY_SEAT_MEMORY_SET: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_BELT_BUCKLED: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_BELT_HEIGHT_POS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_BELT_HEIGHT_MOVE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_FORE_AFT_POS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_FORE_AFT_MOVE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_1_POS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_1_MOVE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_2_POS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_2_MOVE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_HEIGHT_POS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_HEIGHT_MOVE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_DEPTH_POS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_DEPTH_MOVE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_TILT_POS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_TILT_MOVE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_LUMBAR_FORE_AFT_POS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_LUMBAR_FORE_AFT_MOVE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_POS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_MOVE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_HEADREST_HEIGHT_POS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_HEADREST_HEIGHT_MOVE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_HEADREST_ANGLE_POS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_HEADREST_ANGLE_MOVE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_HEADREST_FORE_AFT_POS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_SEAT_HEADREST_FORE_AFT_MOVE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_WINDOW_POS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_WINDOW_MOVE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_WINDOW_VENT_POS: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_WINDOW_VENT_MOVE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; +case VEHICLE_PROPERTY_WINDOW_LOCK: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; case VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: return new int[] { VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE }; default: return null; } @@ -262,6 +426,7 @@ case VEHICLE_PROPERTY_HVAC_MAX_DEFROST_ON: return new int[] { VehiclePropAccess. case VEHICLE_PROPERTY_HVAC_RECIRC_ON: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; case VEHICLE_PROPERTY_HVAC_DUAL_ON: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; case VEHICLE_PROPERTY_HVAC_AUTO_ON: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_HVAC_SEAT_TEMPERATURE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; case VEHICLE_PROPERTY_HVAC_POWER_ON: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; case VEHICLE_PROPERTY_ENV_OUTSIDE_TEMPERATURE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ }; case VEHICLE_PROPERTY_ENV_CABIN_TEMPERATURE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ }; @@ -271,6 +436,7 @@ case VEHICLE_PROPERTY_AUDIO_VOLUME: return new int[] { VehiclePropAccess.VEHICLE case VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; case VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; case VEHICLE_PROPERTY_AUDIO_HW_VARIANT: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ }; +case VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; case VEHICLE_PROPERTY_AP_POWER_STATE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; case VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ , VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; case VEHICLE_PROPERTY_AP_POWER_BOOTUP_REASON: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ }; @@ -278,14 +444,70 @@ case VEHICLE_PROPERTY_HW_KEY_INPUT: return new int[] { VehiclePropAccess.VEHICLE case VEHICLE_PROPERTY_INSTRUMENT_CLUSTER_INFO: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; case VEHICLE_PROPERTY_UNIX_TIME: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; case VEHICLE_PROPERTY_CURRENT_TIME_IN_SECONDS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; -case VEHICLE_PROPERTY_SEAT_TEMPERATURE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_DOOR_POS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_DOOR_MOVE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_DOOR_LOCK: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_MIRROR_Z_POS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_MIRROR_Z_MOVE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_MIRROR_Y_POS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_MIRROR_Y_MOVE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_MIRROR_LOCK: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_MIRROR_HEAT: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_MIRROR_FOLD: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; case VEHICLE_PROPERTY_SEAT_MEMORY_SELECT: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; case VEHICLE_PROPERTY_SEAT_MEMORY_SET: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_SEAT_BELT_BUCKLED: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_SEAT_BELT_HEIGHT_POS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_SEAT_BELT_HEIGHT_MOVE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_SEAT_FORE_AFT_POS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_SEAT_FORE_AFT_MOVE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_1_POS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_1_MOVE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_2_POS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_2_MOVE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_SEAT_HEIGHT_POS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_SEAT_HEIGHT_MOVE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_SEAT_DEPTH_POS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_SEAT_DEPTH_MOVE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_SEAT_TILT_POS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_SEAT_TILT_MOVE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_SEAT_LUMBAR_FORE_AFT_POS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_SEAT_LUMBAR_FORE_AFT_MOVE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_POS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_MOVE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_SEAT_HEADREST_HEIGHT_POS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_SEAT_HEADREST_HEIGHT_MOVE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_SEAT_HEADREST_ANGLE_POS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_SEAT_HEADREST_ANGLE_MOVE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_SEAT_HEADREST_FORE_AFT_POS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_SEAT_HEADREST_FORE_AFT_MOVE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_WINDOW_POS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_WINDOW_MOVE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_WINDOW_VENT_POS: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; +case VEHICLE_PROPERTY_WINDOW_VENT_MOVE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE , VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE }; +case VEHICLE_PROPERTY_WINDOW_LOCK: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; case VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: return new int[] { VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE }; default: return null; } } + + + +public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_RADIO_AM_FM = "RADIO_AM_FM"; +public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_RADIO_AM_FM_HD = "RADIO_AM_FM_HD"; +public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_RADIO_DAB = "RADIO_DAB"; +public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_RADIO_SATELLITE = "RADIO_SATELLITE"; +public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_CD_DVD = "CD_DVD"; +public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_AUX_IN0 = "AUX_IN0"; +public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_AUX_IN1 = "AUX_IN1"; +public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_EXT_NAV_GUIDANCE = "EXT_NAV_GUIDANCE"; +public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_EXT_VOICE_COMMAND = "EXT_VOICE_COMMAND"; +public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_EXT_VOICE_CALL = "EXT_VOICE_CALL"; +public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_EXT_SAFETY_ALERT = "EXT_SAFETY_ALERT"; + + + public static class VehicleHvacFanDirection { public static final int VEHICLE_HVAC_FAN_DIRECTION_FACE = 0x1; public static final int VEHICLE_HVAC_FAN_DIRECTION_FLOOR = 0x2; @@ -425,6 +647,7 @@ public static final int VEHICLE_AUDIO_CONTEXT_CD_ROM_FLAG = 0x100; public static final int VEHICLE_AUDIO_CONTEXT_AUX_AUDIO_FLAG = 0x200; public static final int VEHICLE_AUDIO_CONTEXT_SYSTEM_SOUND_FLAG = 0x400; public static final int VEHICLE_AUDIO_CONTEXT_RADIO_FLAG = 0x800; +public static final int VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG = 0x1000; public static String enumToString(int v) { switch(v) { case VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG: return "VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG"; @@ -439,6 +662,7 @@ case VEHICLE_AUDIO_CONTEXT_CD_ROM_FLAG: return "VEHICLE_AUDIO_CONTEXT_CD_ROM_FLA case VEHICLE_AUDIO_CONTEXT_AUX_AUDIO_FLAG: return "VEHICLE_AUDIO_CONTEXT_AUX_AUDIO_FLAG"; case VEHICLE_AUDIO_CONTEXT_SYSTEM_SOUND_FLAG: return "VEHICLE_AUDIO_CONTEXT_SYSTEM_SOUND_FLAG"; case VEHICLE_AUDIO_CONTEXT_RADIO_FLAG: return "VEHICLE_AUDIO_CONTEXT_RADIO_FLAG"; +case VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG: return "VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG"; default: return "UNKNOWN"; } } @@ -446,9 +670,11 @@ default: return "UNKNOWN"; public static class VehicleAudioVolumeCapabilityFlag { public static final int VEHICLE_AUDIO_VOLUME_CAPABILITY_PERSISTENT_STORAGE = 0x1; +public static final int VEHICLE_AUDIO_VOLUME_CAPABILITY_MASTER_VOLUME_ONLY = 0x2; public static String enumToString(int v) { switch(v) { case VEHICLE_AUDIO_VOLUME_CAPABILITY_PERSISTENT_STORAGE: return "VEHICLE_AUDIO_VOLUME_CAPABILITY_PERSISTENT_STORAGE"; +case VEHICLE_AUDIO_VOLUME_CAPABILITY_MASTER_VOLUME_ONLY: return "VEHICLE_AUDIO_VOLUME_CAPABILITY_MASTER_VOLUME_ONLY"; default: return "UNKNOWN"; } } diff --git a/libvehiclenetwork/native/VehicleNetwork.cpp b/libvehiclenetwork/native/VehicleNetwork.cpp index 8b28bc7a59..5e57962556 100644 --- a/libvehiclenetwork/native/VehicleNetwork.cpp +++ b/libvehiclenetwork/native/VehicleNetwork.cpp @@ -15,6 +15,7 @@ */ #define LOG_TAG "VehicleNetwork.Lib" +#include <assert.h> #include <binder/IServiceManager.h> #include <binder/ProcessState.h> #include <utils/threads.h> @@ -130,18 +131,29 @@ void VehicleNetworkEventMessageHandler::handleMessage(const Message& message) { // ---------------------------------------------------------------------------- +static const int MAX_SERVICE_RETRY = 4; + sp<VehicleNetwork> VehicleNetwork::createVehicleNetwork(sp<VehicleNetworkListener>& listener) { - sp<IBinder> binder = defaultServiceManager()->getService( - String16(IVehicleNetwork::SERVICE_NAME)); - sp<VehicleNetwork> vn; - if (binder != NULL) { - sp<IVehicleNetwork> ivn(interface_cast<IVehicleNetwork>(binder)); - vn = new VehicleNetwork(ivn, listener); - if (vn != NULL) { - // in case thread pool is not started, start it. - ProcessState::self()->startThreadPool(); + sp<IBinder> binder; + int retry = 0; + while (true) { + binder = defaultServiceManager()->getService(String16(IVehicleNetwork::SERVICE_NAME)); + if (binder.get() != NULL) { + break; + } + retry++; + if (retry > MAX_SERVICE_RETRY) { + ALOGE("cannot get VNS, will crash"); + break; } } + ASSERT_ALWAYS_ON_NO_MEMORY(binder.get()); + sp<IVehicleNetwork> ivn(interface_cast<IVehicleNetwork>(binder)); + sp<VehicleNetwork> vn; + vn = new VehicleNetwork(ivn, listener); + ASSERT_ALWAYS_ON_NO_MEMORY(vn.get()); + // in case thread pool is not started, start it. + ProcessState::self()->startThreadPool(); return vn; } diff --git a/libvehiclenetwork/tool/vehiclehal_code_gen.py b/libvehiclenetwork/tool/vehiclehal_code_gen.py index e35654fe44..dfabd7dccd 100755 --- a/libvehiclenetwork/tool/vehiclehal_code_gen.py +++ b/libvehiclenetwork/tool/vehiclehal_code_gen.py @@ -50,9 +50,10 @@ JAVA_TRAIL = \ } """ -RE_PROPERTY_PATTERN = r'/\*\*(.*?)\*/\n\#define\s+VEHICLE_PROPERTY_(\S+)\s+(\S+)' +RE_PROPERTY_PATTERN = r'/\*\*(.*?)\*/\n\#define\s+VEHICLE_PROPERTY_(\S+)\s+(\(0x\S+\))' RE_ENUM_PATTERN = r'enum\s+(\S+)\s+\{\S*(.*?)\}' RE_ENUM_ENTRY_PATTERN = r'(\S+)\s*=\s*(.*?)[,\n]' +RE_AUDIO_EXT_ROUTING_PATTERN = r'\n\#define\s+VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_(\S+)\s+\"(\S+)\"' class PropertyInfo(object): def __init__(self, value, name): @@ -188,6 +189,10 @@ def printEnums(enums): for e in enums: printEnum(e) +def printExtAudioRoutingSources(audio_ext_routing): + for r in audio_ext_routing: + print "public static final String VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_SOURCE_" + r + ' = "' + r + '";' + def main(argv): vehicle_h_path = os.path.dirname(os.path.abspath(__file__)) + "/../../../../../hardware/libhardware/include/hardware/vehicle.h" #print vehicle_h_path @@ -245,10 +250,26 @@ def main(argv): enums.append(info) #for e in enums: # print e + + audio_ext_routing = [] + audio_ext_routing_re = re.compile(RE_AUDIO_EXT_ROUTING_PATTERN, re.MULTILINE | re.DOTALL) + for match in audio_ext_routing_re.finditer(text): + #print match + name = match.group(1) + value = match.group(2) + if name != value: + print "Warning, AUDIO_EXT_ROUTING_SOURCE_" + name + " does not match " + value + else: + audio_ext_routing.append(name) + print JAVA_HEADER printProperties(props) + print "\n\n" + printExtAudioRoutingSources(audio_ext_routing) + print "\n\n" printEnums(enums) print JAVA_TRAIL + if __name__ == '__main__': main(sys.argv) diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml index 9bed93f08d..9168239f6b 100644 --- a/service/AndroidManifest.xml +++ b/service/AndroidManifest.xml @@ -94,24 +94,32 @@ android:label="@string/car_permission_label_audio_volume" android:description="@string/car_permission_desc_audio_volume" /> - <uses-permission android:name="android.permission.WRITE_SETTINGS" /> - <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + <permission + android:name="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE" + android:protectionLevel="signature" + android:label="@string/car_permission_label_bind_instrument_cluster_rendering" + android:description="@string/car_permission_desc_bind_instrument_cluster_rendering"/> + + <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.DEVICE_POWER" /> - <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> - <uses-permission android:name="android.permission.REBOOT" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" /> <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE" /> - <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> - <uses-permission android:name="android.permission.CALL_PHONE" /> - <uses-permission android:name="android.permission.READ_CALL_LOG" /> <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> + <uses-permission android:name="android.permission.READ_CALL_LOG" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> + <uses-permission android:name="android.permission.REAL_GET_TASKS" /> + <uses-permission android:name="android.permission.REBOOT" /> + <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> + <uses-permission android:name="android.permission.REMOVE_TASKS" /> + <uses-permission android:name="android.permission.WRITE_SETTINGS" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <application android:label="Car service" android:directBootAware="true" android:allowBackup="false" - android:persistent="true" - android:process="android.car.service"> + android:persistent="true"> <service android:name=".CarService" android:singleUser="true"> @@ -125,5 +133,12 @@ <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter> </receiver> + <activity android:name="com.android.car.pm.ActivityBlockingActivity" + android:excludeFromRecents="true" + android:exported="false"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + </intent-filter> + </activity> </application> </manifest> diff --git a/service/res/layout/activity_blocking.xml b/service/res/layout/activity_blocking.xml new file mode 100644 index 0000000000..14dcce36ae --- /dev/null +++ b/service/res/layout/activity_blocking.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + <TextView + android:id="@+id/activity_blocked_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/activity_blocked_string" /> + <Button + android:id="@+id/botton_exit_now" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/exit_now" /> +</LinearLayout> diff --git a/service/res/values/config.xml b/service/res/values/config.xml index 1c80579f9b..92aae54945 100644 --- a/service/res/values/config.xml +++ b/service/res/values/config.xml @@ -60,6 +60,18 @@ from vehicle hal --> <string name="inputInjectionDeviceNode">/dev/input/event2</string> - <string name="instrumentClusterRendererPackage">android.car.cluster.loggingrenderer</string> - <string name="instrumentClusterRendererFactoryClass">android.car.cluster.loggingrenderer.InstrumentClusterRendererFactory</string> + <string name="instrumentClusterRendererService">android.car.cluster.loggingrenderer/.LoggingClusterRenderingService</string> + + <!-- Whether to enable Avtivity blocking for safety. When Activity blocking is enabled, + only whitelisted safe Activities will be allowed while car is not parked. --> + <bool name="enableActivityBlockingForSafety">true</bool> + <!-- Activity to be presented when un-safe activity is launched. Take a look at the javadoc of the + default implementation. --> + <string name="activityBlockingActivity">com.android.car/com.android.car.pm.ActivityBlockingActivity</string> + <!-- Comma separated list of activities that will be allowed by default. This only applies to + system apps which is included into system image and non-system app in the list will be + ignored. Format of each entry is either to specify package name to whitelist the whole + package or use format of "packagename/activity_classname" for tagging each activities. + Besides this, system apps with car app meta data will be auto whitelisted. --> + <string name="defauiltActivityWhitelist">com.android.systemui</string> </resources> diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml index 3721fb3498..f97efc4369 100644 --- a/service/res/values/strings.xml +++ b/service/res/values/strings.xml @@ -69,6 +69,8 @@ cluster [CHAR LIMIT=NONE] --> <string name="car_permission_desc_car_navigation_manager">Report navigation data to instrument cluster</string> + <string name="car_permission_label_bind_instrument_cluster_rendering">Instrument Cluster Rendering</string> + <string name="car_permission_desc_bind_instrument_cluster_rendering">Receive instrument cluster data</string> <!-- Notification messages --> <!-- Notification text: Notification shown to the user when vehicle CAN bus fails --> @@ -76,4 +78,9 @@ <!-- Notification text: Notification description shown to the user when vehicle CAN bus fails --> <string name="car_can_bus_failure_desc">CAN bus does not respond. Unplug and plug back headunit box and restart the car</string> + + <!-- Blocking activity: Message to show to user when an application is not allowed. [CHAR LIMIT=NONE] --> + <string name="activity_blocked_string">The application is not allowed while driving.</string> + <!-- Blocking activity: Message on exit button. [CHAR LIMIT=NONE] --> + <string name="exit_now">Exit</string> </resources> diff --git a/service/src/com/android/car/AppContextService.java b/service/src/com/android/car/AppContextService.java deleted file mode 100644 index edc10bd026..0000000000 --- a/service/src/com/android/car/AppContextService.java +++ /dev/null @@ -1,342 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.car; - -import android.car.CarAppContextManager; -import android.car.IAppContext; -import android.car.IAppContextListener; -import android.content.Context; -import android.os.Binder; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; -import android.util.Log; - -import com.android.car.hal.VehicleHal; - -import java.io.PrintWriter; -import java.util.HashMap; - -public class AppContextService extends IAppContext.Stub implements CarServiceBase, - BinderInterfaceContainer.BinderEventHandler<IAppContextListener> { - private static final boolean DBG = true; - private static final boolean DBG_EVENT = false; - - private final ClientHolder mAllClients; - /** K: context flag, V: client owning it */ - private final HashMap<Integer, ClientInfo> mContextOwners = new HashMap<>(); - private int mActiveContexts; - - private final HandlerThread mHandlerThread; - private final DispatchHandler mDispatchHandler; - - public AppContextService(Context context) { - mAllClients = new ClientHolder(this); - mHandlerThread = new HandlerThread(AppContextService.class.getSimpleName()); - mHandlerThread.start(); - mDispatchHandler = new DispatchHandler(mHandlerThread.getLooper()); - } - - @Override - public void registerContextListener(IAppContextListener listener, int filter) { - synchronized (this) { - ClientInfo info = (ClientInfo) mAllClients.getBinderInterface(listener); - if (info == null) { - info = new ClientInfo(mAllClients, listener, Binder.getCallingUid(), - Binder.getCallingPid(), filter); - mAllClients.addBinderInterface(info); - } else { - info.setFilter(filter); - } - } - } - - @Override - public void unregisterContextListener(IAppContextListener listener) { - synchronized (this) { - ClientInfo info = (ClientInfo) mAllClients.getBinderInterface(listener); - if (info == null) { - return; - } - resetActiveContexts(listener, info.getOwnedContexts()); - mAllClients.removeBinder(listener); - } - } - - @Override - public int getActiveAppContexts() { - synchronized (this) { - return mActiveContexts; - } - } - - @Override - public boolean isOwningContext(IAppContextListener listener, int context) { - synchronized (this) { - ClientInfo info = (ClientInfo) mAllClients.getBinderInterface(listener); - if (info == null) { - return false; - } - int ownedContexts = info.getOwnedContexts(); - return (ownedContexts & context) == context; - } - } - - @Override - public void setActiveContexts(IAppContextListener listener, int contexts) { - synchronized (this) { - ClientInfo info = (ClientInfo) mAllClients.getBinderInterface(listener); - if (info == null) { - throw new IllegalStateException("listener not registered"); - } - int alreadyOwnedContexts = info.getOwnedContexts(); - int addedContexts = 0; - for (int c = CarAppContextManager.APP_CONTEXT_START_FLAG; - c <= CarAppContextManager.APP_CONTEXT_END_FLAG; c = (c << 1)) { - if ((c & contexts) != 0 && (c & alreadyOwnedContexts) == 0) { - ClientInfo ownerInfo = mContextOwners.get(c); - if (ownerInfo != null && ownerInfo != info) { - //TODO check if current owner is having fore-ground activity. If yes, - //reject request. Always grant if requestor is fore-ground activity. - ownerInfo.setOwnedContexts(ownerInfo.getOwnedContexts() & ~c); - mDispatchHandler.requestAppContextOwnershipLossDispatch( - ownerInfo.binderInterface, c); - if (DBG) { - Log.i(CarLog.TAG_APP_CONTEXT, "losing context " + - Integer.toHexString(c) + "," + ownerInfo.toString()); - } - } else { - addedContexts |= c; - } - mContextOwners.put(c, info); - } - } - info.setOwnedContexts(alreadyOwnedContexts | contexts); - mActiveContexts |= addedContexts; - if (addedContexts != 0) { - if (DBG) { - Log.i(CarLog.TAG_APP_CONTEXT, "setting context " + - Integer.toHexString(addedContexts) + "," + info.toString()); - } - for (BinderInterfaceContainer.BinderInterface<IAppContextListener> client : - mAllClients.getInterfaces()) { - ClientInfo clientInfo = (ClientInfo) client; - // dispatch events only when there is change after filter and the listener - // is not coming from the current caller. - int clientFilter = clientInfo.getFilter(); - if ((addedContexts & clientFilter) != 0 && clientInfo != info) { - mDispatchHandler.requestAppContextChangeDispatch(clientInfo.binderInterface, - mActiveContexts & clientFilter); - } - } - } - } - } - - @Override - public void resetActiveContexts(IAppContextListener listener, int contexts) { - synchronized (this) { - ClientInfo info = (ClientInfo) mAllClients.getBinderInterface(listener); - if (info == null) { - // ignore as this client cannot have owned anything. - return; - } - if ((contexts & mActiveContexts) == 0) { - // ignore as none of them are active; - return; - } - int removedContexts = 0; - int currentlyOwnedContexts = info.getOwnedContexts(); - for (int c = CarAppContextManager.APP_CONTEXT_START_FLAG; - c <= CarAppContextManager.APP_CONTEXT_END_FLAG; c = (c << 1)) { - if ((c & contexts) != 0 && (c & currentlyOwnedContexts) != 0) { - removedContexts |= c; - mContextOwners.remove(c); - } - } - if (removedContexts != 0) { - mActiveContexts &= ~removedContexts; - info.setOwnedContexts(currentlyOwnedContexts & ~removedContexts); - if (DBG) { - Log.i(CarLog.TAG_APP_CONTEXT, "resetting context " + - Integer.toHexString(removedContexts) + "," + info.toString()); - } - for (BinderInterfaceContainer.BinderInterface<IAppContextListener> client : - mAllClients.getInterfaces()) { - ClientInfo clientInfo = (ClientInfo) client; - int clientFilter = clientInfo.getFilter(); - if ((removedContexts & clientFilter) != 0 && clientInfo != info) { - mDispatchHandler.requestAppContextChangeDispatch(clientInfo.binderInterface, - mActiveContexts & clientFilter); - } - } - } - } - } - - @Override - public void init() { - // nothing to do - } - - @Override - public void release() { - synchronized (this) { - mAllClients.clear(); - mContextOwners.clear(); - mActiveContexts = 0; - } - } - - @Override - public void onBinderDeath( - BinderInterfaceContainer.BinderInterface<IAppContextListener> bInterface) { - ClientInfo info = (ClientInfo) bInterface; - int ownedContexts = info.getOwnedContexts(); - if (ownedContexts != 0) { - resetActiveContexts(bInterface.binderInterface, ownedContexts); - } - } - - @Override - public void dump(PrintWriter writer) { - writer.println("**AppContextService**"); - synchronized (this) { - writer.println("mActiveContexts:" + Integer.toHexString(mActiveContexts)); - for (BinderInterfaceContainer.BinderInterface<IAppContextListener> client : - mAllClients.getInterfaces()) { - ClientInfo clientInfo = (ClientInfo) client; - writer.println(clientInfo.toString()); - } - } - } - - /** - * Returns true if process with given uid and pid owns provided context. - */ - public boolean isContextOwner(int uid, int pid, int context) { - synchronized (this) { - if (mContextOwners.containsKey(context)) { - ClientInfo clientInfo = mContextOwners.get(context); - return clientInfo.uid == uid && clientInfo.pid == pid; - } - } - return false; - } - - private void dispatchAppContextOwnershipLoss(IAppContextListener listener, int contexts) { - try { - listener.onAppContextOwnershipLoss(contexts); - } catch (RemoteException e) { - } - } - - private void dispatchAppContextChange(IAppContextListener listener, int contexts) { - try { - listener.onAppContextChange(contexts); - } catch (RemoteException e) { - } - } - - private static class ClientHolder extends BinderInterfaceContainer<IAppContextListener> { - private ClientHolder(AppContextService service) { - super(service); - } - } - - private static class ClientInfo extends - BinderInterfaceContainer.BinderInterface<IAppContextListener> { - private final int uid; - private final int pid; - private int mFilter; - /** contexts owned by this client */ - private int mOwnedContexts; - - private ClientInfo(ClientHolder holder, IAppContextListener binder, int uid, int pid, - int filter) { - super(holder, binder); - this.uid = uid; - this.pid = pid; - this.mFilter = filter; - } - - private synchronized int getFilter() { - return mFilter; - } - - private synchronized void setFilter(int filter) { - mFilter = filter; - } - - private synchronized int getOwnedContexts() { - if (DBG_EVENT) { - Log.i(CarLog.TAG_APP_CONTEXT, "getOwnedContexts " + - Integer.toHexString(mOwnedContexts)); - } - return mOwnedContexts; - } - - private synchronized void setOwnedContexts(int contexts) { - if (DBG_EVENT) { - Log.i(CarLog.TAG_APP_CONTEXT, "setOwnedContexts " + Integer.toHexString(contexts)); - } - mOwnedContexts = contexts; - } - - @Override - public String toString() { - synchronized (this) { - return "ClientInfo{uid=" + uid + ",pid=" + pid + - ",filter=" + Integer.toHexString(mFilter) + - ",owned=" + Integer.toHexString(mOwnedContexts) + "}"; - } - } - } - - private class DispatchHandler extends Handler { - private static final int MSG_DISPATCH_OWNERSHIP_LOSS = 0; - private static final int MSG_DISPATCH_CONTEXT_CHANGE = 1; - - private DispatchHandler(Looper looper) { - super(looper); - } - - private void requestAppContextOwnershipLossDispatch(IAppContextListener listener, - int contexts) { - Message msg = obtainMessage(MSG_DISPATCH_OWNERSHIP_LOSS, contexts, 0, listener); - sendMessage(msg); - } - - private void requestAppContextChangeDispatch(IAppContextListener listener, int contexts) { - Message msg = obtainMessage(MSG_DISPATCH_CONTEXT_CHANGE, contexts, 0, listener); - sendMessage(msg); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_DISPATCH_OWNERSHIP_LOSS: - dispatchAppContextOwnershipLoss((IAppContextListener) msg.obj, msg.arg1); - break; - case MSG_DISPATCH_CONTEXT_CHANGE: - dispatchAppContextChange((IAppContextListener) msg.obj, msg.arg1); - break; - } - } - } -} diff --git a/service/src/com/android/car/AppFocusService.java b/service/src/com/android/car/AppFocusService.java new file mode 100644 index 0000000000..ad463ba9ff --- /dev/null +++ b/service/src/com/android/car/AppFocusService.java @@ -0,0 +1,452 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.car; + +import android.car.CarAppFocusManager; +import android.car.IAppFocus; +import android.car.IAppFocusListener; +import android.car.IAppFocusOwnershipListener; +import android.content.Context; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; + +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * App focus service ensures only one instance of application type is active at a time. + */ +public class AppFocusService extends IAppFocus.Stub implements CarServiceBase, + BinderInterfaceContainer.BinderEventHandler<IAppFocusOwnershipListener> { + private static final boolean DBG = true; + private static final boolean DBG_EVENT = false; + + private final ClientHolder mAllChangeClients; + private final OwnershipClientHolder mAllOwnershipClients; + /** K: appType, V: client owning it */ + private final HashMap<Integer, OwnershipClientInfo> mFocusOwners = new HashMap<>(); + private final Set<Integer> mActiveAppTypes = new HashSet<>(); + private final HandlerThread mHandlerThread; + private final DispatchHandler mDispatchHandler; + private final CopyOnWriteArrayList<FocusOwnershipListener> mFocusOwnershipListeners = + new CopyOnWriteArrayList<>(); + private final BinderInterfaceContainer.BinderEventHandler<IAppFocusListener> + mAllBinderEventHandler = bInterface -> { /* nothing to do.*/ }; + + public AppFocusService(Context context) { + mAllChangeClients = new ClientHolder(mAllBinderEventHandler); + mAllOwnershipClients = new OwnershipClientHolder(this); + mHandlerThread = new HandlerThread(AppFocusService.class.getSimpleName()); + mHandlerThread.start(); + mDispatchHandler = new DispatchHandler(mHandlerThread.getLooper()); + } + + @Override + public void registerFocusListener(IAppFocusListener listener, int appType) { + synchronized (this) { + ClientInfo info = (ClientInfo) mAllChangeClients.getBinderInterface(listener); + if (info == null) { + info = new ClientInfo(mAllChangeClients, listener, Binder.getCallingUid(), + Binder.getCallingPid(), appType); + mAllChangeClients.addBinderInterface(info); + } else { + info.addAppType(appType); + } + } + } + + @Override + public void unregisterFocusListener(IAppFocusListener listener, int appType) { + synchronized (this) { + ClientInfo info = (ClientInfo) mAllChangeClients.getBinderInterface(listener); + if (info == null) { + return; + } + info.removeAppType(appType); + if (info.getAppTypes().isEmpty()) { + mAllChangeClients.removeBinder(listener); + } + } + } + + @Override + public int[] getActiveAppTypes() { + synchronized (this) { + return toIntArray(mActiveAppTypes); + } + } + + @Override + public boolean isOwningFocus(IAppFocusOwnershipListener listener, int appType) { + synchronized (this) { + OwnershipClientInfo info = + (OwnershipClientInfo) mAllOwnershipClients.getBinderInterface(listener); + if (info == null) { + return false; + } + return info.getOwnedAppTypes().contains(appType); + } + } + + @Override + public int requestAppFocus(IAppFocusOwnershipListener listener, int appType) { + synchronized (this) { + OwnershipClientInfo info = + (OwnershipClientInfo) mAllOwnershipClients.getBinderInterface(listener); + if (info == null) { + info = new OwnershipClientInfo(mAllOwnershipClients, listener, + Binder.getCallingUid(), Binder.getCallingPid()); + mAllOwnershipClients.addBinderInterface(info); + } + Set<Integer> alreadyOwnedAppTypes = info.getOwnedAppTypes(); + if (!alreadyOwnedAppTypes.contains(appType)) { + OwnershipClientInfo ownerInfo = mFocusOwners.get(appType); + if (ownerInfo != null && ownerInfo != info) { + //TODO check if current owner is having fore-ground activity. If yes, + //reject request. Always grant if requester is fore-ground activity. + ownerInfo.removeOwnedAppType(appType); + mDispatchHandler.requestAppFocusOwnershipLossDispatch( + ownerInfo.binderInterface, appType); + if (DBG) { + Log.i(CarLog.TAG_APP_FOCUS, "losing app type " + + appType + "," + ownerInfo.toString()); + } + } + updateFocusOwner(appType, info); + } + info.addOwnedAppType(appType); + if (mActiveAppTypes.add(appType)) { + if (DBG) { + Log.i(CarLog.TAG_APP_FOCUS, "adding active app type " + appType + "," + + info.toString()); + } + for (BinderInterfaceContainer.BinderInterface<IAppFocusListener> client : + mAllChangeClients.getInterfaces()) { + ClientInfo clientInfo = (ClientInfo) client; + // dispatch events only when there is change after filter and the listener + // is not coming from the current caller. + if (clientInfo.getAppTypes().contains(appType)) { + mDispatchHandler.requestAppFocusChangeDispatch(clientInfo.binderInterface, + appType, true); + } + } + } + } + return CarAppFocusManager.APP_FOCUS_REQUEST_GRANTED; + } + + @Override + public void abandonAppFocus(IAppFocusOwnershipListener listener, int appType) { + synchronized (this) { + OwnershipClientInfo info = + (OwnershipClientInfo) mAllOwnershipClients.getBinderInterface(listener); + if (info == null) { + // ignore as this client cannot have owned anything. + return; + } + if (!mActiveAppTypes.contains(appType)) { + // ignore as none of them are active; + return; + } + Set<Integer> currentlyOwnedAppTypes = info.getOwnedAppTypes(); + if (!currentlyOwnedAppTypes.contains(appType)) { + // ignore as listener doesn't own focus. + return; + } + if (mFocusOwners.remove(appType) != null) { + mActiveAppTypes.remove(appType); + info.removeOwnedAppType(appType); + if (DBG) { + Log.i(CarLog.TAG_APP_FOCUS, "abandoning focus " + appType + + "," + info.toString()); + } + for (FocusOwnershipListener ownershipListener : mFocusOwnershipListeners) { + ownershipListener.onFocusAbandoned(appType, info.mUid, info.mPid); + } + for (BinderInterfaceContainer.BinderInterface<IAppFocusListener> client : + mAllChangeClients.getInterfaces()) { + ClientInfo clientInfo = (ClientInfo) client; + if (clientInfo.getAppTypes().contains(appType)) { + mDispatchHandler.requestAppFocusChangeDispatch(clientInfo.binderInterface, + appType, false); + } + } + } + } + } + + @Override + public void init() { + // nothing to do + } + + @Override + public void release() { + synchronized (this) { + mAllChangeClients.clear(); + mAllOwnershipClients.clear(); + mFocusOwners.clear(); + mActiveAppTypes.clear(); + } + } + + @Override + public void onBinderDeath( + BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipListener> bInterface) { + OwnershipClientInfo info = (OwnershipClientInfo) bInterface; + for (Integer appType : info.getOwnedAppTypes()) { + abandonAppFocus(bInterface.binderInterface, appType); + } + } + + @Override + public void dump(PrintWriter writer) { + writer.println("**AppFocusService**"); + synchronized (this) { + writer.println("mActiveAppTypes:" + mActiveAppTypes); + for (BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipListener> client : + mAllOwnershipClients.getInterfaces()) { + OwnershipClientInfo clientInfo = (OwnershipClientInfo) client; + writer.println(clientInfo.toString()); + } + } + } + + /** + * Returns true if process with given uid and pid owns provided focus. + */ + public boolean isFocusOwner(int uid, int pid, int appType) { + synchronized (this) { + if (mFocusOwners.containsKey(appType)) { + OwnershipClientInfo clientInfo = mFocusOwners.get(appType); + return clientInfo.getUid() == uid && clientInfo.getPid() == pid; + } + } + return false; + } + + /** + * Defines callback functions that will be called when ownership has been changed. + */ + public interface FocusOwnershipListener { + void onFocusAcquired(int appType, int uid, int pid); + void onFocusAbandoned(int appType, int uid, int pid); + } + + /** + * Registers listener. + * + * If any focus already acquired it will trigger + * {@link FocusOwnershipListener#onFocusAcquired} call immediately in the same thread. + */ + public void registerContextOwnerChangedListener(FocusOwnershipListener listener) { + mFocusOwnershipListeners.add(listener); + + HashSet<Map.Entry<Integer, OwnershipClientInfo>> owners; + synchronized (this) { + owners = new HashSet<>(mFocusOwners.entrySet()); + } + + for (Map.Entry<Integer, OwnershipClientInfo> entry : owners) { + OwnershipClientInfo clientInfo = entry.getValue(); + listener.onFocusAcquired(entry.getKey(), clientInfo.getUid(), clientInfo.getPid()); + } + } + + /** + * Unregisters provided listener. + */ + public void unregisterContextOwnerChangedListener(FocusOwnershipListener listener) { + mFocusOwnershipListeners.remove(listener); + } + + private void updateFocusOwner(int appType, OwnershipClientInfo owner) { + synchronized (this) { + mFocusOwners.put(appType, owner); + } + + CarServiceUtils.runOnMain(() -> { + for (FocusOwnershipListener listener : mFocusOwnershipListeners) { + listener.onFocusAcquired(appType, owner.getUid(), owner.getPid()); + } + }); + } + + private void dispatchAppFocusOwnershipLoss(IAppFocusOwnershipListener listener, int appType) { + try { + listener.onAppFocusOwnershipLoss(appType); + } catch (RemoteException e) { + } + } + + private void dispatchAppFocusChange(IAppFocusListener listener, int appType, boolean active) { + try { + listener.onAppFocusChange(appType, active); + } catch (RemoteException e) { + } + } + + private static class ClientHolder extends BinderInterfaceContainer<IAppFocusListener> { + private ClientHolder(BinderEventHandler<IAppFocusListener> holder) { + super(holder); + } + } + + private static class OwnershipClientHolder extends + BinderInterfaceContainer<IAppFocusOwnershipListener> { + private OwnershipClientHolder(AppFocusService service) { + super(service); + } + } + + private static class ClientInfo extends + BinderInterfaceContainer.BinderInterface<IAppFocusListener> { + private final int mUid; + private final int mPid; + private final Set<Integer> mAppTypes = new HashSet<>(); + + private ClientInfo(ClientHolder holder, IAppFocusListener binder, int uid, int pid, + int appType) { + super(holder, binder); + this.mUid = uid; + this.mPid = pid; + this.mAppTypes.add(appType); + } + + private synchronized Set<Integer> getAppTypes() { + return mAppTypes; + } + + private synchronized boolean addAppType(Integer appType) { + return mAppTypes.add(appType); + } + + private synchronized boolean removeAppType(Integer appType) { + return mAppTypes.remove(appType); + } + + @Override + public String toString() { + synchronized (this) { + return "ClientInfo{mUid=" + mUid + ",mPid=" + mPid + + ",appTypes=" + mAppTypes + "}"; + } + } + } + + private static class OwnershipClientInfo extends + BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipListener> { + private final int mUid; + private final int mPid; + private final Set<Integer> mOwnedAppTypes = new HashSet<>(); + + private OwnershipClientInfo(OwnershipClientHolder holder, IAppFocusOwnershipListener binder, + int uid, int pid) { + super(holder, binder); + this.mUid = uid; + this.mPid = pid; + } + + private synchronized Set<Integer> getOwnedAppTypes() { + if (DBG_EVENT) { + Log.i(CarLog.TAG_APP_FOCUS, "getOwnedAppTypes " + mOwnedAppTypes); + } + return mOwnedAppTypes; + } + + private synchronized boolean addOwnedAppType(Integer appType) { + if (DBG_EVENT) { + Log.i(CarLog.TAG_APP_FOCUS, "addOwnedAppType " + appType); + } + return mOwnedAppTypes.add(appType); + } + + private synchronized boolean removeOwnedAppType(Integer appType) { + if (DBG_EVENT) { + Log.i(CarLog.TAG_APP_FOCUS, "removeOwnedAppType " + appType); + } + return mOwnedAppTypes.remove(appType); + } + + int getUid() { + return mUid; + } + + int getPid() { + return mPid; + } + + @Override + public String toString() { + synchronized (this) { + return "ClientInfo{mUid=" + mUid + ",mPid=" + mPid + + ",owned=" + mOwnedAppTypes + "}"; + } + } + } + + private class DispatchHandler extends Handler { + private static final int MSG_DISPATCH_OWNERSHIP_LOSS = 0; + private static final int MSG_DISPATCH_FOCUS_CHANGE = 1; + + private DispatchHandler(Looper looper) { + super(looper); + } + + private void requestAppFocusOwnershipLossDispatch(IAppFocusOwnershipListener listener, + int appType) { + Message msg = obtainMessage(MSG_DISPATCH_OWNERSHIP_LOSS, appType, 0, listener); + sendMessage(msg); + } + + private void requestAppFocusChangeDispatch(IAppFocusListener listener, int appType, + boolean active) { + Message msg = obtainMessage(MSG_DISPATCH_FOCUS_CHANGE, appType, active ? 1 : 0, + listener); + sendMessage(msg); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_DISPATCH_OWNERSHIP_LOSS: + dispatchAppFocusOwnershipLoss((IAppFocusOwnershipListener) msg.obj, msg.arg1); + break; + case MSG_DISPATCH_FOCUS_CHANGE: + dispatchAppFocusChange((IAppFocusListener) msg.obj, msg.arg1, msg.arg2 == 1); + break; + } + } + } + + private static int[] toIntArray(Set<Integer> intSet) { + int[] intArr = new int[intSet.size()]; + int index = 0; + for (Integer value : intSet) { + intArr[index++] = value; + } + return intArr; + } +} diff --git a/service/src/com/android/car/AudioRoutingPolicy.java b/service/src/com/android/car/AudioRoutingPolicy.java index fdad5aaeb9..0a5951976a 100644 --- a/service/src/com/android/car/AudioRoutingPolicy.java +++ b/service/src/com/android/car/AudioRoutingPolicy.java @@ -40,6 +40,11 @@ public class AudioRoutingPolicy { public static AudioRoutingPolicy create(Context context, int policyNumber) { final Resources res = context.getResources(); String[] policies = res.getStringArray(R.array.audioRoutingPolicy); + if (policyNumber > (policies.length - 1)) { + Log.e(CarLog.TAG_AUDIO, "AudioRoutingPolicy.create got wrong policy number:" + + policyNumber + ", num of avaiable policies:" + policies.length); + policyNumber = 0; + } return new AudioRoutingPolicy(policies[policyNumber]); } diff --git a/service/src/com/android/car/CarAudioAttributesUtil.java b/service/src/com/android/car/CarAudioAttributesUtil.java index 1d0cb00ebe..54ed2c638c 100644 --- a/service/src/com/android/car/CarAudioAttributesUtil.java +++ b/service/src/com/android/car/CarAudioAttributesUtil.java @@ -28,7 +28,7 @@ public class CarAudioAttributesUtil { public static final int CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY = 101; public static final int CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE = 102; - /** Bundle key for storing media type */ + /** Bundle key for storing media type. */ public static final String KEY_CAR_AUDIO_TYPE = "car_audio_type"; private static final int CAR_AUDIO_TYPE_DEFAULT = 0; @@ -38,12 +38,17 @@ public class CarAudioAttributesUtil { private static final int CAR_AUDIO_TYPE_CARSERVICE_BOTTOM = 4; private static final int CAR_AUDIO_TYPE_CARSERVICE_CAR_PROXY = 5; private static final int CAR_AUDIO_TYPE_CARSERVICE_MEDIA_MUTE = 6; + private static final int CAR_AUDIO_TYPE_EXTERNAL_SOURCE = 7; + + /** Bundle key for storing routing type which is String. */ + public static final String KEY_EXT_ROUTING_TYPE = "ext_routing_type"; public static AudioAttributes getAudioAttributesForCarUsage(int carUsage) { switch (carUsage) { case CarAudioManager.CAR_AUDIO_USAGE_MUSIC: return createAudioAttributes(AudioAttributes.CONTENT_TYPE_MUSIC, AudioAttributes.USAGE_MEDIA); + case CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE: // default to radio case CarAudioManager.CAR_AUDIO_USAGE_RADIO: return createCustomAudioAttributes(CAR_AUDIO_TYPE_RADIO, AudioAttributes.CONTENT_TYPE_MUSIC, AudioAttributes.USAGE_MEDIA); @@ -98,10 +103,13 @@ public class CarAudioAttributesUtil { } switch (usage) { case AudioAttributes.USAGE_MEDIA: - if (type == CAR_AUDIO_TYPE_RADIO) { - return CarAudioManager.CAR_AUDIO_USAGE_RADIO; - } else { - return CarAudioManager.CAR_AUDIO_USAGE_MUSIC; + switch (type) { + case CAR_AUDIO_TYPE_RADIO: + return CarAudioManager.CAR_AUDIO_USAGE_RADIO; + case CAR_AUDIO_TYPE_EXTERNAL_SOURCE: + return CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE; + default: + return CarAudioManager.CAR_AUDIO_USAGE_MUSIC; } case AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE: if (type == CAR_AUDIO_TYPE_VOICE_COMMAND) { @@ -149,4 +157,36 @@ public class CarAudioAttributesUtil { bundle.putInt(KEY_CAR_AUDIO_TYPE, carAudioType); return builder.setContentType(contentType).setUsage(usage).addBundle(bundle).build(); } + + public static AudioAttributes getCarRadioAttributes(String radioType) { + AudioAttributes.Builder builder = new AudioAttributes.Builder(); + Bundle bundle = new Bundle(); + bundle.putInt(KEY_CAR_AUDIO_TYPE, CAR_AUDIO_TYPE_RADIO); + bundle.putString(KEY_EXT_ROUTING_TYPE, radioType); + return builder.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC). + setUsage(AudioAttributes.USAGE_MEDIA).addBundle(bundle).build(); + } + + public static AudioAttributes getCarExtSourceAttributes(String externalSourceType) { + AudioAttributes.Builder builder = new AudioAttributes.Builder(); + Bundle bundle = new Bundle(); + bundle.putInt(KEY_CAR_AUDIO_TYPE, CAR_AUDIO_TYPE_EXTERNAL_SOURCE); + bundle.putString(KEY_EXT_ROUTING_TYPE, externalSourceType); + return builder.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC). + setUsage(AudioAttributes.USAGE_MEDIA).addBundle(bundle).build(); + } + + /** + * Get ext routing type from given AudioAttributes. + * @param attr + * @return {@link CarAudioManager#CAR_RADIO_TYPE_AM_FM} if ext routing info does not exist. + */ + public static String getExtRouting(AudioAttributes attr) { + Bundle bundle = attr.getBundle(); + String extRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM; + if (bundle != null) { + extRouting = bundle.getString(KEY_EXT_ROUTING_TYPE); + } + return extRouting; + } } diff --git a/service/src/com/android/car/CarAudioService.java b/service/src/com/android/car/CarAudioService.java index c6f09a990c..a6d278500a 100644 --- a/service/src/com/android/car/CarAudioService.java +++ b/service/src/com/android/car/CarAudioService.java @@ -40,6 +40,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.util.Log; +import android.util.Pair; import com.android.car.hal.AudioHalService; import com.android.car.hal.AudioHalService.AudioHalFocusListener; @@ -47,7 +48,13 @@ import com.android.car.hal.VehicleHal; import com.android.internal.annotations.GuardedBy; import java.io.PrintWriter; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, AudioHalFocusListener { @@ -107,7 +114,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, @GuardedBy("mLock") private int mBottomFocusState; @GuardedBy("mLock") - private boolean mRadioActive = false; + private boolean mRadioOrExtSourceActive = false; @GuardedBy("mLock") private boolean mCallActive = false; @GuardedBy("mLock") @@ -125,6 +132,24 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, @GuardedBy("mLock") private int mNumConsecutiveHalFailures; + @GuardedBy("mLock") + private boolean mExternalRoutingHintSupported; + @GuardedBy("mLock") + private Map<String, AudioHalService.ExtRoutingSourceInfo> mExternalRoutingTypes; + @GuardedBy("mLock") + private Set<String> mExternalRadioRoutingTypes; + @GuardedBy("mLock") + private String mDefaultRadioRoutingType; + @GuardedBy("mLock") + private Set<String> mExternalNonRadioRoutingTypes; + @GuardedBy("mLock") + private int mRadioPhysicalStream; + @GuardedBy("mLock") + private int[] mExternalRoutings = {0, 0, 0, 0}; + private int[] mExternalRoutingsScratch = {0, 0, 0, 0}; + private final int[] mExternalRoutingsForFocusRelease = {0, 0, 0, 0}; + private final ExtSourceInfo mExtSourceInfoScratch = new ExtSourceInfo(); + private final boolean mUseDynamicRouting; private final AudioAttributes mAttributeBottom = @@ -191,12 +216,61 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, } mAudioHal.setFocusListener(this); mAudioHal.setAudioRoutingPolicy(audioRoutingPolicy); + // get call outside lock as it can take time + HashSet<String> externalRadioRoutingTypes = new HashSet<>(); + HashSet<String> externalNonRadioRoutingTypes = new HashSet<>(); + Map<String, AudioHalService.ExtRoutingSourceInfo> externalRoutingTypes = + mAudioHal.getExternalAudioRoutingTypes(); + if (externalRoutingTypes != null) { + for (String routingType : externalRoutingTypes.keySet()) { + if (routingType.startsWith("RADIO_")) { + externalRadioRoutingTypes.add(routingType); + } else { + externalNonRadioRoutingTypes.add(routingType); + } + } + } + // select default radio routing. AM_FM -> AM_FM_HD -> whatever with AM or FM -> first one + String defaultRadioRouting = null; + if (externalRadioRoutingTypes.contains(CarAudioManager.CAR_RADIO_TYPE_AM_FM)) { + defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM; + } else if (externalRadioRoutingTypes.contains(CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD)) { + defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD; + } else { + for (String radioType : externalRadioRoutingTypes) { + // set to 1st one + if (defaultRadioRouting == null) { + defaultRadioRouting = radioType; + } + if (radioType.contains("AM") || radioType.contains("FM")) { + defaultRadioRouting = radioType; + break; + } + } + } + if (defaultRadioRouting == null) { // no radio type defined. fall back to AM_FM + defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM; + } + int radioPhysicalStream = audioRoutingPolicy.getPhysicalStreamForLogicalStream( + CarAudioManager.CAR_AUDIO_USAGE_RADIO); synchronized (mLock) { if (audioPolicy != null) { mAudioPolicy = audioPolicy; } + mRadioPhysicalStream = radioPhysicalStream; mAudioRoutingPolicy = audioRoutingPolicy; mIsRadioExternal = mAudioHal.isRadioExternal(); + if (externalRoutingTypes != null) { + mExternalRoutingHintSupported = true; + mExternalRoutingTypes = externalRoutingTypes; + } else { + mExternalRoutingHintSupported = false; + mExternalRoutingTypes = new HashMap<>(); + } + mExternalRadioRoutingTypes = externalRadioRoutingTypes; + mExternalNonRadioRoutingTypes = externalNonRadioRoutingTypes; + mDefaultRadioRoutingType = defaultRadioRouting; + Arrays.fill(mExternalRoutings, 0); } mVolumeService.init(); } @@ -322,7 +396,7 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, mLastFocusRequestToCar = null; mTopFocusInfo = null; mPendingFocusChanges.clear(); - mRadioActive = false; + mRadioOrExtSourceActive = false; if (mCarAudioContextChangeHandler != null) { mCarAudioContextChangeHandler.cancelAll(); mCarAudioContextChangeHandler = null; @@ -331,6 +405,9 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, mCurrentPrimaryAudioContext = 0; audioPolicy = mAudioPolicy; mAudioPolicy = null; + mExternalRoutingTypes.clear(); + mExternalRadioRoutingTypes.clear(); + mExternalNonRadioRoutingTypes.clear(); } if (audioPolicy != null) { mAudioManager.unregisterAudioPolicyAsync(audioPolicy); @@ -351,18 +428,33 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, @Override public void dump(PrintWriter writer) { - writer.println("*CarAudioService*"); - writer.println(" mCurrentFocusState:" + mCurrentFocusState + - " mLastFocusRequestToCar:" + mLastFocusRequestToCar); - writer.println(" mCurrentAudioContexts:0x" + Integer.toHexString(mCurrentAudioContexts)); - writer.println(" mCallActive:" + mCallActive + " mRadioActive:" + mRadioActive); - writer.println(" mCurrentPrimaryAudioContext:" + mCurrentPrimaryAudioContext + - " mCurrentPrimaryPhysicalStream:" + mCurrentPrimaryPhysicalStream); - writer.println(" mIsRadioExternal:" + mIsRadioExternal); - writer.println(" mNumConsecutiveHalFailures:" + mNumConsecutiveHalFailures); - writer.println(" media muted:" + mMediaMuteAudioFocusListener.isMuted()); - writer.println(" mAudioPolicy:" + mAudioPolicy); - mAudioRoutingPolicy.dump(writer); + synchronized (mLock) { + writer.println("*CarAudioService*"); + writer.println(" mCurrentFocusState:" + mCurrentFocusState + + " mLastFocusRequestToCar:" + mLastFocusRequestToCar); + writer.println(" mCurrentAudioContexts:0x" + + Integer.toHexString(mCurrentAudioContexts)); + writer.println(" mCallActive:" + mCallActive + " mRadioOrExtSourceActive:" + + mRadioOrExtSourceActive); + writer.println(" mCurrentPrimaryAudioContext:" + mCurrentPrimaryAudioContext + + " mCurrentPrimaryPhysicalStream:" + mCurrentPrimaryPhysicalStream); + writer.println(" mIsRadioExternal:" + mIsRadioExternal); + writer.println(" mNumConsecutiveHalFailures:" + mNumConsecutiveHalFailures); + writer.println(" media muted:" + mMediaMuteAudioFocusListener.isMuted()); + writer.println(" mAudioPolicy:" + mAudioPolicy); + mAudioRoutingPolicy.dump(writer); + writer.println(" mExternalRoutingHintSupported:" + mExternalRoutingHintSupported); + if (mExternalRoutingHintSupported) { + writer.println(" mDefaultRadioRoutingType:" + mDefaultRadioRoutingType); + writer.println(" Routing Types:"); + for (Entry<String, AudioHalService.ExtRoutingSourceInfo> entry : + mExternalRoutingTypes.entrySet()) { + writer.println(" type:" + entry.getKey() + " info:" + entry.getValue()); + } + } + } + writer.println("** Dump CarVolumeService**"); + mVolumeService.dump(writer); } @Override @@ -429,6 +521,44 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, } } + @Override + public AudioAttributes getAudioAttributesForRadio(String radioType) { + synchronized (mLock) { + if (!mExternalRadioRoutingTypes.contains(radioType)) { // type not exist + throw new IllegalArgumentException("Specified radio type is not available:" + + radioType); + } + } + return CarAudioAttributesUtil.getCarRadioAttributes(radioType); + } + + @Override + public AudioAttributes getAudioAttributesForExternalSource(String externalSourceType) { + synchronized (mLock) { + if (!mExternalNonRadioRoutingTypes.contains(externalSourceType)) { // type not exist + throw new IllegalArgumentException("Specified ext source type is not available:" + + externalSourceType); + } + } + return CarAudioAttributesUtil.getCarExtSourceAttributes(externalSourceType); + } + + @Override + public String[] getSupportedExternalSourceTypes() { + synchronized (mLock) { + return mExternalNonRadioRoutingTypes.toArray( + new String[mExternalNonRadioRoutingTypes.size()]); + } + } + + @Override + public String[] getSupportedRadioTypes() { + synchronized (mLock) { + return mExternalRadioRoutingTypes.toArray( + new String[mExternalRadioRoutingTypes.size()]); + } + } + /** * API for system to control mute with lock. * @param mute @@ -495,12 +625,12 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS; } mLastFocusRequestToCar = null; - if (mRadioActive && + if (mRadioOrExtSourceActive && (mCurrentFocusState.externalFocus & AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG) == 0) { // radio flag dropped newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS; - mRadioActive = false; + mRadioOrExtSourceActive = false; } if (newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS || newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT || @@ -627,18 +757,20 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, return false; } - private boolean isFocusFromExternalRadio(AudioFocusInfo info) { - if (!mIsRadioExternal) { - // if radio is not external, no special handling of radio is necessary. - return false; - } + private boolean isFocusFromExternalRadioOrExternalSource(AudioFocusInfo info) { if (info == null) { return false; } AudioAttributes attrib = info.getAttributes(); - if (attrib != null && - CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) == - CarAudioManager.CAR_AUDIO_USAGE_RADIO) { + if (attrib == null) { + return false; + } + // if radio is not external, no special handling of radio is necessary. + if (CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) == + CarAudioManager.CAR_AUDIO_USAGE_RADIO && mIsRadioExternal) { + return true; + } else if (CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) == + CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE) { return true; } return false; @@ -679,8 +811,8 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState); break; } - if (mRadioActive) { // radio is no longer active. - mRadioActive = false; + if (mRadioOrExtSourceActive) { // radio is no longer active. + mRadioOrExtSourceActive = false; } return false; } @@ -692,53 +824,53 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, ? logicalStreamTypeForTop : CarAudioManager.CAR_AUDIO_USAGE_MUSIC); boolean muteMedia = false; + String primaryExtSource = CarAudioAttributesUtil.getExtRouting(attrib); // update primary context and notify if necessary - int primaryContext = AudioHalService.logicalStreamToHalContextType(logicalStreamTypeForTop); - switch (logicalStreamTypeForTop) { - case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE: + int primaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType( + logicalStreamTypeForTop, primaryExtSource); + if (logicalStreamTypeForTop == + CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) { muteMedia = true; - // remaining parts the same with other cases. fall through. - case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM: - case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY: - primaryContext = 0; - break; - } - // save the current context now but it is sent to context change listener after focus - // response from car - if (mCurrentPrimaryAudioContext != primaryContext) { - mCurrentPrimaryAudioContext = primaryContext; - mCurrentPrimaryPhysicalStream = physicalStreamTypeForTop; } - - int audioContexts = 0; if (logicalStreamTypeForTop == CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL) { - if (!mCallActive) { - mCallActive = true; - audioContexts |= AudioHalService.AUDIO_CONTEXT_CALL_FLAG; - } + mCallActive = true; } else { - if (mCallActive) { - mCallActive = false; - } - audioContexts = primaryContext; + mCallActive = false; } // other apps having focus int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE; int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG; int streamsToRequest = 0x1 << physicalStreamTypeForTop; + boolean primaryIsExternal = false; + if (isFocusFromExternalRadioOrExternalSource(mTopFocusInfo)) { + streamsToRequest = 0; + mRadioOrExtSourceActive = true; + primaryIsExternal = true; + if (fixExtSourceAndContext( + mExtSourceInfoScratch.set(primaryExtSource, primaryContext))) { + primaryExtSource = mExtSourceInfoScratch.source; + primaryContext = mExtSourceInfoScratch.context; + } + } else { + mRadioOrExtSourceActive = false; + primaryExtSource = null; + } + // save the current context now but it is sent to context change listener after focus + // response from car + if (mCurrentPrimaryAudioContext != primaryContext) { + mCurrentPrimaryAudioContext = primaryContext; + mCurrentPrimaryPhysicalStream = physicalStreamTypeForTop; + } + + boolean secondaryIsExternal = false; + int secondaryContext = 0; + String secondaryExtSource = null; switch (mTopFocusInfo.getGainRequest()) { case AudioManager.AUDIOFOCUS_GAIN: - if (isFocusFromExternalRadio(mTopFocusInfo)) { - mRadioActive = true; - } else { - mRadioActive = false; - } focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN; break; case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: - // radio cannot be active - mRadioActive = false; focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT; break; case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: @@ -758,9 +890,30 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, muteMedia = true; break; } - int secondContext = AudioHalService.logicalStreamToHalContextType( - logicalStreamTypeForSecond); - audioContexts |= secondContext; + if (isFocusFromExternalRadioOrExternalSource(mSecondFocusInfo)) { + secondaryIsExternal = true; + secondaryExtSource = CarAudioAttributesUtil.getExtRouting(secondAttrib); + secondaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType( + logicalStreamTypeForSecond, secondaryExtSource); + if (fixExtSourceAndContext( + mExtSourceInfoScratch.set(secondaryExtSource, secondaryContext))) { + secondaryExtSource = mExtSourceInfoScratch.source; + secondaryContext = mExtSourceInfoScratch.context; + } + int secondaryExtPhysicalStreamFlag = + getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource); + if ((secondaryExtPhysicalStreamFlag & streamsToRequest) != 0) { + // secondary stream is the same as primary. cannot keep secondary + secondaryIsExternal = false; + secondaryContext = 0; + secondaryExtSource = null; + break; + } + mRadioOrExtSourceActive = true; + } else { + secondaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType( + logicalStreamTypeForSecond, null); + } switch (mCurrentFocusState.focusState) { case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN: streamsToRequest |= mCurrentFocusState.streams; @@ -783,45 +936,155 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, streamsToRequest = 0; break; } + int audioContexts = 0; if (muteMedia) { - mRadioActive = false; - audioContexts &= ~(AudioHalService.AUDIO_CONTEXT_RADIO_FLAG | - AudioHalService.AUDIO_CONTEXT_MUSIC_FLAG); - extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG; - int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream( - CarAudioManager.CAR_AUDIO_USAGE_RADIO); - streamsToRequest &= ~(0x1 << radioPhysicalStream); - } else if (mRadioActive) { - // TODO any need to keep media stream while radio is active? - // Most cars do not allow that, but if mixing is possible, it can take media stream. - // For now, assume no mixing capability. - int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream( - CarAudioManager.CAR_AUDIO_USAGE_RADIO); - if (!isFocusFromExternalRadio(mTopFocusInfo) && - (physicalStreamTypeForTop == radioPhysicalStream) && mIsRadioExternal) { - Log.i(CarLog.TAG_AUDIO, "Top stream is taking the same stream:" + - physicalStreamTypeForTop + " as radio, stopping radio"); - // stream conflict here. radio cannot be played - extFocus = 0; - mRadioActive = false; - audioContexts &= ~AudioHalService.AUDIO_CONTEXT_RADIO_FLAG; + boolean addMute = true; + if (primaryIsExternal) { + if ((getPhysicalStreamFlagForExtSourceLocked(primaryExtSource) & + (0x1 << mRadioPhysicalStream)) != 0) { + // cannot mute as primary is media + addMute = false; + } + } else if (secondaryIsExternal) { + if ((getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource) & + (0x1 << mRadioPhysicalStream)) != 0) { + mRadioOrExtSourceActive = false; + } } else { + mRadioOrExtSourceActive = false; + } + audioContexts = primaryContext | secondaryContext; + if (addMute) { + audioContexts &= ~(AudioHalService.AUDIO_CONTEXT_RADIO_FLAG | + AudioHalService.AUDIO_CONTEXT_MUSIC_FLAG | + AudioHalService.AUDIO_CONTEXT_CD_ROM_FLAG | + AudioHalService.AUDIO_CONTEXT_AUX_AUDIO_FLAG); + extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG; + streamsToRequest &= ~(0x1 << mRadioPhysicalStream); + } + } else if (mRadioOrExtSourceActive) { + boolean addExtFocusFlag = true; + if (primaryIsExternal) { + int primaryExtPhysicalStreamFlag = + getPhysicalStreamFlagForExtSourceLocked(primaryExtSource); + if (secondaryIsExternal) { + int secondaryPhysicalStreamFlag = + getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource); + if (primaryExtPhysicalStreamFlag == secondaryPhysicalStreamFlag) { + // overlap, drop secondary + audioContexts &= ~secondaryContext; + secondaryContext = 0; + secondaryExtSource = null; + } + streamsToRequest = 0; + } else { // primary only + if (streamsToRequest == primaryExtPhysicalStreamFlag) { + // cannot keep secondary + secondaryContext = 0; + } + streamsToRequest &= ~primaryExtPhysicalStreamFlag; + } + } + if (addExtFocusFlag) { extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG; - streamsToRequest &= ~(0x1 << radioPhysicalStream); } + audioContexts = primaryContext | secondaryContext; } else if (streamsToRequest == 0) { mCurrentAudioContexts = 0; mFocusHandler.handleFocusReleaseRequest(); return false; + } else { + audioContexts = primaryContext | secondaryContext; } + boolean routingHintChanged = sendExtRoutingHintToCarIfNecessaryLocked(primaryExtSource, + secondaryExtSource); return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus, - audioContexts); + audioContexts, routingHintChanged); + } + + /** + * Fix external source info if it is not valid. + * @param extSourceInfo + * @return true if value is not valid and was updated. + */ + private boolean fixExtSourceAndContext(ExtSourceInfo extSourceInfo) { + if (!mExternalRoutingTypes.containsKey(extSourceInfo.source)) { + Log.w(CarLog.TAG_AUDIO, "External source not available:" + extSourceInfo.source); + // fall back to radio + extSourceInfo.source = mDefaultRadioRoutingType; + extSourceInfo.context = AudioHalService.AUDIO_CONTEXT_RADIO_FLAG; + return true; + } + if (extSourceInfo.context == AudioHalService.AUDIO_CONTEXT_RADIO_FLAG && + !extSourceInfo.source.startsWith("RADIO_")) { + Log.w(CarLog.TAG_AUDIO, "Expecting Radio source:" + extSourceInfo.source); + extSourceInfo.source = mDefaultRadioRoutingType; + return true; + } + return false; + } + + private int getPhysicalStreamFlagForExtSourceLocked(String extSource) { + AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get( + extSource); + if (info != null) { + return 0x1 << info.physicalStreamNumber; + } else { + return 0x1 << mRadioPhysicalStream; + } + } + + private boolean sendExtRoutingHintToCarIfNecessaryLocked(String primarySource, + String secondarySource) { + if (!mExternalRoutingHintSupported) { + return false; + } + if (DBG) { + Log.d(TAG_FOCUS, "Setting external routing hint, primary:" + primarySource + + " secondary:" + secondarySource); + } + Arrays.fill(mExternalRoutingsScratch, 0); + fillExtRoutingPositionLocked(mExternalRoutingsScratch, primarySource); + fillExtRoutingPositionLocked(mExternalRoutingsScratch, secondarySource); + if (Arrays.equals(mExternalRoutingsScratch, mExternalRoutings)) { + return false; + } + System.arraycopy(mExternalRoutingsScratch, 0, mExternalRoutings, 0, + mExternalRoutingsScratch.length); + if (DBG) { + Log.d(TAG_FOCUS, "Set values:" + Arrays.toString(mExternalRoutingsScratch)); + } + try { + mAudioHal.setExternalRoutingSource(mExternalRoutings); + } catch (IllegalArgumentException e) { + //ignore. can happen with mocking. + return false; + } + return true; + } + + private void fillExtRoutingPositionLocked(int[] array, String extSource) { + if (extSource == null) { + return; + } + AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get( + extSource); + if (info == null) { + return; + } + int pos = info.bitPosition; + if (pos < 0) { + return; + } + int index = pos / 32; + int bitPosInInt = pos % 32; + array[index] |= (0x1 << bitPosInInt); } private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest, - int streamsToRequest, int extFocus, int audioContexts) { + int streamsToRequest, int extFocus, int audioContexts, boolean forceSend) { if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus, - audioContexts)) { + audioContexts) || forceSend) { mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest, extFocus); mCurrentAudioContexts = audioContexts; @@ -963,6 +1226,9 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, mLastFocusRequestToCar = FocusRequest.STATE_RELEASE; sent = true; try { + if (mExternalRoutingHintSupported) { + mAudioHal.setExternalRoutingSource(mExternalRoutingsForFocusRelease); + } mAudioHal.requestAudioFocusChange( AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0); } catch (IllegalArgumentException e) { @@ -1355,4 +1621,16 @@ public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, public static FocusRequest STATE_RELEASE = new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0); } + + private static class ExtSourceInfo { + + public String source; + public int context; + + public ExtSourceInfo set(String source, int context) { + this.source = source; + this.context = context; + return this; + } + } } diff --git a/service/src/com/android/car/CarHvacService.java b/service/src/com/android/car/CarHvacService.java index ab09f606ab..173e3077e5 100644 --- a/service/src/com/android/car/CarHvacService.java +++ b/service/src/com/android/car/CarHvacService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2016 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. @@ -17,182 +17,13 @@ package com.android.car; import android.car.Car; -import android.car.hardware.hvac.CarHvacEvent; -import android.car.hardware.CarPropertyConfig; -import android.car.hardware.CarPropertyValue; -import android.car.hardware.hvac.ICarHvac; -import android.car.hardware.hvac.ICarHvacEventListener; import android.content.Context; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.car.hal.HvacHalService; +import com.android.car.CarLog; import com.android.car.hal.VehicleHal; -import java.io.PrintWriter; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class CarHvacService extends ICarHvac.Stub - implements CarServiceBase, HvacHalService.HvacHalListener { - public static final boolean DBG = true; - public static final String TAG = CarLog.TAG_HVAC + ".CarHvacService"; - - private HvacHalService mHvacHal; - private final Map<IBinder, ICarHvacEventListener> mListenersMap = new HashMap<>(); - private final Map<IBinder, HvacDeathRecipient> mDeathRecipientMap = new HashMap<>(); - private final Context mContext; - +public class CarHvacService extends CarPropertyServiceBase { public CarHvacService(Context context) { - mHvacHal = VehicleHal.getInstance().getHvacHal(); - mContext = context; - } - - class HvacDeathRecipient implements IBinder.DeathRecipient { - private static final String TAG = CarHvacService.TAG + ".HvacDeathRecipient"; - private IBinder mListenerBinder; - - HvacDeathRecipient(IBinder listenerBinder) { - mListenerBinder = listenerBinder; - } - - /** - * Client died. Remove the listener from HAL service and unregister if this is the last - * client. - */ - @Override - public void binderDied() { - if (DBG) { - Log.d(TAG, "binderDied " + mListenerBinder); - } - CarHvacService.this.unregisterListenerLocked(mListenerBinder); - } - - void release() { - mListenerBinder.unlinkToDeath(this, 0); - } - } - - @Override - public synchronized void init() { - } - - @Override - public synchronized void release() { - for (HvacDeathRecipient recipient : mDeathRecipientMap.values()) { - recipient.release(); - } - mDeathRecipientMap.clear(); - mListenersMap.clear(); - } - - @Override - public void dump(PrintWriter writer) { - // TODO - } - - @Override - public synchronized void registerListener(ICarHvacEventListener listener) { - if (DBG) { - Log.d(TAG, "registerListener"); - } - ICarImpl.assertHvacPermission(mContext); - if (listener == null) { - Log.e(TAG, "registerListener: Listener is null."); - throw new IllegalArgumentException("listener cannot be null."); - } - - IBinder listenerBinder = listener.asBinder(); - if (mListenersMap.containsKey(listenerBinder)) { - // Already registered, nothing to do. - return; - } - - HvacDeathRecipient deathRecipient = new HvacDeathRecipient(listenerBinder); - try { - listenerBinder.linkToDeath(deathRecipient, 0); - } catch (RemoteException e) { - Log.e(TAG, "Failed to link death for recipient. " + e); - throw new IllegalStateException(Car.CAR_NOT_CONNECTED_EXCEPTION_MSG); - } - mDeathRecipientMap.put(listenerBinder, deathRecipient); - - if (mListenersMap.isEmpty()) { - mHvacHal.setListener(this); - } - - mListenersMap.put(listenerBinder, listener); - } - - @Override - public synchronized void unregisterListener(ICarHvacEventListener listener) { - if (DBG) { - Log.d(TAG, "unregisterListener"); - } - ICarImpl.assertHvacPermission(mContext); - if (listener == null) { - Log.e(TAG, "unregisterListener: Listener is null."); - throw new IllegalArgumentException("Listener is null"); - } - - IBinder listenerBinder = listener.asBinder(); - if (!mListenersMap.containsKey(listenerBinder)) { - Log.e(TAG, "unregisterListener: Listener was not previously registered."); - } - unregisterListenerLocked(listenerBinder); - } - - // Removes the listenerBinder from the current state. - // The function assumes that the binder will exist both in listeners and death recipients list. - private void unregisterListenerLocked(IBinder listenerBinder) { - Object status = mListenersMap.remove(listenerBinder); - - if (status != null) { - mDeathRecipientMap.get(listenerBinder).release(); - mDeathRecipientMap.remove(listenerBinder); - } - - if (mListenersMap.isEmpty()) { - mHvacHal.setListener(null); - } - } - - @Override - public synchronized List<CarPropertyConfig> getHvacProperties() { - ICarImpl.assertHvacPermission(mContext); - return mHvacHal.getHvacProperties(); - } - - @Override - public synchronized CarPropertyValue getProperty(int prop, int zone) { - ICarImpl.assertHvacPermission(mContext); - return mHvacHal.getHvacProperty(prop, zone); - } - - @Override - public synchronized void setProperty(CarPropertyValue prop) { - ICarImpl.assertHvacPermission(mContext); - mHvacHal.setHvacProperty(prop); - } - - // Implement HvacHalListener interface - @Override - public synchronized void onPropertyChange(CarHvacEvent event) { - for (ICarHvacEventListener l : mListenersMap.values()) { - try { - l.onEvent(event); - } catch (RemoteException ex) { - // If we could not send a record, its likely the connection snapped. Let the binder - // death handle the situation. - Log.e(TAG, "onEvent calling failed: " + ex); - } - } - } - - @Override - public synchronized void onError(int zone, int property) { - // TODO: + super(context, VehicleHal.getInstance().getHvacHal(), Car.PERMISSION_CAR_HVAC, true, + CarLog.TAG_HVAC); } } diff --git a/service/src/com/android/car/CarLog.java b/service/src/com/android/car/CarLog.java index 79ed6060ea..6b2a2e404e 100644 --- a/service/src/com/android/car/CarLog.java +++ b/service/src/com/android/car/CarLog.java @@ -17,22 +17,24 @@ package com.android.car; public class CarLog { - public static final String TAG_APP_CONTEXT = "CAR.APP_CONTEXT"; + public static final String TAG_AM = "CAR.AM"; + public static final String TAG_APP_FOCUS = "CAR.APP_FOCUS"; public static final String TAG_AUDIO = "CAR.AUDIO"; public static final String TAG_CAMERA = "CAR.CAMERA"; + public static final String TAG_CAN_BUS = "CAR.CAN_BUS"; + public static final String TAG_CLUSTER = "CAR.CLUSTER"; public static final String TAG_HAL = "CAR.HAL"; public static final String TAG_HVAC = "CAR.HVAC"; public static final String TAG_INFO = "CAR.INFO"; + public static final String TAG_INPUT = "CAR.INPUT"; + public static final String TAG_NAV = "CAR.NAV"; public static final String TAG_PACKAGE = "CAR.PACKAGE"; public static final String TAG_POWER = "CAR.POWER"; + public static final String TAG_PROJECTION = "CAR.PROJECTION"; + public static final String TAG_PROPERTY = "CAR.PROPERTY"; public static final String TAG_RADIO = "CAR.RADIO"; public static final String TAG_SENSOR = "CAR.SENSOR"; public static final String TAG_SERVICE = "CAR.SERVICE"; - public static final String TAG_NAV = "CAR.NAV"; - public static final String TAG_TEST = "CAR.TEST"; - public static final String TAG_INPUT = "CAR.INPUT"; - public static final String TAG_PROJECTION = "CAR.PROJECTION"; - public static final String TAG_CLUSTER = "CAR.CLUSTER"; - public static final String TAG_CAN_BUS = "CAR.CAN_BUS"; public static final String TAG_SYS = "CAR.SYS"; + public static final String TAG_TEST = "CAR.TEST"; } diff --git a/service/src/com/android/car/CarPropertyServiceBase.java b/service/src/com/android/car/CarPropertyServiceBase.java new file mode 100644 index 0000000000..4c2028ad70 --- /dev/null +++ b/service/src/com/android/car/CarPropertyServiceBase.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.car; + +import android.car.Car; +import android.car.hardware.CarPropertyConfig; +import android.car.hardware.CarPropertyValue; +import android.car.hardware.property.CarPropertyEvent; +import android.car.hardware.property.ICarProperty; +import android.car.hardware.property.ICarPropertyEventListener; +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import com.android.car.hal.PropertyHalServiceBase; + +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class implements the binder interface for ICarProperty.aidl to make it easier to create + * multiple managers that deal with Vehicle Properties. To create a new service, simply extend + * this class and call the super() constructor with the appropriate arguments for the new service. + * CarHvacService.java shows the basic usage. + */ +public class CarPropertyServiceBase extends ICarProperty.Stub + implements CarServiceBase, PropertyHalServiceBase.PropertyHalListener { + private final Context mContext; + private final boolean mDbg; + private final Map<IBinder, PropertyDeathRecipient> mDeathRecipientMap = new HashMap<>(); + private final PropertyHalServiceBase mHal; + private final Map<IBinder, ICarPropertyEventListener> mListenersMap = new HashMap<>(); + private final String mPermission; + private final String mTag; + + public CarPropertyServiceBase(Context context, PropertyHalServiceBase hal, String permission, + boolean dbg, String tag) { + mContext = context; + mHal = hal; + mPermission = permission; + mDbg = dbg; + mTag = tag + ".service"; + } + + class PropertyDeathRecipient implements IBinder.DeathRecipient { + private IBinder mListenerBinder; + + PropertyDeathRecipient(IBinder listenerBinder) { + mListenerBinder = listenerBinder; + } + + /** + * Client died. Remove the listener from HAL service and unregister if this is the last + * client. + */ + @Override + public void binderDied() { + if (mDbg) { + Log.d(mTag, "binderDied " + mListenerBinder); + } + CarPropertyServiceBase.this.unregisterListenerLocked(mListenerBinder); + } + + void release() { + mListenerBinder.unlinkToDeath(this, 0); + } + } + + @Override + public synchronized void init() { + } + + @Override + public synchronized void release() { + for (PropertyDeathRecipient recipient : mDeathRecipientMap.values()) { + recipient.release(); + } + mDeathRecipientMap.clear(); + mListenersMap.clear(); + } + + @Override + public void dump(PrintWriter writer) { + // TODO + } + + @Override + public synchronized void registerListener(ICarPropertyEventListener listener) { + if (mDbg) { + Log.d(mTag, "registerListener"); + } + ICarImpl.assertPermission(mContext, mPermission); + if (listener == null) { + Log.e(mTag, "registerListener: Listener is null."); + throw new IllegalArgumentException("listener cannot be null."); + } + + IBinder listenerBinder = listener.asBinder(); + if (mListenersMap.containsKey(listenerBinder)) { + // Already registered, nothing to do. + return; + } + + PropertyDeathRecipient deathRecipient = new PropertyDeathRecipient(listenerBinder); + try { + listenerBinder.linkToDeath(deathRecipient, 0); + } catch (RemoteException e) { + Log.e(mTag, "Failed to link death for recipient. " + e); + throw new IllegalStateException(Car.CAR_NOT_CONNECTED_EXCEPTION_MSG); + } + mDeathRecipientMap.put(listenerBinder, deathRecipient); + + if (mListenersMap.isEmpty()) { + mHal.setListener(this); + } + + mListenersMap.put(listenerBinder, listener); + } + + @Override + public synchronized void unregisterListener(ICarPropertyEventListener listener) { + if (mDbg) { + Log.d(mTag, "unregisterListener"); + } + ICarImpl.assertPermission(mContext, mPermission); + if (listener == null) { + Log.e(mTag, "unregisterListener: Listener is null."); + throw new IllegalArgumentException("Listener is null"); + } + + IBinder listenerBinder = listener.asBinder(); + if (!mListenersMap.containsKey(listenerBinder)) { + Log.e(mTag, "unregisterListener: Listener was not previously registered."); + } + unregisterListenerLocked(listenerBinder); + } + + // Removes the listenerBinder from the current state. + // The function assumes that binder will exist both in listeners and death recipients list. + private void unregisterListenerLocked(IBinder listenerBinder) { + boolean found = mListenersMap.remove(listenerBinder) != null; + + if (found) { + mDeathRecipientMap.get(listenerBinder).release(); + mDeathRecipientMap.remove(listenerBinder); + } + + if (mListenersMap.isEmpty()) { + mHal.setListener(null); + } + } + + @Override + public synchronized List<CarPropertyConfig> getPropertyList() { + ICarImpl.assertPermission(mContext, mPermission); + return mHal.getPropertyList(); + } + + @Override + public synchronized CarPropertyValue getProperty(int prop, int zone) { + ICarImpl.assertPermission(mContext, mPermission); + return mHal.getProperty(prop, zone); + } + + @Override + public synchronized void setProperty(CarPropertyValue prop) { + ICarImpl.assertPermission(mContext, mPermission); + mHal.setProperty(prop); + } + + // Implement PropertyHalListener interface + @Override + public synchronized void onPropertyChange(CarPropertyEvent event) { + for (ICarPropertyEventListener l : mListenersMap.values()) { + try { + l.onEvent(event); + } catch (RemoteException ex) { + // If we could not send a record, its likely the connection snapped. Let the binder + // death handle the situation. + Log.e(mTag, "onEvent calling failed: " + ex); + } + } + } + + @Override + public synchronized void onError(int zone, int property) { + // TODO: + } +} diff --git a/service/src/com/android/car/CarVolumeControllerFactory.java b/service/src/com/android/car/CarVolumeControllerFactory.java index b126f0cb18..ef4a5e1141 100644 --- a/service/src/com/android/car/CarVolumeControllerFactory.java +++ b/service/src/com/android/car/CarVolumeControllerFactory.java @@ -36,6 +36,7 @@ import com.android.car.CarVolumeService.CarVolumeController; import com.android.car.hal.AudioHalService; import com.android.internal.annotations.GuardedBy; +import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; @@ -136,6 +137,12 @@ public class CarVolumeControllerFactory { return true; } + @Override + public void dump(PrintWriter writer) { + writer.println("Volume controller:" + SimpleCarVolumeController.class.getSimpleName()); + // nothing else to dump + } + private void handleVolumeKeyDefault(KeyEvent event) { if (event.getAction() != KeyEvent.ACTION_DOWN || interceptVolKeyBeforeDispatching(mContext)) { @@ -200,6 +207,7 @@ public class CarVolumeControllerFactory { private int mSupportedAudioContext; private boolean mHasExternalMemory; + private boolean mMasterVolumeOnly; @GuardedBy("this") private int mCurrentContext = CarVolumeService.DEFAULT_CAR_AUDIO_CONTEXT; @@ -266,6 +274,11 @@ public class CarVolumeControllerFactory { case MSG_UPDATE_HAL: stream = msg.arg1; volume = msg.arg2; + synchronized (CarExternalVolumeController.this) { + if (mMasterVolumeOnly) { + stream = 0; + } + } mHal.setStreamVolume(stream, volume); break; default: @@ -287,6 +300,7 @@ public class CarVolumeControllerFactory { void init() { mSupportedAudioContext = mHal.getSupportedAudioVolumeContexts(); mHasExternalMemory = mHal.isExternalAudioVolumePersistent(); + mMasterVolumeOnly = mHal.isAudioVolumeMasterOnly(); synchronized (this) { initVolumeLimitLocked(); initCurrentVolumeLocked(); @@ -328,7 +342,8 @@ public class CarVolumeControllerFactory { int carStream = carContextToCarStream(i); Integer volume = volumesPerCarStream.get(carStream); if (volume == null) { - volume = Integer.valueOf(mHal.getStreamVolume(carStream)); + volume = Integer.valueOf(mHal.getStreamVolume(mMasterVolumeOnly ? 0 : + carStream)); volumesPerCarStream.put(carStream, volume); } mCurrentCarContextVolume.put(i, volume); @@ -422,11 +437,14 @@ public class CarVolumeControllerFactory { synchronized (this) { if (DBG) { Log.d(TAG, "onVolumeChange carStream:" + carStream + " volume: " + volume - + "volumeState: " + volumeState); + + " volumeState: " + volumeState); } // Assume single channel here. int currentLogicalStream = VolumeUtils.carContextToAndroidStream(mCurrentContext); int currentCarStream = carContextToCarStream(mCurrentContext); + if (mMasterVolumeOnly) { //for master volume only H/W, always assume current stream + carStream = currentCarStream; + } if (currentCarStream == carStream) { mCurrentCarContextVolume.put(mCurrentContext, volume); mHandler.sendMessage( @@ -537,7 +555,7 @@ public class CarVolumeControllerFactory { // Otherwise, we need to tell Hal what the correct volume is for the new context. int currentVolume = mCurrentCarContextVolume.get(primaryFocusContext); - int carStreamNumber = mSupportedAudioContext == 0 ? primaryFocusPhysicalStream : + int carStreamNumber = (mSupportedAudioContext == 0) ? primaryFocusPhysicalStream : primaryFocusContext; if (DBG) { Log.d(TAG, "Change volume from: " @@ -547,5 +565,32 @@ public class CarVolumeControllerFactory { updateHalVolumeLocked(carStreamNumber, currentVolume); } } + + @Override + public void dump(PrintWriter writer) { + writer.println("Volume controller:" + + CarExternalVolumeController.class.getSimpleName()); + synchronized (this) { + writer.println("mSupportedAudioContext:0x" + + Integer.toHexString(mSupportedAudioContext) + + ",mHasExternalMemory:" + mHasExternalMemory + + ",mMasterVolumeOnly:" + mMasterVolumeOnly); + writer.println("mCurrentContext:0x" + Integer.toHexString(mCurrentContext)); + writer.println("mCurrentCarContextVolume:"); + dumpVolumes(writer, mCurrentCarContextVolume); + writer.println("mCarContextVolumeMax:"); + dumpVolumes(writer, mCarContextVolumeMax); + writer.println("mCarContextVolumeMin:"); + dumpVolumes(writer, mCarContextVolumeMin); + writer.println("Number of volume controllers:" + + mVolumeControllers.getRegisteredCallbackCount()); + } + } + + private void dumpVolumes(PrintWriter writer, SparseArray<Integer> array) { + for (int i = 0; i < array.size(); i++) { + writer.println("0x" + Integer.toHexString(array.keyAt(i)) + ":" + array.valueAt(i)); + } + } } } diff --git a/service/src/com/android/car/CarVolumeService.java b/service/src/com/android/car/CarVolumeService.java index b9bc5829bd..c8bfc9053e 100644 --- a/service/src/com/android/car/CarVolumeService.java +++ b/service/src/com/android/car/CarVolumeService.java @@ -24,6 +24,8 @@ import android.view.KeyEvent; import com.android.car.hal.AudioHalService; import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioContextFlag; +import java.io.PrintWriter; + /** * Handles car volume controls. * @@ -87,6 +89,10 @@ public class CarVolumeService { return getController().getStreamVolume(stream); } + public void dump(PrintWriter writer) { + mCarVolumeController.dump(writer); + } + private synchronized CarVolumeController getController() { return mCarVolumeController; } @@ -102,5 +108,6 @@ public class CarVolumeService { abstract public int getStreamMaxVolume(int stream); abstract public int getStreamMinVolume(int stream); abstract public int getStreamVolume(int stream); + abstract public void dump(PrintWriter writer); } } diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java index 580e5e96a1..05e8f592e2 100644 --- a/service/src/com/android/car/ICarImpl.java +++ b/service/src/com/android/car/ICarImpl.java @@ -18,12 +18,12 @@ package com.android.car; import android.car.Car; import android.car.ICar; +import android.car.cluster.renderer.IInstrumentClusterNavigation; import android.content.Context; import android.content.pm.PackageManager; import android.os.IBinder; import android.util.Log; -import com.android.car.cluster.CarNavigationService; import com.android.car.cluster.InstrumentClusterService; import com.android.car.hal.VehicleHal; import com.android.car.pm.CarPackageManagerService; @@ -34,6 +34,8 @@ import java.io.PrintWriter; public class ICarImpl extends ICar.Stub { public static final String INTERNAL_INPUT_SERVICE = "internal_input"; + public static final String INTERNAL_SYSTEM_ACTIVITY_MONITORING_SERVICE = + "system_activity_monitoring"; // load jni for all services here static { @@ -46,6 +48,7 @@ public class ICarImpl extends ICar.Stub { private final Context mContext; private final VehicleHal mHal; + private final SystemActivityMonitoringService mSystemActivityMonitoringService; private final CarPowerManagementService mCarPowerManagementService; private final CarPackageManagerService mCarPackageManagerService; private final CarInputService mCarInputService; @@ -57,9 +60,8 @@ public class ICarImpl extends ICar.Stub { private final CarHvacService mCarHvacService; private final CarRadioService mCarRadioService; private final CarNightService mCarNightService; - private final AppContextService mAppContextService; + private final AppFocusService mAppFocusService; private final GarageModeService mGarageModeService; - private final CarNavigationService mCarNavigationService; private final InstrumentClusterService mInstrumentClusterService; private final SystemStateControllerService mSystemStateControllerService; @@ -87,34 +89,36 @@ public class ICarImpl extends ICar.Stub { public ICarImpl(Context serviceContext) { mContext = serviceContext; mHal = VehicleHal.getInstance(); + mSystemActivityMonitoringService = new SystemActivityMonitoringService(serviceContext); mCarPowerManagementService = new CarPowerManagementService(serviceContext); + mCarSensorService = new CarSensorService(serviceContext); + mCarPackageManagerService = new CarPackageManagerService(serviceContext, mCarSensorService, + mSystemActivityMonitoringService); mCarInputService = new CarInputService(serviceContext); mCarProjectionService = new CarProjectionService(serviceContext, mCarInputService); mGarageModeService = new GarageModeService(mContext, mCarPowerManagementService); mCarInfoService = new CarInfoService(serviceContext); - mAppContextService = new AppContextService(serviceContext); - mCarSensorService = new CarSensorService(serviceContext); + mAppFocusService = new AppFocusService(serviceContext); mCarAudioService = new CarAudioService(serviceContext, mCarInputService); mCarHvacService = new CarHvacService(serviceContext); mCarRadioService = new CarRadioService(serviceContext); mCarCameraService = new CarCameraService(serviceContext); mCarNightService = new CarNightService(serviceContext); - mCarPackageManagerService = new CarPackageManagerService(serviceContext); - mInstrumentClusterService = new InstrumentClusterService(serviceContext); - mCarNavigationService = new CarNavigationService( - mAppContextService, mInstrumentClusterService); + mInstrumentClusterService = new InstrumentClusterService(serviceContext, + mAppFocusService, mCarInputService); mSystemStateControllerService = new SystemStateControllerService(serviceContext, mCarPowerManagementService, mCarAudioService, this); // Be careful with order. Service depending on other service should be inited later. mAllServices = new CarServiceBase[] { + mSystemActivityMonitoringService, mCarPowerManagementService, + mCarSensorService, mCarPackageManagerService, mCarInputService, mGarageModeService, mCarInfoService, - mAppContextService, - mCarSensorService, + mAppFocusService, mCarAudioService, mCarHvacService, mCarRadioService, @@ -122,7 +126,6 @@ public class ICarImpl extends ICar.Stub { mCarNightService, mInstrumentClusterService, mCarProjectionService, - mCarNavigationService, mSystemStateControllerService }; } @@ -172,8 +175,8 @@ public class ICarImpl extends ICar.Stub { return mCarSensorService; case Car.INFO_SERVICE: return mCarInfoService; - case Car.APP_CONTEXT_SERVICE: - return mAppContextService; + case Car.APP_FOCUS_SERVICE: + return mAppFocusService; case Car.PACKAGE_SERVICE: return mCarPackageManagerService; case Car.CAMERA_SERVICE: @@ -187,7 +190,9 @@ public class ICarImpl extends ICar.Stub { return mCarRadioService; case Car.CAR_NAVIGATION_SERVICE: assertNavigationManagerPermission(mContext); - return mCarNavigationService; + IInstrumentClusterNavigation navService = + mInstrumentClusterService.getNavigationService(); + return navService == null ? null : navService.asBinder(); case Car.PROJECTION_SERVICE: assertProjectionPermission(mContext); return mCarProjectionService; @@ -219,6 +224,8 @@ public class ICarImpl extends ICar.Stub { switch (serviceName) { case INTERNAL_INPUT_SERVICE: return mCarInputService; + case INTERNAL_SYSTEM_ACTIVITY_MONITORING_SERVICE: + return mSystemActivityMonitoringService; default: Log.w(CarLog.TAG_SERVICE, "getCarInternalService for unknown service:" + serviceName); @@ -238,49 +245,32 @@ public class ICarImpl extends ICar.Stub { } public static void assertVehicleHalMockPermission(Context context) { - if (context.checkCallingOrSelfPermission(Car.PERMISSION_MOCK_VEHICLE_HAL) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("requires CAR_MOCK_VEHICLE_HAL permission"); - } + assertPermission(context, Car.PERMISSION_MOCK_VEHICLE_HAL); } public static void assertCameraPermission(Context context) { - if (context.checkCallingOrSelfPermission(Car.PERMISSION_CAR_CAMERA) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException( - "requires " + Car.PERMISSION_CAR_CAMERA); - } + assertPermission(context, Car.PERMISSION_CAR_CAMERA); } public static void assertNavigationManagerPermission(Context context) { - if (context.checkCallingOrSelfPermission(Car.PERMISSION_CAR_NAVIGATION_MANAGER) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException( - "requires " + Car.PERMISSION_CAR_NAVIGATION_MANAGER); - } + assertPermission(context, Car.PERMISSION_CAR_NAVIGATION_MANAGER); } public static void assertHvacPermission(Context context) { - if (context.checkCallingOrSelfPermission(Car.PERMISSION_CAR_HVAC) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException( - "requires " + Car.PERMISSION_CAR_HVAC); - } + assertPermission(context, Car.PERMISSION_CAR_HVAC); } private static void assertRadioPermission(Context context) { - if (context.checkCallingOrSelfPermission(Car.PERMISSION_CAR_RADIO) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException( - "requires permission " + Car.PERMISSION_CAR_RADIO); - } + assertPermission(context, Car.PERMISSION_CAR_RADIO); } public static void assertProjectionPermission(Context context) { - if (context.checkCallingOrSelfPermission(Car.PERMISSION_CAR_PROJECTION) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException( - "requires " + Car.PERMISSION_CAR_PROJECTION); + assertPermission(context, Car.PERMISSION_CAR_PROJECTION); + } + + public static void assertPermission(Context context, String permission) { + if (context.checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("requires " + permission); } } diff --git a/service/src/com/android/car/SystemActivityMonitoringService.java b/service/src/com/android/car/SystemActivityMonitoringService.java new file mode 100644 index 0000000000..f647993a1e --- /dev/null +++ b/service/src/com/android/car/SystemActivityMonitoringService.java @@ -0,0 +1,462 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.car; + +import android.app.ActivityManager; +import android.app.ActivityManager.StackInfo; +import android.app.ActivityManagerNative; +import android.app.IActivityManager; +import android.app.IProcessObserver; +import android.app.ITaskStackListener; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.ArraySet; +import android.util.Log; +import android.util.Pair; +import android.util.SparseArray; + +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Service to monitor AMS for new Activity or Service launching. + */ +public class SystemActivityMonitoringService implements CarServiceBase { + + /** + * Container to hold info on top task in an Activity stack + */ + public static class TopTaskInfoContainer { + public final ComponentName topActivity; + public final int taskId; + public final StackInfo stackInfo; + + private TopTaskInfoContainer(ComponentName topActivity, int taskId, StackInfo stackInfo) { + this.topActivity = topActivity; + this.taskId = taskId; + this.stackInfo = stackInfo; + } + + public boolean isMatching(ComponentName topActivity, int taskId, StackInfo stackInfo) { + return this.topActivity.equals(topActivity) && this.taskId == taskId && + this.stackInfo.stackId == stackInfo.stackId && + this.stackInfo.userId == stackInfo.userId; + } + + @Override + public String toString() { + return String.format( + "TaskInfoContainer [topActivity=%s, taskId=%d, stackId=%d, userId=%d", + topActivity, taskId, stackInfo.stackId, stackInfo.userId); + } + } + + public interface ActivityLaunchListener { + /** + * Notify launch of activity. + * @param topTask Task information for what is currently launched. + */ + void onActivityLaunch(TopTaskInfoContainer topTask); + } + + private static final boolean DBG = true; + + private static final int NUM_MAX_TASK_TO_FETCH = 10; + + private final Context mContext; + private final IActivityManager mAm; + private final ProcessObserver mProcessObserver; + private final TaskListener mTaskListener; + + private final HandlerThread mMonitorHandlerThread; + private final ActivityMonitorHandler mHandler; + + /** K: stack id, V: top task */ + private final SparseArray<TopTaskInfoContainer> mTopTasks = new SparseArray<>(); + /** K: uid, V : list of pid */ + private final Map<Integer, Set<Integer>> mForegroundUidPids = new HashMap<>(); + private int mFocusedStackId = -1; + + /** + * Temporary container to dispatch tasks for onActivityLaunch. Only used in handler thread. + * can be accessed without lock. */ + private final List<TopTaskInfoContainer> mTasksToDispatch = new LinkedList<>(); + private ActivityLaunchListener mActivityLaunchListener; + + public SystemActivityMonitoringService(Context context) { + mContext = context; + mMonitorHandlerThread = new HandlerThread(CarLog.TAG_AM); + mMonitorHandlerThread.start(); + mHandler = new ActivityMonitorHandler(mMonitorHandlerThread.getLooper()); + mProcessObserver = new ProcessObserver(); + mTaskListener = new TaskListener(); + mAm = ActivityManagerNative.getDefault(); + // Monitoring both listeners are necessary as there are cases where one listener cannot + // monitor activity change. + try { + mAm.registerProcessObserver(mProcessObserver); + mAm.registerTaskStackListener(mTaskListener); + } catch (RemoteException e) { + Log.e(CarLog.TAG_AM, "cannot register activity monitoring", e); + throw new RuntimeException(e); + } + updateTasks(); + } + + @Override + public void init() { + } + + @Override + public void release() { + } + + @Override + public void dump(PrintWriter writer) { + writer.println("*SystemActivityMonitoringService*"); + writer.println(" Top Tasks:"); + synchronized (this) { + for (int i = 0; i < mTopTasks.size(); i++) { + TopTaskInfoContainer info = mTopTasks.valueAt(i); + if (info != null) { + writer.println(info); + } + } + writer.println(" Foregroud uid-pids:"); + for (Integer key : mForegroundUidPids.keySet()) { + Set<Integer> pids = mForegroundUidPids.get(key); + if (pids == null) { + continue; + } + writer.println("uid:" + key + ", pids:" + Arrays.toString(pids.toArray())); + } + writer.println(" focused stack:" + mFocusedStackId); + } + } + + /** + * Block the current task: Launch new activity with given Intent and finish the current task. + * @param currentTask task to finish + * @param newActivityIntent Intent for new Activity + */ + public void blockActivity(TopTaskInfoContainer currentTask, Intent newActivityIntent) { + mHandler.requestBlockActivity(currentTask, newActivityIntent); + } + + public List<TopTaskInfoContainer> getTopTasks() { + LinkedList<TopTaskInfoContainer> tasks = new LinkedList<>(); + synchronized (this) { + for (int i = 0; i < mTopTasks.size(); i++) { + tasks.add(mTopTasks.valueAt(i)); + } + } + return tasks; + } + + public boolean isInForeground(int pid, int uid) { + synchronized (this) { + Set<Integer> pids = mForegroundUidPids.get(uid); + if (pids == null) { + return false; + } + if (pids.contains(pid)) { + return true; + } + } + return false; + } + + public void registerActivityLaunchListener(ActivityLaunchListener listener) { + synchronized (this) { + mActivityLaunchListener = listener; + } + } + + private void updateTasks() { + List<StackInfo> infos; + try { + infos = mAm.getAllStackInfos(); + } catch (RemoteException e) { + Log.e(CarLog.TAG_AM, "cannot getTasks", e); + return; + } + int focusedStackId = -1; + try { + focusedStackId = mAm.getFocusedStackId(); + } catch (RemoteException e) { + Log.e(CarLog.TAG_AM, "cannot getFocusedStackId", e); + return; + } + mTasksToDispatch.clear(); + ActivityLaunchListener listener; + synchronized (this) { + listener = mActivityLaunchListener; + for (StackInfo info : infos) { + int stackId = info.stackId; + if (info.taskNames.length == 0 || !info.visible) { // empty stack or not shown + mTopTasks.remove(stackId); + continue; + } + // Assume last activity as top activity. StackInfo.topAcvitiy does not represent + // visible Activity correctly. Things will break if this assumption does not work. + int topActivityTaskId = info.taskIds[info.taskIds.length -1]; + String topActivityName = info.taskNames[info.taskNames.length -1]; + ComponentName topActivity = ComponentName.unflattenFromString(topActivityName); + TopTaskInfoContainer currentTopTaskInfo = mTopTasks.get(stackId); + // if a new task is added to stack or focused stack changes, should notify + if (currentTopTaskInfo == null || + !currentTopTaskInfo.isMatching(topActivity, topActivityTaskId, info) || + (focusedStackId == stackId && focusedStackId != mFocusedStackId)) { + currentTopTaskInfo = new TopTaskInfoContainer(topActivity, + topActivityTaskId, info); + mTopTasks.put(stackId, currentTopTaskInfo); + mTasksToDispatch.add(currentTopTaskInfo); + if (DBG) { + Log.i(CarLog.TAG_AM, "top activity:" + topActivityName + " stack:" + info); + } + } + } + mFocusedStackId = focusedStackId; + } + if (listener != null) { + for (TopTaskInfoContainer topTask : mTasksToDispatch) { + if (DBG) { + Log.i(CarLog.TAG_AM, "activity launched:" + topTask.toString()); + } + listener.onActivityLaunch(topTask); + } + } + } + + public StackInfo getFocusedStackForTopActivity(ComponentName activity) { + int focusedStackId = -1; + try { + focusedStackId = mAm.getFocusedStackId(); + } catch (RemoteException e) { + Log.e(CarLog.TAG_AM, "cannot getFocusedStackId", e); + return null; + } + StackInfo focusedStack; + try { + focusedStack = mAm.getStackInfo(focusedStackId); + } catch (RemoteException e) { + Log.e(CarLog.TAG_AM, "cannot getFocusedStackId", e); + return null; + } + if (focusedStack.taskNames.length == 0) { // nothing in focused stack + return null; + } + ComponentName topActivity = ComponentName.unflattenFromString( + focusedStack.taskNames[focusedStack.taskNames.length - 1]); + if (topActivity.equals(activity)) { + return focusedStack; + } else { + return null; + } + } + + private void handleForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) { + synchronized (this) { + if (foregroundActivities) { + Set<Integer> pids = mForegroundUidPids.get(uid); + if (pids == null) { + pids = new ArraySet<Integer>(); + mForegroundUidPids.put(uid, pids); + } + pids.add(pid); + } else { + doHandlePidGoneLocked(pid, uid); + } + } + } + + private void handleProcessDied(int pid, int uid) { + synchronized (this) { + doHandlePidGoneLocked(pid, uid); + } + } + + private void doHandlePidGoneLocked(int pid, int uid) { + Set<Integer> pids = mForegroundUidPids.get(uid); + if (pids != null) { + pids.remove(pid); + if (pids.isEmpty()) { + mForegroundUidPids.remove(uid); + } + } + } + + private void handleBlockActivity(TopTaskInfoContainer currentTask, Intent newActivityIntent) { + Log.i(CarLog.TAG_AM, String.format("stopping activity %s with taskid:%d", + currentTask.topActivity, currentTask.taskId)); + newActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivityAsUser(newActivityIntent, + new UserHandle(currentTask.stackInfo.userId)); + // now make stack with new activity focused. + findTaskAndGrantFocus(newActivityIntent.getComponent()); + try { + mAm.removeTask(currentTask.taskId); + } catch (RemoteException e) { + Log.w(CarLog.TAG_AM, "cannot remove task:" + currentTask.taskId, e); + } + } + + private void findTaskAndGrantFocus(ComponentName activity) { + List<StackInfo> infos; + try { + infos = mAm.getAllStackInfos(); + } catch (RemoteException e) { + Log.e(CarLog.TAG_AM, "cannot getTasks", e); + return; + } + for (StackInfo info : infos) { + if (info.taskNames.length == 0) { + continue; + } + ComponentName topActivity = ComponentName.unflattenFromString( + info.taskNames[info.taskNames.length - 1]); + if (activity.equals(topActivity)) { + try { + mAm.setFocusedStack(info.stackId); + } catch (RemoteException e) { + Log.e(CarLog.TAG_AM, "cannot setFocusedStack to stack:" + info.stackId, e); + } + return; + } + } + Log.i(CarLog.TAG_AM, "cannot give focus, cannot find Activity:" + activity); + } + + private class ProcessObserver extends IProcessObserver.Stub { + @Override + public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) { + if (DBG) { + Log.i(CarLog.TAG_AM, + String.format("onForegroundActivitiesChanged uid %d pid %d fg %b", + uid, pid, foregroundActivities)); + } + mHandler.requestForegroundActivitiesChanged(pid, uid, foregroundActivities); + } + + @Override + public void onProcessStateChanged(int pid, int uid, int procState) { + // ignore + } + + @Override + public void onProcessDied(int pid, int uid) { + mHandler.requestProcessDied(pid, uid); + } + } + + private class TaskListener extends ITaskStackListener.Stub { + @Override + public void onTaskStackChanged() { + if (DBG) { + Log.i(CarLog.TAG_AM, "onTaskStackChanged"); + } + mHandler.requestUpdatingTask(); + } + + @Override + public void onActivityPinned() { + } + + @Override + public void onPinnedActivityRestartAttempt() { + } + + @Override + public void onPinnedStackAnimationEnded() { + } + + @Override + public void onActivityForcedResizable(String packageName, int taskId) { + } + + @Override + public void onActivityDismissingDockedStack() { + } + } + + private class ActivityMonitorHandler extends Handler { + private static final int MSG_UPDATE_TASKS = 0; + private static final int MSG_FOREGROUND_ACTIVITIES_CHANGED = 1; + private static final int MSG_PROCESS_DIED = 2; + private static final int MSG_BLOCK_ACTIVITY = 3; + + private ActivityMonitorHandler(Looper looper) { + super(looper); + } + + private void requestUpdatingTask() { + Message msg = obtainMessage(MSG_UPDATE_TASKS); + sendMessage(msg); + } + + private void requestForegroundActivitiesChanged(int pid, int uid, + boolean foregroundActivities) { + Message msg = obtainMessage(MSG_FOREGROUND_ACTIVITIES_CHANGED, pid, uid, + Boolean.valueOf(foregroundActivities)); + sendMessage(msg); + } + + private void requestProcessDied(int pid, int uid) { + Message msg = obtainMessage(MSG_PROCESS_DIED, pid, uid); + sendMessage(msg); + } + + private void requestBlockActivity(TopTaskInfoContainer currentTask, + Intent newActivityIntent) { + Message msg = obtainMessage(MSG_BLOCK_ACTIVITY, + new Pair<TopTaskInfoContainer, Intent>(currentTask, newActivityIntent)); + sendMessage(msg); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_TASKS: + updateTasks(); + break; + case MSG_FOREGROUND_ACTIVITIES_CHANGED: + handleForegroundActivitiesChanged(msg.arg1, msg.arg2, (Boolean) msg.obj); + updateTasks(); + break; + case MSG_PROCESS_DIED: + handleProcessDied(msg.arg1, msg.arg2); + break; + case MSG_BLOCK_ACTIVITY: + Pair<TopTaskInfoContainer, Intent> pair = + (Pair<TopTaskInfoContainer, Intent>) msg.obj; + handleBlockActivity(pair.first, pair.second); + break; + } + } + } +} diff --git a/service/src/com/android/car/cluster/CarNavigationService.java b/service/src/com/android/car/cluster/CarNavigationService.java deleted file mode 100644 index 1e2887df95..0000000000 --- a/service/src/com/android/car/cluster/CarNavigationService.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.car.cluster; - -import android.car.CarAppContextManager; -import android.car.cluster.renderer.NavigationRenderer; -import android.car.navigation.CarNavigationInstrumentCluster; -import android.car.navigation.CarNavigationManager; -import android.car.navigation.ICarNavigation; -import android.car.navigation.ICarNavigationEventListener; -import android.graphics.Bitmap; -import android.os.Binder; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.car.AppContextService; -import com.android.car.CarLog; -import com.android.car.CarServiceBase; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; - -/** - * Service that will push navigation event to navigation renderer in instrument cluster. - */ -public class CarNavigationService extends ICarNavigation.Stub implements CarServiceBase { - private static final String TAG = CarLog.TAG_NAV; - - private final List<CarNavigationEventListener> mListeners = new ArrayList<>(); - private final AppContextService mAppContextService; - private final InstrumentClusterService mInstrumentClusterService; - - private volatile CarNavigationInstrumentCluster mInstrumentClusterInfo = null; - private volatile NavigationRenderer mNavigationRenderer; - - public CarNavigationService(AppContextService appContextService, - InstrumentClusterService instrumentClusterService) { - mAppContextService = appContextService; - mInstrumentClusterService = instrumentClusterService; - } - - @Override - public void init() { - Log.d(TAG, "init"); - mNavigationRenderer = mInstrumentClusterService.getNavigationRenderer(); - mInstrumentClusterInfo = mNavigationRenderer != null - ? mNavigationRenderer.getNavigationProperties() : null; - } - - @Override - public void release() { - synchronized(mListeners) { - mListeners.clear(); - } - } - - @Override - public void sendNavigationStatus(int status) { - Log.d(TAG, "sendNavigationStatus, status: " + status); - if (!isRendererAvailable()) { - return; - } - verifyNavigationContextOwner(); - - if (status == CarNavigationManager.STATUS_ACTIVE) { - mNavigationRenderer.onStartNavigation(); - } else if (status == CarNavigationManager.STATUS_INACTIVE - || status == CarNavigationManager.STATUS_UNAVAILABLE) { - mNavigationRenderer.onStopNavigation(); - } else { - throw new IllegalArgumentException("Unknown navigation status: " + status); - } - } - - @Override - public void sendNavigationTurnEvent( - int event, String road, int turnAngle, int turnNumber, Bitmap image, int turnSide) { - Log.d(TAG, "sendNavigationTurnEvent, event:" + event + ", turnAngle: " + turnAngle + ", " - + "turnNumber: " + turnNumber + ", " + "turnSide: " + turnSide); - if (!isRendererAvailable()) { - return; - } - verifyNavigationContextOwner(); - - mNavigationRenderer.onNextTurnChanged(event, road, turnAngle, turnNumber, image, turnSide); - } - - @Override - public void sendNavigationTurnDistanceEvent(int distanceMeters, int timeSeconds) { - Log.d(TAG, "sendNavigationTurnDistanceEvent, distanceMeters:" + distanceMeters + ", " - + "timeSeconds: " + timeSeconds); - if (!isRendererAvailable()) { - return; - } - verifyNavigationContextOwner(); - - mNavigationRenderer.onNextTurnDistanceChanged(distanceMeters, timeSeconds); - } - - @Override - public boolean registerEventListener(ICarNavigationEventListener listener) { - CarNavigationEventListener eventListener; - synchronized(mListeners) { - if (findClientLocked(listener) != null) { - return true; - } - - eventListener = new CarNavigationEventListener(listener); - try { - listener.asBinder().linkToDeath(eventListener, 0); - } catch (RemoteException e) { - Log.w(TAG, "Adding listener failed.", e); - return false; - } - mListeners.add(eventListener); - } - - // The new listener needs to be told the instrument cluster parameters. - if (isRendererAvailable()) { - return eventListener.onInstrumentClusterStart(mInstrumentClusterInfo); - } - return true; - } - - @Override - public boolean unregisterEventListener(ICarNavigationEventListener listener) { - CarNavigationEventListener client; - synchronized (mListeners) { - client = findClientLocked(listener); - } - return client != null && removeClient(client); - } - - @Override - public CarNavigationInstrumentCluster getInstrumentClusterInfo() { - return mInstrumentClusterInfo; - } - - @Override - public boolean isInstrumentClusterSupported() { - return mInstrumentClusterInfo != null; - } - - private void verifyNavigationContextOwner() { - if (!mAppContextService.isContextOwner( - Binder.getCallingUid(), - Binder.getCallingPid(), - CarAppContextManager.APP_CONTEXT_NAVIGATION)) { - throw new IllegalStateException( - "Client is not an owner of APP_CONTEXT_NAVIGATION."); - } - } - - private boolean removeClient(CarNavigationEventListener listener) { - synchronized(mListeners) { - for (CarNavigationEventListener currentListener : mListeners) { - // Use asBinder() for comparison. - if (currentListener == listener) { - currentListener.listener.asBinder().unlinkToDeath(currentListener, 0); - return mListeners.remove(currentListener); - } - } - } - return false; - } - - private CarNavigationEventListener findClientLocked( - ICarNavigationEventListener listener) { - for (CarNavigationEventListener existingListener : mListeners) { - if (existingListener.listener.asBinder() == listener.asBinder()) { - return existingListener; - } - } - return null; - } - - private class CarNavigationEventListener implements IBinder.DeathRecipient { - final ICarNavigationEventListener listener; - - public CarNavigationEventListener(ICarNavigationEventListener listener) { - this.listener = listener; - } - - @Override - public void binderDied() { - listener.asBinder().unlinkToDeath(this, 0); - removeClient(this); - } - - /** Returns true if event sent successfully */ - public boolean onInstrumentClusterStart(CarNavigationInstrumentCluster clusterInfo) { - try { - listener.onInstrumentClusterStart(clusterInfo); - } catch (RemoteException e) { - Log.e(TAG, "Unable to call onInstrumentClusterStart for listener: " + listener, e); - return false; - } - return true; - } - } - - @Override - public void dump(PrintWriter writer) { - // TODO Auto-generated method stub - } - - private boolean isRendererAvailable() { - boolean available = mNavigationRenderer != null && mInstrumentClusterInfo != null; - if (!available) { - Log.w(TAG, "Instrument cluster renderer is not available."); - } - return available; - } -} diff --git a/service/src/com/android/car/cluster/InstrumentClusterRendererLoader.java b/service/src/com/android/car/cluster/InstrumentClusterRendererLoader.java deleted file mode 100644 index 6d0a590e5e..0000000000 --- a/service/src/com/android/car/cluster/InstrumentClusterRendererLoader.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.car.cluster; - -import android.car.cluster.renderer.InstrumentClusterRenderer; -import android.content.Context; -import android.content.ContextWrapper; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.text.TextUtils; -import android.util.Log; - -import com.android.car.CarLog; -import com.android.car.R; - -import dalvik.system.PathClassLoader; - -import java.lang.reflect.Method; - -/** - * Responsible for loading {@link InstrumentClusterRenderer} from separate android.car.cluster APK - * library. - */ -public class InstrumentClusterRendererLoader { - private final static String TAG = CarLog.TAG_CLUSTER; - - private final static String sCreateRendererMethod = "createRenderer"; - - /** - * Returns true if instrument cluster renderer installed. - */ - public static boolean isRendererAvailable(Context context) { - String packageName = getRendererPackageName(context); - if (TextUtils.isEmpty(packageName)) { - Log.d(TAG, "Instrument cluster renderer was not configured."); - return false; - } - - PackageManager packageManager = context.getPackageManager(); - try { - packageManager.getPackageInfo(packageName, 0); - return true; - } catch (NameNotFoundException e) { - Log.e(TAG, "Package not found: " + packageName); - return false; - } - } - - /** Dynamically load renderer APK and creates {@link InstrumentClusterRenderer}. */ - public static InstrumentClusterRenderer createRenderer(Context context) { - final String packageName = getRendererPackageName(context); - try { - return load(context, packageName, getRendererFactoryClassName(context)); - } catch (Exception e) { - Log.e(TAG, "Failed to load renderer class: " + e.getMessage(), e); - throw new RuntimeException(e); - } - } - - public static Context createRendererPackageContext(Context context) { - return createPackageContext(context, getRendererPackageName(context)); - } - - /** To prevent instantiation of a singleton class. */ - private InstrumentClusterRendererLoader() {} - - /** - * Creates package context for given package name. It is necessary to get renderer's context - * so appropriate resources will be loaded in the renderer code. - */ - private static Context createPackageContext(Context currentContext, String packageName) { - try { - return new PackageContextWrapper(currentContext.createPackageContext(packageName, - Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY)); - } catch (NameNotFoundException e) { - Log.e(TAG, "Package not found: " + packageName, e); - throw new IllegalStateException(e); - } - } - - /** Returns instrument cluster renderer or null if renderer package is not found */ - private static InstrumentClusterRenderer load(Context context, String packageName, - String factoryClassName) throws Exception { - PackageManager packageManager = context.getPackageManager(); - - assertSignature(context.getApplicationContext(), packageManager, packageName); - - String clusterRendererApk = packageManager.getApplicationInfo(packageName, 0).sourceDir; - - PathClassLoader pathClassLoader = new dalvik.system.PathClassLoader( - clusterRendererApk, ClassLoader.getSystemClassLoader()); - - Class<?> factoryClass = Class.forName(factoryClassName, true, pathClassLoader); - - Method createRendererMethod = factoryClass.getMethod(sCreateRendererMethod); - - Object rendererObject = createRendererMethod.invoke(null /* static */); - - if (rendererObject == null) { - Log.e(TAG, factoryClassName + "#" + sCreateRendererMethod + " returned null."); - throw new IllegalStateException(); - } - - if (!(rendererObject instanceof InstrumentClusterRenderer)) { - Log.e(TAG, factoryClassName + "#" + sCreateRendererMethod + " returned unexpected" - + " object of class " + rendererObject.getClass().getCanonicalName()); - throw new IllegalStateException(); - } - return (InstrumentClusterRenderer) rendererObject; - } - - /** Asserts that signature of a given alienPackageName matches with the current application. */ - private static void assertSignature(Context applicationContext, PackageManager packageManager, - String alienPackageName) { - String carServicePackage = applicationContext.getPackageName(); - int signatureMatch = packageManager.checkSignatures(carServicePackage, alienPackageName); - if (signatureMatch != PackageManager.SIGNATURE_MATCH) { - throw new IllegalArgumentException( - "Signature doesn't match for package: " + alienPackageName - + ", signatureMatch: " + signatureMatch); - } - } - - private static String getRendererFactoryClassName(Context context) { - return context.getString(R.string.instrumentClusterRendererFactoryClass); - } - - private static String getRendererPackageName(Context context) { - return context.getString(R.string.instrumentClusterRendererPackage); - } - - /** - * The context returned by Context.createPackageContext returns null in getApplicationContext - * method which causes some problems later, e.g. when CursorLoader class is being used. - */ - private static class PackageContextWrapper extends ContextWrapper { - PackageContextWrapper(Context packageContext) { - super(packageContext); - } - - @Override - public Context getApplicationContext() { - return this; - } - } -} diff --git a/service/src/com/android/car/cluster/InstrumentClusterService.java b/service/src/com/android/car/cluster/InstrumentClusterService.java index a6bd7f9fc3..425c65f146 100644 --- a/service/src/com/android/car/cluster/InstrumentClusterService.java +++ b/service/src/com/android/car/cluster/InstrumentClusterService.java @@ -15,15 +15,29 @@ */ package com.android.car.cluster; -import android.annotation.Nullable; import android.annotation.SystemApi; -import android.car.cluster.renderer.InstrumentClusterRenderer; -import android.car.cluster.renderer.NavigationRenderer; +import android.car.CarAppFocusManager; +import android.car.cluster.renderer.IInstrumentCluster; +import android.car.cluster.renderer.IInstrumentClusterNavigation; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; +import android.text.TextUtils; import android.util.Log; +import android.util.Pair; +import android.view.KeyEvent; +import com.android.car.AppFocusService; +import com.android.car.AppFocusService.FocusOwnershipListener; +import com.android.car.CarInputService; +import com.android.car.CarInputService.KeyEventListener; import com.android.car.CarLog; import com.android.car.CarServiceBase; +import com.android.car.CarServiceUtils; +import com.android.car.R; import java.io.PrintWriter; @@ -33,54 +47,160 @@ import java.io.PrintWriter; * @hide */ @SystemApi -public class InstrumentClusterService implements CarServiceBase { +public class InstrumentClusterService implements CarServiceBase, + FocusOwnershipListener, KeyEventListener { - private static final String TAG = CarLog.TAG_CLUSTER + "." - + InstrumentClusterService.class.getSimpleName(); + private static final String TAG = CarLog.TAG_CLUSTER; + private static final Boolean DBG = true; private final Context mContext; + private final AppFocusService mAppFocusService; + private final CarInputService mCarInputService; - private InstrumentClusterRenderer mRenderer; + private Pair<Integer, Integer> mNavContextOwner; - public InstrumentClusterService(Context context) { + private IInstrumentCluster mRendererService; + private boolean mRendererBound = false; + + private final ServiceConnection mRendererServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder binder) { + if (DBG) { + Log.d(TAG, "onServiceConnected, name: " + name + ", binder: " + binder); + } + mRendererService = IInstrumentCluster.Stub.asInterface(binder); + + if (mNavContextOwner != null) { + notifyNavContextOwnerChanged(mNavContextOwner.first, mNavContextOwner.second); + } + + try { + binder.linkToDeath(() -> CarServiceUtils.runOnMainSync(() -> { + Log.w(TAG, "Instrument cluster renderer died, trying to rebind"); + mRendererService = null; + // Try to rebind with instrument cluster. + mRendererBound = bindInstrumentClusterRendererService(); + }), 0); + } catch (RemoteException e) { + Log.e(TAG, e.getMessage(), e); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + Log.d(TAG, "onServiceDisconnected, name: " + name); + } + }; + + public InstrumentClusterService(Context context, AppFocusService appFocusService, + CarInputService carInputService) { mContext = context; + mAppFocusService = appFocusService; + mCarInputService = carInputService; } @Override public void init() { - Log.d(TAG, "init"); - - boolean rendererFound = InstrumentClusterRendererLoader.isRendererAvailable(mContext); - - if (rendererFound) { - mRenderer = InstrumentClusterRendererLoader.createRenderer(mContext); - Context packageContext = InstrumentClusterRendererLoader - .createRendererPackageContext(mContext); - mRenderer.onCreate(packageContext); - mRenderer.initialize(); - mRenderer.onStart(); + if (DBG) { + Log.d(TAG, "init"); } + + mAppFocusService.registerContextOwnerChangedListener(this /* FocusOwnershipListener */); + mCarInputService.setInstrumentClusterKeyListener(this /* KeyEventListener */); + mRendererBound = bindInstrumentClusterRendererService(); } @Override public void release() { - Log.d(TAG, "release"); - if (mRenderer != null) { - mRenderer.onStop(); - mRenderer = null; + if (DBG) { + Log.d(TAG, "release"); + } + + mAppFocusService.unregisterContextOwnerChangedListener(this); + if (mRendererBound) { + mContext.unbindService(mRendererServiceConnection); + mRendererBound = false; } } @Override public void dump(PrintWriter writer) { writer.println("**" + getClass().getSimpleName() + "**"); - writer.println("InstrumentClusterRenderer: " + mRenderer); - writer.println("NavigationRenderer: " - + (mRenderer != null ? mRenderer.getNavigationRenderer() : null)); + writer.println("bound with renderer: " + mRendererBound); + writer.println("renderer service: " + mRendererService); + } + + @Override + public void onFocusAcquired(int appType, int uid, int pid) { + if (appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) { + return; + } + + mNavContextOwner = new Pair<>(uid, pid); + + notifyNavContextOwnerChanged(uid, pid); + } + + @Override + public void onFocusAbandoned(int appType, int uid, int pid) { + if (appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) { + return; + } + + if (mNavContextOwner != null + && mNavContextOwner.first == uid + && mNavContextOwner.second == pid) { + notifyNavContextOwnerChanged(0, 0); // Reset focus ownership + } } - @Nullable - public NavigationRenderer getNavigationRenderer() { - return mRenderer != null ? mRenderer.getNavigationRenderer() : null; + private void notifyNavContextOwnerChanged(int uid, int pid) { + if (mRendererService != null) { + try { + mRendererService.setNavigationContextOwner(uid, pid); + } catch (RemoteException e) { + Log.e(TAG, "Failed to call setNavigationContextOwner", e); + } + } + } + + private boolean bindInstrumentClusterRendererService() { + String rendererService = mContext.getString(R.string.instrumentClusterRendererService); + if (TextUtils.isEmpty(rendererService)) { + Log.i(TAG, "Instrument cluster renderer was not configured"); + return false; + } + + Log.d(TAG, "bindInstrumentClusterRendererService, component: " + rendererService); + + Intent intent = new Intent(); + intent.setComponent(ComponentName.unflattenFromString(rendererService)); + // Explicitly start service as we do not use BIND_AUTO_CREATE flag to handle renderer crash. + mContext.startService(intent); + return mContext.bindService(intent, mRendererServiceConnection, Context.BIND_IMPORTANT); + } + + public IInstrumentClusterNavigation getNavigationService() { + try { + return mRendererService == null ? null : mRendererService.getNavigationService(); + } catch (RemoteException e) { + Log.e(TAG, "getNavigationServiceBinder" , e); + return null; + } + } + + @Override + public boolean onKeyEvent(KeyEvent event) { + if (DBG) { + Log.d(TAG, "InstrumentClusterService#onKeyEvent: " + event); + } + if (mRendererService != null) { + try { + mRendererService.onKeyEvent(event); + } catch (RemoteException e) { + Log.e(TAG, "onKeyEvent", e); + } + } + return true; } } diff --git a/service/src/com/android/car/hal/AudioHalService.java b/service/src/com/android/car/hal/AudioHalService.java index d60479d0b8..5470f9700a 100644 --- a/service/src/com/android/car/hal/AudioHalService.java +++ b/service/src/com/android/car/hal/AudioHalService.java @@ -47,6 +47,8 @@ import java.io.PrintWriter; import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.Set; public class AudioHalService extends HalServiceBase { public static final int VEHICLE_AUDIO_FOCUS_REQUEST_INVALID = -1; @@ -131,6 +133,8 @@ public class AudioHalService extends HalServiceBase { VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_AUX_AUDIO_FLAG; public static final int AUDIO_CONTEXT_SYSTEM_SOUND_FLAG = VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_SYSTEM_SOUND_FLAG; + public static final int AUDIO_CONTEXT_EXT_SOURCE_FLAG = + VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG; public interface AudioHalFocusListener { /** @@ -165,6 +169,8 @@ public class AudioHalService extends HalServiceBase { void onVolumeLimitChange(int streamNumber, int volume); } + private static final boolean DBG = true; + private final VehicleHal mVehicleHal; private AudioHalFocusListener mFocusListener; private AudioHalVolumeListener mVolumeListener; @@ -215,7 +221,7 @@ public class AudioHalService extends HalServiceBase { /** * Returns the volume limits of a stream in the form <min, max>. */ - public Pair<Integer, Integer> getStreamVolumeLimit(int stream) { + public synchronized Pair<Integer, Integer> getStreamVolumeLimit(int stream) { if (!isPropertySupportedLocked(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME)) { throw new IllegalStateException("VEHICLE_PROPERTY_AUDIO_VOLUME not supported"); } @@ -254,6 +260,10 @@ public class AudioHalService extends HalServiceBase { * Convert car audio manager stream type (usage) into audio context type. */ public static int logicalStreamToHalContextType(int logicalStream) { + return logicalStreamWithExtTypeToHalContextType(logicalStream, null); + } + + public static int logicalStreamWithExtTypeToHalContextType(int logicalStream, String extType) { switch (logicalStream) { case CarAudioManager.CAR_AUDIO_USAGE_RADIO: return VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG; @@ -275,6 +285,24 @@ public class AudioHalService extends HalServiceBase { return VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_SYSTEM_SOUND_FLAG; case CarAudioManager.CAR_AUDIO_USAGE_DEFAULT: return VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_UNKNOWN_FLAG; + case CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE: + if (extType != null) { + switch (extType) { + case CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_CD_DVD: + return AudioHalService.AUDIO_CONTEXT_CD_ROM_FLAG; + case CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_AUX_IN0: + case CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_AUX_IN1: + return AudioHalService.AUDIO_CONTEXT_AUX_AUDIO_FLAG; + default: + if (extType.startsWith("RADIO_")) { + return VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG; + } else { + return AudioHalService.AUDIO_CONTEXT_EXT_SOURCE_FLAG; + } + } + } else { // no external source specified. fall back to radio + return VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG; + } case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM: case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY: case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE: @@ -300,11 +328,11 @@ public class AudioHalService extends HalServiceBase { case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_VOICE_COMMAND_FLAG: return CarAudioManager.CAR_AUDIO_USAGE_VOICE_COMMAND; case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_AUX_AUDIO_FLAG: - return CarAudioManager.CAR_AUDIO_USAGE_MUSIC; + return CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE; case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_CALL_FLAG: return CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL; case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_CD_ROM_FLAG: - return CarAudioManager.CAR_AUDIO_USAGE_MUSIC; + return CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE; case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NOTIFICATION_FLAG: return CarAudioManager.CAR_AUDIO_USAGE_NOTIFICATION; case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG: @@ -315,6 +343,8 @@ public class AudioHalService extends HalServiceBase { return CarAudioManager.CAR_AUDIO_USAGE_SYSTEM_SOUND; case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_UNKNOWN_FLAG: return CarAudioManager.CAR_AUDIO_USAGE_DEFAULT; + case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG: + return CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE; default: Log.w(CarLog.TAG_AUDIO, "Unknown car context:" + carContext); return 0; @@ -435,6 +465,20 @@ public class AudioHalService extends HalServiceBase { return isPropertySupportedLocked(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT); } + public synchronized boolean isAudioVolumeMasterOnly() { + if (!isPropertySupportedLocked(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME)) { + throw new IllegalStateException("VEHICLE_PROPERTY_AUDIO_VOLUME not supported"); + } + VehiclePropConfig config = mProperties.get( + VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME); + if ((config.getConfigArray(1) & + VehicleAudioVolumeCapabilityFlag.VEHICLE_AUDIO_VOLUME_CAPABILITY_MASTER_VOLUME_ONLY) + != 0) { + return true; + } + return false; + } + /** * Get the current audio focus state. * @return 0: focusState, 1: streams, 2: externalFocus @@ -452,6 +496,87 @@ public class AudioHalService extends HalServiceBase { } } + public static class ExtRoutingSourceInfo { + /** Represents an external route which will not disable any physical stream in android side. + */ + public static final int NO_DISABLED_PHYSICAL_STREAM = -1; + + /** Bit position of this source in vhal */ + public final int bitPosition; + /** + * Physical stream replaced by this routing. will be {@link #NO_DISABLED_PHYSICAL_STREAM} + * if no physical stream for android is replaced by this routing. + */ + public final int physicalStreamNumber; + + public ExtRoutingSourceInfo(int bitPosition, int physycalStreamNumber) { + this.bitPosition = bitPosition; + this.physicalStreamNumber = physycalStreamNumber; + } + + @Override + public String toString() { + return "[bitPosition=" + bitPosition + ", physycalStreamNumber=" + + physicalStreamNumber + "]"; + } + } + + /** + * Get external audio routing types from AUDIO_EXT_ROUTING_HINT property. + * + * @return null if AUDIO_EXT_ROUTING_HINT is not supported. + */ + public Map<String, ExtRoutingSourceInfo> getExternalAudioRoutingTypes() { + VehiclePropConfig config; + synchronized (this) { + if (!isPropertySupportedLocked( + VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT)) { + return null; + } + config = mProperties.get( + VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT); + } + if (!config.hasConfigString()) { + Log.w(CarLog.TAG_HAL, "AUDIO_EXT_ROUTING_HINT with empty config string"); + return null; + } + Map<String, ExtRoutingSourceInfo> routingTypes = new HashMap<>(); + String configString = config.getConfigString(); + if (DBG) { + Log.i(CarLog.TAG_HAL, "AUDIO_EXT_ROUTING_HINT config string:" + configString); + } + String[] routes = configString.split(","); + for (String routeString : routes) { + String[] tokens = routeString.split(":"); + int bitPosition = 0; + String name = null; + int physicalStreamNumber = ExtRoutingSourceInfo.NO_DISABLED_PHYSICAL_STREAM; + if (tokens.length == 2) { + bitPosition = Integer.parseInt(tokens[0]); + name = tokens[1]; + } else if (tokens.length == 3) { + bitPosition = Integer.parseInt(tokens[0]); + name = tokens[1]; + physicalStreamNumber = Integer.parseInt(tokens[2]); + } else { + Log.w(CarLog.TAG_AUDIO, "VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT has wrong entry:" + + routeString); + continue; + } + routingTypes.put(name, new ExtRoutingSourceInfo(bitPosition, physicalStreamNumber)); + } + return routingTypes; + } + + public void setExternalRoutingSource(int[] externalRoutings) { + try { + mVehicleHal.getVehicleNetwork().setIntVectorProperty( + VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT, externalRoutings); + } catch (ServiceSpecificException e) { + Log.e(CarLog.TAG_AUDIO, "Cannot write to VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT", e); + } + } + private boolean isPropertySupportedLocked(int property) { VehiclePropConfig config = mProperties.get(property); return config != null; @@ -495,6 +620,7 @@ public class AudioHalService extends HalServiceBase { case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME: case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT: case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_HW_VARIANT: + case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT: case VehicleNetworkConsts.VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: mProperties.put(p.getProp(), p); break; diff --git a/service/src/com/android/car/hal/HvacHalService.java b/service/src/com/android/car/hal/HvacHalService.java index 98a15d1de3..1341506649 100644 --- a/service/src/com/android/car/hal/HvacHalService.java +++ b/service/src/com/android/car/hal/HvacHalService.java @@ -15,193 +15,21 @@ */ package com.android.car.hal; -import static com.android.car.hal.CarPropertyUtils.toCarPropertyValue; -import static com.android.car.hal.CarPropertyUtils.toVehiclePropValue; -import static java.lang.Integer.toHexString; - -import android.car.hardware.CarPropertyConfig; -import android.car.hardware.CarPropertyValue; -import android.car.hardware.hvac.CarHvacEvent; import android.car.hardware.hvac.CarHvacManager.HvacPropertyId; -import android.os.ServiceSpecificException; -import android.util.Log; -import android.util.SparseIntArray; -import com.android.car.CarLog; -import com.android.car.vehiclenetwork.VehicleNetwork; import com.android.car.vehiclenetwork.VehicleNetworkConsts; -import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfig; -import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValue; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -public class HvacHalService extends HalServiceBase { - private static final boolean DBG = true; - private static final String TAG = CarLog.TAG_HVAC + ".HvacHalService"; - private HvacHalListener mListener; - private final VehicleHal mVehicleHal; - - private final HashMap<Integer, CarPropertyConfig<?>> mProps = new HashMap<>(); - private final SparseIntArray mHalPropToValueType = new SparseIntArray(); - - public interface HvacHalListener { - void onPropertyChange(CarHvacEvent event); - void onError(int zone, int property); - } +public class HvacHalService extends PropertyHalServiceBase { + private static final boolean DBG = true; + private static final String TAG = "HvacHalService"; public HvacHalService(VehicleHal vehicleHal) { - mVehicleHal = vehicleHal; - if (DBG) { - Log.d(TAG, "started HvacHalService!"); - } - } - - public void setListener(HvacHalListener listener) { - synchronized (this) { - mListener = listener; - } - } - - public List<CarPropertyConfig> getHvacProperties() { - List<CarPropertyConfig> propList; - synchronized (mProps) { - propList = new ArrayList<>(mProps.values()); - } - return propList; - } - - public CarPropertyValue getHvacProperty(int hvacPropertyId, int areaId) { - int halProp = hvacToHalPropId(hvacPropertyId); - - VehiclePropValue value = null; - try { - VehiclePropValue valueRequest = VehiclePropValue.newBuilder() - .setProp(halProp) - .setZone(areaId) - .setValueType(mHalPropToValueType.get(halProp)) - .build(); - - value = mVehicleHal.getVehicleNetwork().getProperty(valueRequest); - } catch (ServiceSpecificException e) { - Log.e(CarLog.TAG_HVAC, "get, property not ready 0x" + toHexString(halProp), e); - } - - return value == null ? null : toCarPropertyValue(value, hvacPropertyId); - } - - public void setHvacProperty(CarPropertyValue prop) { - VehiclePropValue halProp = toVehiclePropValue(prop, hvacToHalPropId(prop.getPropertyId())); - try { - mVehicleHal.getVehicleNetwork().setProperty(halProp); - } catch (ServiceSpecificException e) { - Log.e(CarLog.TAG_HVAC, "set, property not ready 0x" + toHexString(halProp.getProp()), e); - throw new RuntimeException(e); - } - } - - @Override - public void init() { - if (DBG) { - Log.d(TAG, "init()"); - } - synchronized (mProps) { - // Subscribe to each of the HVAC properties - for (Integer prop : mProps.keySet()) { - mVehicleHal.subscribeProperty(this, prop, 0); - } - } - } - - @Override - public void release() { - if (DBG) { - Log.d(TAG, "release()"); - } - synchronized (mProps) { - for (Integer prop : mProps.keySet()) { - mVehicleHal.unsubscribeProperty(this, prop); - } - - // Clear the property list - mProps.clear(); - } - mListener = null; - } - - @Override - public synchronized List<VehiclePropConfig> takeSupportedProperties( - List<VehiclePropConfig> allProperties) { - List<VehiclePropConfig> taken = new LinkedList<>(); - - for (VehiclePropConfig p : allProperties) { - int hvacPropId; - try { - hvacPropId = halToHvacPropId(p.getProp()); - } catch (IllegalArgumentException e) { - continue; - } - CarPropertyConfig hvacConfig = CarPropertyUtils.toCarPropertyConfig(p, hvacPropId); - - taken.add(p); - mProps.put(p.getProp(), hvacConfig); - mHalPropToValueType.put(p.getProp(), p.getValueType()); - - if (DBG) { - Log.d(TAG, "takeSupportedProperties: " + toHexString(p.getProp())); - } - } - return taken; - } - - @Override - public void handleHalEvents(List<VehiclePropValue> values) { - HvacHalListener listener; - synchronized (this) { - listener = mListener; - } - if (listener != null) { - dispatchEventToListener(listener, values); - } - } - - private void dispatchEventToListener(HvacHalListener listener, List<VehiclePropValue> values) { - for (VehiclePropValue v : values) { - int prop = v.getProp(); - - int hvacPropId; - try { - hvacPropId = halToHvacPropId(prop); - } catch (IllegalArgumentException ex) { - Log.e(TAG, "Property is not supported: 0x" + toHexString(prop), ex); - continue; - } - - CarHvacEvent event; - CarPropertyValue<?> hvacProperty = toCarPropertyValue(v, hvacPropId); - event = new CarHvacEvent(CarHvacEvent.HVAC_EVENT_PROPERTY_CHANGE, hvacProperty); - - listener.onPropertyChange(event); - if (DBG) { - Log.d(TAG, "dispatchEventToListener event: " + event); - } - } - } - - @Override - public void dump(PrintWriter writer) { - writer.println("*HVAC HAL*"); - writer.println(" Properties available:"); - for (CarPropertyConfig prop : mProps.values()) { - writer.println(" " + prop.toString()); - } + super(vehicleHal, TAG, DBG); } // Convert the HVAC public API property ID to HAL property ID - private static int hvacToHalPropId(int hvacPropId) { + @Override + protected int managerToHalPropId(int hvacPropId) { switch (hvacPropId) { case HvacPropertyId.ZONED_FAN_SPEED_SETPOINT: return VehicleNetworkConsts.VEHICLE_PROPERTY_HVAC_FAN_SPEED; @@ -228,12 +56,13 @@ public class HvacHalService extends HalServiceBase { case HvacPropertyId.ZONED_MAX_DEFROST_ON: return VehicleNetworkConsts.VEHICLE_PROPERTY_HVAC_MAX_DEFROST_ON; default: - throw new IllegalArgumentException("hvacPropId " + hvacPropId + " is not supported"); + throw new IllegalArgumentException("hvacPropId " + hvacPropId + " not supported"); } } // Convert he HAL specific property ID to HVAC public API - private static int halToHvacPropId(int halPropId) { + @Override + protected int halToManagerPropId(int halPropId) { switch (halPropId) { case VehicleNetworkConsts.VEHICLE_PROPERTY_HVAC_FAN_SPEED: return HvacPropertyId.ZONED_FAN_SPEED_SETPOINT; @@ -260,7 +89,7 @@ public class HvacHalService extends HalServiceBase { case VehicleNetworkConsts.VEHICLE_PROPERTY_HVAC_MAX_DEFROST_ON: return HvacPropertyId.ZONED_MAX_DEFROST_ON; default: - throw new IllegalArgumentException("halPropId " + halPropId + " is not supported"); + throw new IllegalArgumentException("halPropId " + halPropId + " not supported"); } } } diff --git a/service/src/com/android/car/hal/PropertyHalServiceBase.java b/service/src/com/android/car/hal/PropertyHalServiceBase.java new file mode 100644 index 0000000000..bc93e56847 --- /dev/null +++ b/service/src/com/android/car/hal/PropertyHalServiceBase.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.car.hal; + +import static com.android.car.hal.CarPropertyUtils.toCarPropertyValue; +import static com.android.car.hal.CarPropertyUtils.toVehiclePropValue; +import static java.lang.Integer.toHexString; + +import android.car.hardware.CarPropertyConfig; +import android.car.hardware.CarPropertyValue; +import android.car.hardware.property.CarPropertyEvent; +import android.os.ServiceSpecificException; +import android.util.Log; +import android.util.SparseIntArray; + +import com.android.car.CarLog; +import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfig; +import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValue; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Common interface for HAL services that send Vehicle Properties back and forth via ICarProperty. + * Services that communicate by passing vehicle properties back and forth via ICarProperty should + * extend this class. + */ +public abstract class PropertyHalServiceBase extends HalServiceBase { + private final boolean mDbg; + private final SparseIntArray mHalPropToValueType = new SparseIntArray(); + private PropertyHalListener mListener; + private final ConcurrentHashMap<Integer, CarPropertyConfig<?>> mProps = + new ConcurrentHashMap<>(); + private final String mTag; + private final VehicleHal mVehicleHal; + + public interface PropertyHalListener { + void onPropertyChange(CarPropertyEvent event); + void onError(int zone, int property); + } + + public PropertyHalServiceBase(VehicleHal vehicleHal, String tag, boolean dbg) { + mVehicleHal = vehicleHal; + mTag = "PropertyHalServiceBase." + tag; + mDbg = dbg; + + if (mDbg) { + Log.d(mTag, "started PropertyHalServiceBase!"); + } + } + + public void setListener(PropertyHalListener listener) { + synchronized (this) { + mListener = listener; + } + } + + public List<CarPropertyConfig> getPropertyList() { + return new ArrayList<>(mProps.values()); + } + + public CarPropertyValue getProperty(int mgrPropId, int areaId) { + int halPropId = managerToHalPropId(mgrPropId); + + VehiclePropValue value = null; + try { + VehiclePropValue valueRequest = VehiclePropValue.newBuilder() + .setProp(halPropId) + .setZone(areaId) + .setValueType(mHalPropToValueType.get(halPropId)) + .build(); + + value = mVehicleHal.getVehicleNetwork().getProperty(valueRequest); + } catch (ServiceSpecificException e) { + Log.e(CarLog.TAG_PROPERTY, "get, property not ready 0x" + toHexString(halPropId), e); + } + + return value == null ? null : toCarPropertyValue(value, mgrPropId); + } + + public void setProperty(CarPropertyValue prop) { + int halPropId = managerToHalPropId(prop.getPropertyId()); + VehiclePropValue halProp = toVehiclePropValue(prop, halPropId); + try { + mVehicleHal.getVehicleNetwork().setProperty(halProp); + } catch (ServiceSpecificException e) { + Log.e(CarLog.TAG_PROPERTY, "set, property not ready 0x" + toHexString(halPropId), e); + throw new RuntimeException(e); + } + } + + @Override + public void init() { + if (mDbg) { + Log.d(mTag, "init()"); + } + // Subscribe to each of the properties + for (Integer prop : mProps.keySet()) { + mVehicleHal.subscribeProperty(this, prop, 0); + } + } + + @Override + public void release() { + if (mDbg) { + Log.d(mTag, "release()"); + } + + for (Integer prop : mProps.keySet()) { + mVehicleHal.unsubscribeProperty(this, prop); + } + + // Clear the property list + mProps.clear(); + + synchronized (this) { + mListener = null; + } + } + + @Override + public List<VehiclePropConfig> takeSupportedProperties( + List<VehiclePropConfig> allProperties) { + List<VehiclePropConfig> taken = new LinkedList<>(); + + for (VehiclePropConfig p : allProperties) { + int mgrPropId; + int halPropId; + + try { + // See if the property is handled by this HAL + mgrPropId = halToManagerPropId(p.getProp()); + halPropId = managerToHalPropId(mgrPropId); + if (halPropId != p.getProp()) { + throw new IllegalArgumentException("propId " + p.getProp() + " becomes " + + halPropId); + } + } catch (IllegalArgumentException e) { + continue; + } + CarPropertyConfig config = CarPropertyUtils.toCarPropertyConfig(p, mgrPropId); + + taken.add(p); + mProps.put(p.getProp(), config); + mHalPropToValueType.put(p.getProp(), p.getValueType()); + + if (mDbg) { + Log.d(mTag, "takeSupportedProperties: " + toHexString(p.getProp())); + } + } + return taken; + } + + @Override + public void handleHalEvents(List<VehiclePropValue> values) { + PropertyHalListener listener; + synchronized (this) { + listener = mListener; + } + if (listener != null) { + for (VehiclePropValue v : values) { + int prop = v.getProp(); + int mgrPropId; + + try { + mgrPropId = halToManagerPropId(prop); + } catch (IllegalArgumentException ex) { + Log.e(mTag, "Property is not supported: 0x" + toHexString(prop), ex); + continue; + } + + CarPropertyEvent event; + CarPropertyValue<?> propVal = toCarPropertyValue(v, mgrPropId); + event = new CarPropertyEvent(CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, + propVal); + + listener.onPropertyChange(event); + if (mDbg) { + Log.d(mTag, "handleHalEvents event: " + event); + } + } + } + } + + @Override + public void dump(PrintWriter writer) { + writer.println(mTag); + writer.println(" Properties available:"); + for (CarPropertyConfig prop : mProps.values()) { + writer.println(" " + prop.toString()); + } + } + + /** + * Convert manager property ID to Vehicle HAL property ID + */ + abstract protected int managerToHalPropId(int managerPropId); + + /** + * Convert Vehicle HAL property ID to manager property ID + */ + abstract protected int halToManagerPropId(int halPropId); +} diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java index 181252417f..305f9aedf3 100644 --- a/service/src/com/android/car/hal/VehicleHal.java +++ b/service/src/com/android/car/hal/VehicleHal.java @@ -247,9 +247,9 @@ public class VehicleHal implements VehicleNetworkListener { } public static boolean isPropertySubscribable(VehiclePropConfig config) { - if (config.hasAccess() & VehiclePropAccess.VEHICLE_PROP_ACCESS_READ == 0 || - config.getChangeMode() == - VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_STATIC) { + if ((config.getAccess() & VehiclePropAccess.VEHICLE_PROP_ACCESS_READ) == 0 || + (config.getChangeMode() == + VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_STATIC)) { return false; } return true; diff --git a/service/src/com/android/car/pm/ActivityBlockingActivity.java b/service/src/com/android/car/pm/ActivityBlockingActivity.java new file mode 100644 index 0000000000..fc6d9976a4 --- /dev/null +++ b/service/src/com/android/car/pm/ActivityBlockingActivity.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.car.pm; + +import android.app.Activity; +import android.car.Car; +import android.car.CarNotConnectedException; +import android.car.content.pm.CarPackageManager; +import android.content.ComponentName; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.util.Log; +import android.view.View; +import android.widget.Button; + +import com.android.car.CarLog; +import com.android.car.R; + +/** + * Default activity that will be launched when the current foreground activity is not allowed. + * Additional information on blocked Activity will be passed as extra in Intent + * via {@link #INTENT_KEY_BLOCKED_ACTIVITY} key. * + */ +public class ActivityBlockingActivity extends Activity { + + public static final String INTENT_KEY_BLOCKED_ACTIVITY = "blocked_activity"; + + private static final long AUTO_DISMISS_TIME_MS = 3000; + + private Handler mHandler; + + private Button mExitButton; + + private Car mCar; + + private boolean mExitRequested; + + private final Runnable mFinishRunnable = () -> handleFinish(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_blocking); + mHandler = new Handler(Looper.getMainLooper()); + mExitButton = (Button) findViewById(R.id.botton_exit_now); + mExitButton.setOnClickListener((View v) -> handleFinish()); + mCar = Car.createCar(this, new ServiceConnection() { + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (mExitRequested) { + handleFinish(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + } + + }); + mCar.connect(); + } + + @Override + protected void onResume() { + super.onResume(); + mHandler.postDelayed(mFinishRunnable, AUTO_DISMISS_TIME_MS); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mHandler.removeCallbacks(mFinishRunnable); + } + + private void handleFinish() { + if (!mCar.isConnected()) { + mExitRequested = true; + return; + } + if (isFinishing()) { + return; + } + try { + CarPackageManager carPm = (CarPackageManager) mCar.getCarManager(Car.PACKAGE_SERVICE); + + // finish itself only when it will not lead into another blocking + if (carPm.isActivityBackedBySafeActivity(getComponentName())) { + finish(); + return; + } + // back activity is not safe either. Now try home + Intent homeIntent = new Intent(Intent.ACTION_MAIN); + homeIntent.addCategory(Intent.CATEGORY_HOME); + PackageManager pm = getPackageManager(); + ComponentName homeComponent = homeIntent.resolveActivity(pm); + if (carPm.isActivityAllowedWhileDriving(homeComponent.getPackageName(), + homeComponent.getClassName())) { + startActivity(homeIntent); + finish(); + return; + } else { + Log.w(CarLog.TAG_AM, "Home activity is not in white list. Keep blocking activity. " + + ", Home Activity:" + homeComponent); + } + } catch (CarNotConnectedException e) { + Log.w(CarLog.TAG_AM, "Car service not avaiable, will finish", e); + finish(); + } + } +} diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java index 881650271b..e9298d472d 100644 --- a/service/src/com/android/car/pm/CarPackageManagerService.java +++ b/service/src/com/android/car/pm/CarPackageManagerService.java @@ -15,12 +15,17 @@ */ package com.android.car.pm; +import android.app.ActivityManager.StackInfo; import android.car.Car; import android.car.content.pm.AppBlockingPackageInfo; import android.car.content.pm.CarAppBlockingPolicy; import android.car.content.pm.CarAppBlockingPolicyService; import android.car.content.pm.CarPackageManager; import android.car.content.pm.ICarPackageManager; +import android.car.hardware.CarSensorEvent; +import android.car.hardware.CarSensorManager; +import android.car.hardware.ICarSensorEventListener; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; @@ -29,6 +34,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ServiceInfo; import android.content.pm.ResolveInfo; import android.content.pm.Signature; +import android.content.res.Resources; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -38,8 +44,12 @@ import android.util.Log; import android.util.Pair; import com.android.car.CarLog; +import com.android.car.CarSensorService; import com.android.car.CarServiceBase; import com.android.car.CarServiceUtils; +import com.android.car.R; +import com.android.car.SystemActivityMonitoringService; +import com.android.car.SystemActivityMonitoringService.TopTaskInfoContainer; import com.android.car.pm.CarAppMetadataReader.CarAppMetadataInfo; import com.android.internal.annotations.GuardedBy; @@ -49,14 +59,18 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; +import java.util.Set; //TODO monitor app installing and refresh policy public class CarPackageManagerService extends ICarPackageManager.Stub implements CarServiceBase { static final boolean DBG_POLICY_SET = true; static final boolean DBG_POLICY_CHECK = false; + static final boolean DBG_POLICY_ENFORCEMENT = true; private final Context mContext; + private final SystemActivityMonitoringService mSystemActivityMonitoringService; + private final CarSensorService mSensorService; private final PackageManager mPackageManager; private final HandlerThread mHandlerThread; @@ -79,12 +93,25 @@ public class CarPackageManagerService extends ICarPackageManager.Stub implements @GuardedBy("this") private final LinkedList<CarAppBlockingPolicy> mWaitingPolicies = new LinkedList<>(); - public CarPackageManagerService(Context context) { + private final boolean mEnableActivityBlocking; + private final ComponentName mActivityBlockingActivity; + + private final ActivityLaunchListener mActivityLaunchListener = new ActivityLaunchListener(); + private final SensorListener mDrivingStateListener = new SensorListener(); + + public CarPackageManagerService(Context context, CarSensorService sensorService, + SystemActivityMonitoringService systemActivityMonitoringService) { mContext = context; + mSensorService = sensorService; + mSystemActivityMonitoringService = systemActivityMonitoringService; mPackageManager = mContext.getPackageManager(); mHandlerThread = new HandlerThread(CarLog.TAG_PACKAGE); mHandlerThread.start(); mHandler = new PackageHandler(mHandlerThread.getLooper()); + Resources res = context.getResources(); + mEnableActivityBlocking = res.getBoolean(R.bool.enableActivityBlockingForSafety); + String blockingActivity = res.getString(R.string.activityBlockingActivity); + mActivityBlockingActivity = ComponentName.unflattenFromString(blockingActivity); } @Override @@ -163,6 +190,25 @@ public class CarPackageManagerService extends ICarPackageManager.Stub implements return false; } + @Override + public boolean isActivityBackedBySafeActivity(ComponentName activityName) { + if (!mEnableActivityBlocking || !mDrivingStateListener.isRestricted()) { + return true; + } + StackInfo info = mSystemActivityMonitoringService.getFocusedStackForTopActivity( + activityName); + if (info == null) { // not top in focused stack + return true; + } + if (info.taskNames.length <= 1) { // nothing below this. + return false; + } + ComponentName activityBehind = ComponentName.unflattenFromString( + info.taskNames[info.taskNames.length - 2]); + return isActivityAllowedWhileDriving(activityBehind.getPackageName(), + activityBehind.getClassName()); + } + public Looper getLooper() { return mHandlerThread.getLooper(); } @@ -221,6 +267,13 @@ public class CarPackageManagerService extends ICarPackageManager.Stub implements mReleased = false; mHandler.requestInit(); } + if (mEnableActivityBlocking) { + mSensorService.registerOrUpdateSensorListener( + CarSensorManager.SENSOR_TYPE_DRIVING_STATUS, 0, mDrivingStateListener); + mDrivingStateListener.resetState(); + mSystemActivityMonitoringService.registerActivityLaunchListener( + mActivityLaunchListener); + } } @Override @@ -238,6 +291,11 @@ public class CarPackageManagerService extends ICarPackageManager.Stub implements } wakeupClientsWaitingForPolicySetitngLocked(); } + if (mEnableActivityBlocking) { + mSensorService.unregisterSensorListener(CarSensorManager.SENSOR_TYPE_DRIVING_STATUS, + mDrivingStateListener); + mSystemActivityMonitoringService.registerActivityLaunchListener(null); + } } // run from HandlerThread @@ -256,11 +314,10 @@ public class CarPackageManagerService extends ICarPackageManager.Stub implements } private void doSetPolicy() { - //TODO should set policy to AMS - // waiting for framework API to be ready synchronized (this) { wakeupClientsWaitingForPolicySetitngLocked(); } + blockTopActivitiesIfNecessary(); } private void doUpdatePolicy(String packageName, CarAppBlockingPolicy policy, int flags) { @@ -293,6 +350,7 @@ public class CarPackageManagerService extends ICarPackageManager.Stub implements Log.i(CarLog.TAG_PACKAGE, "policy set:" + dumpPoliciesLocked(false)); } } + blockTopActivitiesIfNecessary(); } private AppBlockingPackageInfoWrapper[] verifyList(AppBlockingPackageInfo[] list) { @@ -369,22 +427,76 @@ public class CarPackageManagerService extends ICarPackageManager.Stub implements return false; } + /** + * Return list of whitelist including default activity. Key is package name while + * value is list of activities. If list is empty, whole activities in the package + * are whitelisted. + * @return + */ + private HashMap<String, Set<String>> parseConfigWhitelist() { + HashMap<String, Set<String>> packageToActivityMap = new HashMap<>(); + Set<String> defaultActivity = new ArraySet<>(); + defaultActivity.add(mActivityBlockingActivity.getClassName()); + packageToActivityMap.put(mActivityBlockingActivity.getPackageName(), defaultActivity); + Resources res = mContext.getResources(); + String whitelist = res.getString(R.string.defauiltActivityWhitelist); + String[] entries = whitelist.split(","); + for (String entry : entries) { + String[] packageActivityPair = entry.split("/"); + Set<String> activities = packageToActivityMap.get(packageActivityPair[0]); + boolean newPackage = false; + if (activities == null) { + activities = new ArraySet<>(); + newPackage = true; + packageToActivityMap.put(packageActivityPair[0], activities); + } + if (packageActivityPair.length == 1) { // whole package + activities.clear(); + } else if (packageActivityPair.length == 2){ + // add class name only when the whole package is not whitelisted. + if (newPackage || (activities.size() > 0)) { + activities.add(packageActivityPair[1]); + } + } + } + return packageToActivityMap; + } + private void generateSystemWhitelists() { HashMap<String, AppBlockingPackageInfoWrapper> systemWhitelists = new HashMap<>(); + HashMap<String, Set<String>> configWhitelist = parseConfigWhitelist(); // trust all system apps for services and trust all activities with car app meta-data. List<PackageInfo> packages = mPackageManager.getInstalledPackages(0); for (PackageInfo info : packages) { if (info.applicationInfo != null && (info.applicationInfo.isSystemApp() || info.applicationInfo.isUpdatedSystemApp())) { - CarAppMetadataInfo metadataInfo = CarAppMetadataReader.parseMetadata(mContext, - info.packageName); int flags = AppBlockingPackageInfo.FLAG_SYSTEM_APP; - String[] activities = null; - if (metadataInfo != null) { - if (metadataInfo.useAllActivities) { + Set<String> configActivitiesForPackage = + configWhitelist.get(info.packageName); + if (configActivitiesForPackage != null) { + if(configActivitiesForPackage.size() == 0) { flags |= AppBlockingPackageInfo.FLAG_WHOLE_ACTIVITY; - } else { - activities = metadataInfo.activities; + } + } else { + configActivitiesForPackage = new ArraySet<>(); + } + String[] activities = null; + // Go through meta data if whole activities are allowed already + if ((flags & AppBlockingPackageInfo.FLAG_WHOLE_ACTIVITY) == 0) { + CarAppMetadataInfo metadataInfo = CarAppMetadataReader.parseMetadata(mContext, + info.packageName); + if (metadataInfo != null) { + if (metadataInfo.useAllActivities) { + flags |= AppBlockingPackageInfo.FLAG_WHOLE_ACTIVITY; + } else if(metadataInfo.activities != null) { + for (String activity : metadataInfo.activities) { + configActivitiesForPackage.add(activity); + } + } + } + if (configActivitiesForPackage.size() > 0) { + activities = configActivitiesForPackage.toArray( + new String[configActivitiesForPackage.size()]); } } AppBlockingPackageInfo appBlockingInfo = new AppBlockingPackageInfo( @@ -473,6 +585,8 @@ public class CarPackageManagerService extends ICarPackageManager.Stub implements public void dump(PrintWriter writer) { synchronized (this) { writer.println("*PackageManagementService*"); + writer.println("mEnableActivityBlocking:" + mEnableActivityBlocking); + writer.println("ActivityRestricted:" + mDrivingStateListener.isRestricted()); writer.print(dumpPoliciesLocked(true)); } } @@ -506,6 +620,45 @@ public class CarPackageManagerService extends ICarPackageManager.Stub implements return sb.toString(); } + private void blockTopActivityIfNecessary(TopTaskInfoContainer topTask) { + boolean restricted = mDrivingStateListener.isRestricted(); + if (!restricted) { + return; + } + doBlockTopActivityIfNotAllowed(topTask); + } + + private void doBlockTopActivityIfNotAllowed(TopTaskInfoContainer topTask) { + boolean allowed = isActivityAllowedWhileDriving( + topTask.topActivity.getPackageName(), + topTask.topActivity.getClassName()); + if (DBG_POLICY_ENFORCEMENT) { + Log.i(CarLog.TAG_PACKAGE, "new activity:" + topTask.toString() + " allowed:" + allowed); + } + if (!allowed) { + Log.i(CarLog.TAG_PACKAGE, "Current activity " + topTask.topActivity + + " not allowed, will block, number of tasks in stack:" + + topTask.stackInfo.taskIds.length); + Intent newActivityIntent = new Intent(); + newActivityIntent.setComponent(mActivityBlockingActivity); + newActivityIntent.putExtra( + ActivityBlockingActivity.INTENT_KEY_BLOCKED_ACTIVITY, + topTask.topActivity.flattenToString()); + mSystemActivityMonitoringService.blockActivity(topTask, newActivityIntent); + } + } + + private void blockTopActivitiesIfNecessary() { + boolean restricted = mDrivingStateListener.isRestricted(); + if (!restricted) { + return; + } + List<TopTaskInfoContainer> topTasks = mSystemActivityMonitoringService.getTopTasks(); + for (TopTaskInfoContainer topTask : topTasks) { + doBlockTopActivityIfNotAllowed(topTask); + } + } + /** * Reading policy and setting policy can take time. Run it in a separate handler thread. */ @@ -643,4 +796,46 @@ public class CarPackageManagerService extends ICarPackageManager.Stub implements } } } + + private class ActivityLaunchListener + implements SystemActivityMonitoringService.ActivityLaunchListener { + @Override + public void onActivityLaunch(TopTaskInfoContainer topTask) { + blockTopActivityIfNecessary(topTask); + } + } + + private class SensorListener extends ICarSensorEventListener.Stub { + private int mLatestDrivingState; + + private void resetState() { + CarSensorEvent lastEvent = mSensorService.getLatestSensorEvent( + CarSensorManager.SENSOR_TYPE_DRIVING_STATUS); + boolean shouldBlock = false; + synchronized (this) { + if (lastEvent == null) { + // When driving status is not available yet, do not block. + // This happens during bootup. + mLatestDrivingState = CarSensorEvent.DRIVE_STATUS_UNRESTRICTED; + } else { + mLatestDrivingState = lastEvent.intValues[0]; + } + if (mLatestDrivingState != CarSensorEvent.DRIVE_STATUS_UNRESTRICTED) { + shouldBlock = true; + } + } + if (shouldBlock) { + blockTopActivitiesIfNecessary(); + } + } + + private synchronized boolean isRestricted() { + return mLatestDrivingState != CarSensorEvent.DRIVE_STATUS_UNRESTRICTED; + } + + @Override + public void onSensorChanged(List<CarSensorEvent> events) { + resetState(); + } + } } diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/hvac_test.xml b/tests/EmbeddedKitchenSinkApp/res/layout/hvac_test.xml index f5fb340cd5..063a6f9edb 100644 --- a/tests/EmbeddedKitchenSinkApp/res/layout/hvac_test.xml +++ b/tests/EmbeddedKitchenSinkApp/res/layout/hvac_test.xml @@ -17,7 +17,7 @@ android:gravity="center" android:layout_height="match_parent" android:layout_width="match_parent" - android:layout_marginTop="60dp" + android:layout_marginTop="160dp" android:orientation="vertical"> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml b/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml index a92f608356..71c7fca195 100644 --- a/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml +++ b/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml @@ -17,8 +17,8 @@ android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginTop="100dp" - android:layout_marginLeft="40dp"> + android:layout_marginTop="160dp" + android:layout_marginStart="40dp"> <LinearLayout android:orientation="vertical" diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java index ce9e43e5aa..d22de6b02f 100644 --- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java +++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java @@ -24,7 +24,7 @@ import android.content.pm.PackageManager; import android.graphics.Color; import android.os.Bundle; import android.support.car.Car; -import android.support.car.CarAppContextManager; +import android.support.car.CarAppFocusManager; import android.support.car.CarNotConnectedException; import android.support.car.CarNotSupportedException; import android.support.car.ServiceConnectionListener; @@ -34,7 +34,6 @@ import android.support.car.app.menu.CarMenuCallbacks; import android.support.car.app.menu.RootMenu; import android.support.car.hardware.CarSensorEvent; import android.support.car.hardware.CarSensorManager; -import android.support.car.navigation.CarNavigationManager; import android.util.Log; import com.google.android.car.kitchensink.audio.AudioTestFragment; @@ -74,9 +73,7 @@ public class KitchenSinkActivity extends CarDrawerActivity { private CarCameraManager mCameraManager; private CarHvacManager mHvacManager; private CarSensorManager mCarSensorManager; - private CarNavigationManager mCarNavigationManager; - private CarAppContextManager mCarAppContextManager; - + private CarAppFocusManager mCarAppFocusManager; private AudioTestFragment mAudioTestFragment; private RadioTestFragment mRadioTestFragment; @@ -183,14 +180,12 @@ public class KitchenSinkActivity extends CarDrawerActivity { mCameraManager = (CarCameraManager) mCarApi.getCarManager(android.car.Car .CAMERA_SERVICE); mHvacManager = (CarHvacManager) mCarApi.getCarManager(android.car.Car.HVAC_SERVICE); - mCarNavigationManager = (CarNavigationManager) mCarApi.getCarManager( - android.car.Car.CAR_NAVIGATION_SERVICE); mCarSensorManager = (CarSensorManager) mCarApi.getCarManager(Car.SENSOR_SERVICE); mCarSensorManager.registerListener(mListener, CarSensorManager.SENSOR_TYPE_DRIVING_STATUS, CarSensorManager.SENSOR_RATE_NORMAL); - mCarAppContextManager = - (CarAppContextManager) mCarApi.getCarManager(Car.APP_CONTEXT_SERVICE); + mCarAppFocusManager = + (CarAppFocusManager) mCarApi.getCarManager(Car.APP_FOCUS_SERVICE); } catch (CarNotConnectedException e) { Log.e(TAG, "Car is not connected!", e); } catch (CarNotSupportedException e) { @@ -288,8 +283,6 @@ public class KitchenSinkActivity extends CarDrawerActivity { } else if (id.equals(MENU_CLUSTER)) { if (mInstrumentClusterFragment == null) { mInstrumentClusterFragment = new InstrumentClusterFragment(); - mInstrumentClusterFragment.setCarNavigationManager(mCarNavigationManager); - mInstrumentClusterFragment.setCarAppContextManager(mCarAppContextManager); } setContentFragment(mInstrumentClusterFragment); } else if (id.equals(MENU_INPUT_TEST)) { diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java index bed33e2a8b..a040533187 100644 --- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java +++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java @@ -17,9 +17,9 @@ package com.google.android.car.kitchensink.audio; import android.car.Car; -import android.car.CarAppContextManager; -import android.car.CarAppContextManager.AppContextChangeListener; -import android.car.CarAppContextManager.AppContextOwnershipChangeListener; +import android.car.CarAppFocusManager; +import android.car.CarAppFocusManager.AppFocusChangeListener; +import android.car.CarAppFocusManager.AppFocusOwnershipChangeListener; import android.car.CarNotConnectedException; import android.car.media.CarAudioManager; import android.content.ComponentName; @@ -88,7 +88,7 @@ public class AudioTestFragment extends Fragment { private Context mContext; private Car mCar; - private CarAppContextManager mAppContextManager; + private CarAppFocusManager mAppFocusManager; private CarAudioManager mCarAudioManager; private AudioAttributes mMusicAudioAttrib; private AudioAttributes mNavAudioAttrib; @@ -119,10 +119,10 @@ public class AudioTestFragment extends Fragment { } }; - private final AppContextOwnershipChangeListener mOwnershipListener = - new AppContextOwnershipChangeListener() { + private final AppFocusOwnershipChangeListener mOwnershipListener = + new AppFocusOwnershipChangeListener() { @Override - public void onAppContextOwnershipLoss(int context) { + public void onAppFocusOwnershipLoss(int focus) { } }; @@ -133,20 +133,23 @@ public class AudioTestFragment extends Fragment { @Override public void onServiceConnected(ComponentName name, IBinder service) { try { - mAppContextManager = - (CarAppContextManager) mCar.getCarManager(Car.APP_CONTEXT_SERVICE); + mAppFocusManager = + (CarAppFocusManager) mCar.getCarManager(Car.APP_FOCUS_SERVICE); } catch (CarNotConnectedException e) { - throw new RuntimeException("Failed to create app context manager", e); + throw new RuntimeException("Failed to create app focus manager", e); } try { - mAppContextManager.registerContextListener(new AppContextChangeListener() { + AppFocusChangeListener listener = new AppFocusChangeListener() { @Override - public void onAppContextChange(int activeContexts) { + public void onAppFocusChange(int appType, boolean active) { } - }, CarAppContextManager.APP_CONTEXT_NAVIGATION | - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); + }; + mAppFocusManager.registerFocusListener(listener, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + mAppFocusManager.registerFocusListener(listener, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); } catch (CarNotConnectedException e) { - Log.e(TAG, "Failed to register context listener", e); + Log.e(TAG, "Failed to register focus listener", e); } try { mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE); @@ -250,7 +253,7 @@ public class AudioTestFragment extends Fragment { mNavPlayOnce.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - if (mAppContextManager == null) { + if (mAppFocusManager == null) { return; } if (DBG) { @@ -258,10 +261,10 @@ public class AudioTestFragment extends Fragment { } if (!mNavGuidancePlayer.isPlaying()) { try { - mAppContextManager.setActiveContexts(mOwnershipListener, - CarAppContextManager.APP_CONTEXT_NAVIGATION); + mAppFocusManager.requestAppFocus(mOwnershipListener, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); } catch (CarNotConnectedException e) { - Log.e(TAG, "Failed to set active context", e); + Log.e(TAG, "Failed to set active focus", e); } mNavGuidancePlayer.start(true, false, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, @@ -269,10 +272,10 @@ public class AudioTestFragment extends Fragment { @Override public void onCompletion() { try { - mAppContextManager.resetActiveContexts( - CarAppContextManager.APP_CONTEXT_NAVIGATION); + mAppFocusManager.abandonAppFocus(mOwnershipListener, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); } catch (CarNotConnectedException e) { - Log.e(TAG, "Failed to reset active context", e); + Log.e(TAG, "Failed to reset active focus", e); } } }); @@ -283,17 +286,17 @@ public class AudioTestFragment extends Fragment { mVrPlayOnce.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - if (mAppContextManager == null) { + if (mAppFocusManager == null) { return; } if (DBG) { Log.i(TAG, "VR start"); } try { - mAppContextManager.setActiveContexts(mOwnershipListener, - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); + mAppFocusManager.requestAppFocus(mOwnershipListener, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); } catch (CarNotConnectedException e) { - Log.e(TAG, "Failed to set active context", e); + Log.e(TAG, "Failed to set active focus", e); } if (!mVrPlayer.isPlaying()) { mVrPlayer.start(true, false, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, @@ -301,10 +304,10 @@ public class AudioTestFragment extends Fragment { @Override public void onCompletion() { try { - mAppContextManager.resetActiveContexts( - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); + mAppFocusManager.abandonAppFocus(mOwnershipListener, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); } catch (CarNotConnectedException e) { - Log.e(TAG, "Failed to reset active context", e); + Log.e(TAG, "Failed to reset active focus", e); } } }); @@ -464,18 +467,17 @@ public class AudioTestFragment extends Fragment { mAudioFocusHandler.release(); mAudioFocusHandler = null; } - if (mAppContextManager != null) { + if (mAppFocusManager != null) { try { - mAppContextManager.resetActiveContexts(CarAppContextManager.APP_CONTEXT_NAVIGATION | - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); + mAppFocusManager.abandonAppFocus(mOwnershipListener); } catch (CarNotConnectedException e) { - Log.e(TAG, "Failed to reset active context", e); + Log.e(TAG, "Failed to reset active focus", e); } } } private void handleNavStart() { - if (mAppContextManager == null) { + if (mAppFocusManager == null) { return; } if (mCarAudioManager == null) { @@ -485,17 +487,17 @@ public class AudioTestFragment extends Fragment { Log.i(TAG, "Nav start"); } try { - mAppContextManager.setActiveContexts(mOwnershipListener, - CarAppContextManager.APP_CONTEXT_NAVIGATION); + mAppFocusManager.requestAppFocus(mOwnershipListener, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); } catch (CarNotConnectedException e) { - Log.e(TAG, "Failed to set active context", e); + Log.e(TAG, "Failed to set active focus", e); } mCarAudioManager.requestAudioFocus(mNavFocusListener, mNavAudioAttrib, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); } private void handleNavEnd() { - if (mAppContextManager == null) { + if (mAppFocusManager == null) { return; } if (mCarAudioManager == null) { @@ -505,16 +507,16 @@ public class AudioTestFragment extends Fragment { Log.i(TAG, "Nav end"); } try { - mAppContextManager.resetActiveContexts( - CarAppContextManager.APP_CONTEXT_NAVIGATION); + mAppFocusManager.abandonAppFocus(mOwnershipListener, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); } catch (CarNotConnectedException e) { - Log.e(TAG, "Failed to reset active context", e); + Log.e(TAG, "Failed to reset active focus", e); } mCarAudioManager.abandonAudioFocus(mNavFocusListener, mNavAudioAttrib); } private void handleVrStart() { - if (mAppContextManager == null) { + if (mAppFocusManager == null) { return; } if (mCarAudioManager == null) { @@ -524,17 +526,17 @@ public class AudioTestFragment extends Fragment { Log.i(TAG, "VR start"); } try { - mAppContextManager.setActiveContexts(mOwnershipListener, - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); + mAppFocusManager.requestAppFocus(mOwnershipListener, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); } catch (CarNotConnectedException e) { - Log.e(TAG, "Failed to set active context", e); + Log.e(TAG, "Failed to set active focus", e); } mCarAudioManager.requestAudioFocus(mVrFocusListener, mVrAudioAttrib, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0); } private void handleVrEnd() { - if (mAppContextManager == null) { + if (mAppFocusManager == null) { return; } if (mCarAudioManager == null) { @@ -544,10 +546,10 @@ public class AudioTestFragment extends Fragment { Log.i(TAG, "VR end"); } try { - mAppContextManager.resetActiveContexts( - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); + mAppFocusManager.abandonAppFocus(mOwnershipListener, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); } catch (CarNotConnectedException e) { - Log.e(TAG, "Failed to reset active context", e); + Log.e(TAG, "Failed to reset active focus", e); } mCarAudioManager.abandonAudioFocus(mVrFocusListener, mVrAudioAttrib); } diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java index 421bb422bc..a24c122bc9 100644 --- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java +++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java @@ -16,13 +16,17 @@ package com.google.android.car.kitchensink.cluster; import android.app.AlertDialog; +import android.content.ComponentName; import android.os.Bundle; import android.support.annotation.Nullable; -import android.support.car.CarAppContextManager; -import android.support.car.CarAppContextManager.AppContextChangeListener; -import android.support.car.CarAppContextManager.AppContextOwnershipChangeListener; +import android.support.car.Car; +import android.support.car.CarAppFocusManager; +import android.support.car.CarAppFocusManager.AppFocusChangeListener; +import android.support.car.CarAppFocusManager.AppFocusOwnershipChangeListener; import android.support.car.CarNotConnectedException; -import android.support.car.navigation.CarNavigationManager; +import android.support.car.CarNotSupportedException; +import android.support.car.ServiceConnectionListener; +import android.support.car.navigation.CarNavigationStatusManager; import android.support.v4.app.Fragment; import android.util.Log; import android.view.LayoutInflater; @@ -37,15 +41,51 @@ import com.google.android.car.kitchensink.R; public class InstrumentClusterFragment extends Fragment { private static final String TAG = InstrumentClusterFragment.class.getSimpleName(); - private CarNavigationManager mCarNavigationManager; - private CarAppContextManager mCarAppContextManager; + private CarNavigationStatusManager mCarNavigationStatusManager; + private CarAppFocusManager mCarAppFocusManager; + private Car mCarApi; - public void setCarNavigationManager(CarNavigationManager carNavigationManager) { - mCarNavigationManager = carNavigationManager; - } + private final ServiceConnectionListener mServiceConnectionListener = + new ServiceConnectionListener() { + @Override + public void onServiceConnected(ComponentName name) { + Log.d(TAG, "Connected to Car Service"); + try { + mCarNavigationStatusManager = (CarNavigationStatusManager) mCarApi.getCarManager( + android.car.Car.CAR_NAVIGATION_SERVICE); + mCarAppFocusManager = + (CarAppFocusManager) mCarApi.getCarManager(Car.APP_FOCUS_SERVICE); + } catch (CarNotConnectedException e) { + Log.e(TAG, "Car is not connected!", e); + } catch (CarNotSupportedException e) { + Log.e(TAG, "Car is not supported!", e); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + Log.d(TAG, "Disconnect from Car Service"); + } - public void setCarAppContextManager(CarAppContextManager carAppContextManager) { - mCarAppContextManager = carAppContextManager; + @Override + public void onServiceSuspended(int cause) { + Log.d(TAG, "Car Service connection suspended"); + } + + @Override + public void onServiceConnectionFailed(int cause) { + Log.d(TAG, "Car Service connection failed"); + } + }; + + private void initCarApi() { + if (mCarApi != null && mCarApi.isConnected()) { + mCarApi.disconnect(); + mCarApi = null; + } + + mCarApi = Car.createCar(getContext(), mServiceConnectionListener); + mCarApi.connect(); } @Nullable @@ -57,62 +97,75 @@ public class InstrumentClusterFragment extends Fragment { view.findViewById(R.id.cluster_start_button).setOnClickListener(v -> initCluster()); view.findViewById(R.id.cluster_turn_left_button).setOnClickListener(v -> turnLeft()); - return super.onCreateView(inflater, container, savedInstanceState); + return view; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + initCarApi(); + + super.onCreate(savedInstanceState); } private void turnLeft() { try { - mCarNavigationManager.sendNavigationTurnEvent(CarNavigationManager.TURN_TURN, - "Huff Ave", 90, -1, null, CarNavigationManager.TURN_SIDE_LEFT); - mCarNavigationManager.sendNavigationTurnDistanceEvent(500, 10); + mCarNavigationStatusManager + .sendNavigationTurnEvent(CarNavigationStatusManager.TURN_TURN, "Huff Ave", 90, + -1, null, CarNavigationStatusManager.TURN_SIDE_LEFT); + mCarNavigationStatusManager.sendNavigationTurnDistanceEvent(500, 10, 500, + CarNavigationStatusManager.DISTANCE_METERS); } catch (CarNotConnectedException e) { e.printStackTrace(); + initCarApi(); // This might happen due to inst cluster renderer crash. } } private void initCluster() { try { - mCarAppContextManager.registerContextListener(new AppContextChangeListener() { + mCarAppFocusManager.registerFocusListener(new AppFocusChangeListener() { @Override - public void onAppContextChange(int activeContexts) { - Log.d(TAG, "onAppContextChange, activeContexts: " + activeContexts); + public void onAppFocusChange(int appType, boolean active) { + Log.d(TAG, "onAppFocusChange, appType: " + appType + " active: " + active); } - }, CarAppContextManager.APP_CONTEXT_NAVIGATION); + }, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); } catch (CarNotConnectedException e) { - Log.e(TAG, "Failed to register context listener", e); + Log.e(TAG, "Failed to register focus listener", e); } + AppFocusOwnershipChangeListener focusListener = new AppFocusOwnershipChangeListener() { + @Override + public void onAppFocusOwnershipLoss(int focus) { + Log.w(TAG, "onAppFocusOwnershipLoss, focus: " + focus); + new AlertDialog.Builder(getContext()) + .setTitle(getContext().getApplicationInfo().name) + .setMessage(R.string.cluster_nav_app_context_loss) + .show(); + } + }; try { - mCarAppContextManager.setActiveContexts(new AppContextOwnershipChangeListener() { - @Override - public void onAppContextOwnershipLoss(int context) { - Log.w(TAG, "onAppContextOwnershipLoss, context: " + context); - new AlertDialog.Builder(getContext()) - .setTitle(getContext().getApplicationInfo().name) - .setMessage(R.string.cluster_nav_app_context_loss) - .show(); - } - }, CarAppContextManager.APP_CONTEXT_NAVIGATION); + mCarAppFocusManager.requestAppFocus(focusListener, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); } catch (CarNotConnectedException e) { - Log.e(TAG, "Failed to set active context", e); + Log.e(TAG, "Failed to set active focus", e); } try { - boolean ownsContext = - mCarAppContextManager.isOwningContext( - CarAppContextManager.APP_CONTEXT_NAVIGATION); - Log.d(TAG, "Owns APP_CONTEXT_NAVIGATION: " + ownsContext); - if (!ownsContext) { - throw new RuntimeException("Context was not acquired."); + boolean ownsFocus = mCarAppFocusManager.isOwningFocus(focusListener, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + Log.d(TAG, "Owns APP_FOCUS_TYPE_NAVIGATION: " + ownsFocus); + if (!ownsFocus) { + throw new RuntimeException("Focus was not acquired."); } } catch (CarNotConnectedException e) { - Log.e(TAG, "Failed to get owned context", e); + Log.e(TAG, "Failed to get owned focus", e); } try { - mCarNavigationManager.sendNavigationStatus(CarNavigationManager.STATUS_ACTIVE); + mCarNavigationStatusManager + .sendNavigationStatus(CarNavigationStatusManager.STATUS_ACTIVE); } catch (CarNotConnectedException e) { - Log.e(TAG, "Failed to set navigation status", e); + Log.e(TAG, "Failed to set navigation status, reconnecting to the car", e); + initCarApi(); // This might happen due to inst cluster renderer crash. } } } diff --git a/tests/android_car_api_test/Android.mk b/tests/android_car_api_test/Android.mk index aec4c71cb5..4048564325 100644 --- a/tests/android_car_api_test/Android.mk +++ b/tests/android_car_api_test/Android.mk @@ -32,7 +32,7 @@ LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) LOCAL_PROGUARD_ENABLED := disabled -LOCAL_STATIC_JAVA_LIBRARIES += car-systemtest +LOCAL_STATIC_JAVA_LIBRARIES += car-systemtest android-support-test LOCAL_JAVA_LIBRARIES := android.car android.test.runner diff --git a/tests/android_car_api_test/src/com/android/car/apitest/CarAppContextManagerTest.java b/tests/android_car_api_test/src/com/android/car/apitest/CarAppContextManagerTest.java deleted file mode 100644 index 84e1c50f5d..0000000000 --- a/tests/android_car_api_test/src/com/android/car/apitest/CarAppContextManagerTest.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.car.apitest; - -import android.car.Car; -import android.car.CarAppContextManager; -import android.test.suitebuilder.annotation.MediumTest; -import android.util.Log; - -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -@MediumTest -public class CarAppContextManagerTest extends CarApiTestBase { - private static final String TAG = CarAppContextManager.class.getSimpleName(); - private CarAppContextManager mManager; - - @Override - protected void setUp() throws Exception { - super.setUp(); - mManager = (CarAppContextManager) getCar().getCarManager(Car.APP_CONTEXT_SERVICE); - assertNotNull(mManager); - } - - public void testSetActiveNullListener() throws Exception { - try { - mManager.setActiveContexts(null, CarAppContextManager.APP_CONTEXT_NAVIGATION); - fail(); - } catch (IllegalArgumentException e) { - // expected - } - } - - public void testRegisterNull() throws Exception { - try { - mManager.registerContextListener(null, 0); - fail(); - } catch (IllegalArgumentException e) { - // expected - } - } - - public void testRegisterUnregister() throws Exception { - ContextChangeListerner listener = new ContextChangeListerner(); - ContextChangeListerner listener2 = new ContextChangeListerner(); - mManager.registerContextListener(listener, 0); - mManager.registerContextListener(listener2, 0); - mManager.unregisterContextListener(); - // this one is no-op - mManager.unregisterContextListener(); - } - - public void testContextChange() throws Exception { - DefaultServiceConnectionListener connectionListener = - new DefaultServiceConnectionListener(); - Car car2 = Car.createCar(getContext(), connectionListener, null); - car2.connect(); - connectionListener.waitForConnection(DEFAULT_WAIT_TIMEOUT_MS); - CarAppContextManager manager2 = (CarAppContextManager) - car2.getCarManager(Car.APP_CONTEXT_SERVICE); - assertNotNull(manager2); - - assertEquals(0, mManager.getActiveAppContexts()); - ContextChangeListerner change = new ContextChangeListerner(); - ContextChangeListerner change2 = new ContextChangeListerner(); - ContextOwnershipChangeListerner owner = new ContextOwnershipChangeListerner(); - ContextOwnershipChangeListerner owner2 = new ContextOwnershipChangeListerner(); - mManager.registerContextListener(change, CarAppContextManager.APP_CONTEXT_NAVIGATION | - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); - manager2.registerContextListener(change2, CarAppContextManager.APP_CONTEXT_NAVIGATION | - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); - - mManager.setActiveContexts(owner, CarAppContextManager.APP_CONTEXT_NAVIGATION); - int expectedContexts = CarAppContextManager.APP_CONTEXT_NAVIGATION; - assertEquals(expectedContexts, mManager.getActiveAppContexts()); - assertEquals(expectedContexts, manager2.getActiveAppContexts()); - assertTrue(mManager.isOwningContext(expectedContexts)); - assertFalse(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertFalse(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION | - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertFalse(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertFalse(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertTrue(change2.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, - expectedContexts)); - // change should not get notification for its own change - assertFalse(change.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 0)); - - mManager.setActiveContexts(owner, CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); - expectedContexts = CarAppContextManager.APP_CONTEXT_NAVIGATION | - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND; - assertTrue(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertTrue(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertTrue(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION | - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertFalse(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertFalse(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertEquals(expectedContexts, mManager.getActiveAppContexts()); - assertEquals(expectedContexts, manager2.getActiveAppContexts()); - assertTrue(change2.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, - expectedContexts)); - // change should not get notification for its own change - assertFalse(change.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 0)); - - // this should be no-op - mManager.setActiveContexts(owner, CarAppContextManager.APP_CONTEXT_NAVIGATION); - assertEquals(expectedContexts, mManager.getActiveAppContexts()); - assertEquals(expectedContexts, manager2.getActiveAppContexts()); - assertFalse(change2.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 0)); - assertFalse(change.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 0)); - - manager2.setActiveContexts(owner2, CarAppContextManager.APP_CONTEXT_NAVIGATION); - assertFalse(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertTrue(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertTrue(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertFalse(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertEquals(expectedContexts, mManager.getActiveAppContexts()); - assertEquals(expectedContexts, manager2.getActiveAppContexts()); - assertTrue(owner.waitForOwnershipLossAndAssert(DEFAULT_WAIT_TIMEOUT_MS, - CarAppContextManager.APP_CONTEXT_NAVIGATION)); - - // no-op as it is not owning it - mManager.resetActiveContexts(CarAppContextManager.APP_CONTEXT_NAVIGATION); - assertFalse(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertTrue(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertTrue(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertFalse(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertEquals(expectedContexts, mManager.getActiveAppContexts()); - assertEquals(expectedContexts, manager2.getActiveAppContexts()); - - mManager.resetActiveContexts(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); - assertFalse(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertFalse(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertTrue(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertFalse(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - expectedContexts = CarAppContextManager.APP_CONTEXT_NAVIGATION; - assertEquals(expectedContexts, mManager.getActiveAppContexts()); - assertEquals(expectedContexts, manager2.getActiveAppContexts()); - assertTrue(change2.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, - CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertFalse(change.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 0)); - - manager2.resetActiveContexts(CarAppContextManager.APP_CONTEXT_NAVIGATION); - assertFalse(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertFalse(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertFalse(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertFalse(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - expectedContexts = 0; - assertEquals(expectedContexts, mManager.getActiveAppContexts()); - assertEquals(expectedContexts, manager2.getActiveAppContexts()); - assertTrue(change.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 0)); - mManager.unregisterContextListener(); - manager2.unregisterContextListener(); - } - - public void testFilter() throws Exception { - DefaultServiceConnectionListener connectionListener = - new DefaultServiceConnectionListener(); - Car car2 = Car.createCar(getContext(), connectionListener); - car2.connect(); - connectionListener.waitForConnection(DEFAULT_WAIT_TIMEOUT_MS); - CarAppContextManager manager2 = (CarAppContextManager) - car2.getCarManager(Car.APP_CONTEXT_SERVICE); - assertNotNull(manager2); - - assertEquals(0, mManager.getActiveAppContexts()); - ContextChangeListerner change = new ContextChangeListerner(); - ContextChangeListerner listener = new ContextChangeListerner(); - ContextOwnershipChangeListerner owner = new ContextOwnershipChangeListerner(); - mManager.registerContextListener(change, CarAppContextManager.APP_CONTEXT_NAVIGATION | - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); - manager2.registerContextListener(listener, CarAppContextManager.APP_CONTEXT_NAVIGATION); - mManager.setActiveContexts(owner, CarAppContextManager.APP_CONTEXT_NAVIGATION); - assertTrue(listener.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, - CarAppContextManager.APP_CONTEXT_NAVIGATION)); - mManager.setActiveContexts(owner, CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); - assertFalse(listener.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 0)); - mManager.resetActiveContexts(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); - assertFalse(listener.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 0)); - mManager.resetActiveContexts(CarAppContextManager.APP_CONTEXT_NAVIGATION); - assertTrue(listener.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 0)); - } - - private class ContextChangeListerner implements CarAppContextManager.AppContextChangeListener { - private int mLastChangeEvent; - private final Semaphore mChangeWait = new Semaphore(0); - - public boolean waitForContextChangeAndAssert(long timeoutMs, int expectedContexts) - throws Exception { - if (!mChangeWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { - return false; - } - assertEquals(expectedContexts, mLastChangeEvent); - return true; - } - - @Override - public void onAppContextChange(int activeContexts) { - Log.i(TAG, "onAppContextChange " + Integer.toHexString(activeContexts)); - assertMainThread(); - mLastChangeEvent = activeContexts; - mChangeWait.release(); - } - } - - private class ContextOwnershipChangeListerner - implements CarAppContextManager.AppContextOwnershipChangeListener { - private int mLastLossEvent; - private final Semaphore mLossEventWait = new Semaphore(0); - - public boolean waitForOwnershipLossAndAssert(long timeoutMs, int expectedContexts) - throws Exception { - if (!mLossEventWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { - return false; - } - assertEquals(expectedContexts, mLastLossEvent); - return true; - } - - @Override - public void onAppContextOwnershipLoss(int context) { - Log.i(TAG, "onAppContextOwnershipLoss " + Integer.toHexString(context)); - assertMainThread(); - mLastLossEvent = context; - mLossEventWait.release(); - } - } -} diff --git a/tests/android_car_api_test/src/com/android/car/apitest/CarAppFocusManagerTest.java b/tests/android_car_api_test/src/com/android/car/apitest/CarAppFocusManagerTest.java new file mode 100644 index 0000000000..e234916f0c --- /dev/null +++ b/tests/android_car_api_test/src/com/android/car/apitest/CarAppFocusManagerTest.java @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.car.apitest; + +import android.car.Car; +import android.car.CarAppFocusManager; +import android.test.suitebuilder.annotation.MediumTest; +import android.util.Log; + +import org.junit.Assert; + +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +@MediumTest +public class CarAppFocusManagerTest extends CarApiTestBase { + private static final String TAG = CarAppFocusManagerTest.class.getSimpleName(); + private CarAppFocusManager mManager; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mManager = (CarAppFocusManager) getCar().getCarManager(Car.APP_FOCUS_SERVICE); + assertNotNull(mManager); + + // Request all application focuses and abandon them to ensure no active context is present + // when test starts. + FocusOwnershipChangeListerner owner = new FocusOwnershipChangeListerner(); + mManager.requestAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + mManager.requestAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + mManager.abandonAppFocus(owner); + } + + public void testSetActiveNullListener() throws Exception { + try { + mManager.requestAppFocus(null, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + } + + public void testRegisterNull() throws Exception { + try { + mManager.registerFocusListener(null, 0); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + } + + public void testRegisterUnregister() throws Exception { + FocusChangeListerner listener = new FocusChangeListerner(); + FocusChangeListerner listener2 = new FocusChangeListerner(); + mManager.registerFocusListener(listener, 1); + mManager.registerFocusListener(listener2, 1); + mManager.unregisterFocusListener(listener); + mManager.unregisterFocusListener(listener2); + } + + public void testFocusChange() throws Exception { + DefaultServiceConnectionListener connectionListener = + new DefaultServiceConnectionListener(); + Car car2 = Car.createCar(getContext(), connectionListener, null); + car2.connect(); + connectionListener.waitForConnection(DEFAULT_WAIT_TIMEOUT_MS); + CarAppFocusManager manager2 = (CarAppFocusManager) + car2.getCarManager(Car.APP_FOCUS_SERVICE); + assertNotNull(manager2); + final int[] emptyFocus = new int[0]; + + Assert.assertArrayEquals(emptyFocus, mManager.getActiveAppTypes()); + FocusChangeListerner change = new FocusChangeListerner(); + FocusChangeListerner change2 = new FocusChangeListerner(); + FocusOwnershipChangeListerner owner = new FocusOwnershipChangeListerner(); + FocusOwnershipChangeListerner owner2 = new FocusOwnershipChangeListerner(); + mManager.registerFocusListener(change, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + mManager.registerFocusListener(change, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + manager2.registerFocusListener(change2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + manager2.registerFocusListener(change2, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + + mManager.requestAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + int[] expectedFocuses = new int[] {CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION}; + Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes()); + Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes()); + assertTrue(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertFalse(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + assertFalse(manager2.isOwningFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertFalse(manager2.isOwningFocus(owner2, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + assertTrue(change2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true)); + assertTrue(change.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true)); + + mManager.requestAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + expectedFocuses = new int[] { + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND }; + assertTrue(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertTrue(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + assertFalse(manager2.isOwningFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertFalse(manager2.isOwningFocus(owner2, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes()); + Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes()); + assertTrue(change2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, true)); + assertTrue(change.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, true)); + + // this should be no-op + change.reset(); + change2.reset(); + mManager.requestAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes()); + Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes()); + assertFalse(change2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true)); + assertFalse(change.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true)); + + manager2.requestAppFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + assertFalse(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertTrue(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + assertTrue(manager2.isOwningFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertFalse(manager2.isOwningFocus(owner2, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes()); + Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes()); + assertTrue(owner.waitForOwnershipLossAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + + // no-op as it is not owning it + change.reset(); + change2.reset(); + mManager.abandonAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + assertFalse(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertTrue(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + assertTrue(manager2.isOwningFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertFalse(manager2.isOwningFocus(owner2, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes()); + Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes()); + + change.reset(); + change2.reset(); + mManager.abandonAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + assertFalse(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertFalse(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + assertTrue(manager2.isOwningFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertFalse(manager2.isOwningFocus(owner2, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + expectedFocuses = new int[] {CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION}; + Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes()); + Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes()); + assertTrue(change2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, false)); + assertTrue(change.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, false)); + + change.reset(); + change2.reset(); + manager2.abandonAppFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + assertFalse(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertFalse(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + assertFalse(manager2.isOwningFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertFalse(manager2.isOwningFocus(owner2, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + expectedFocuses = emptyFocus; + Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes()); + Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes()); + assertTrue(change.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false)); + mManager.unregisterFocusListener(change); + manager2.unregisterFocusListener(change2); + } + + public void testFilter() throws Exception { + DefaultServiceConnectionListener connectionListener = + new DefaultServiceConnectionListener(); + Car car2 = Car.createCar(getContext(), connectionListener); + car2.connect(); + connectionListener.waitForConnection(DEFAULT_WAIT_TIMEOUT_MS); + CarAppFocusManager manager2 = (CarAppFocusManager) + car2.getCarManager(Car.APP_FOCUS_SERVICE); + assertNotNull(manager2); + + Assert.assertArrayEquals(new int[0], mManager.getActiveAppTypes()); + + FocusChangeListerner listener = new FocusChangeListerner(); + FocusChangeListerner listener2 = new FocusChangeListerner(); + FocusOwnershipChangeListerner owner = new FocusOwnershipChangeListerner(); + mManager.registerFocusListener(listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + mManager.registerFocusListener(listener, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + manager2.registerFocusListener(listener2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + + mManager.requestAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true)); + assertTrue(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true)); + + listener.reset(); + listener2.reset(); + mManager.requestAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, true)); + assertFalse(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, true)); + + listener.reset(); + listener2.reset(); + mManager.abandonAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, false)); + assertFalse(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, false)); + + listener.reset(); + listener2.reset(); + mManager.abandonAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false)); + assertTrue(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false)); + } + + public void testMultipleChangeListenersPerManager() throws Exception { + FocusChangeListerner listener = new FocusChangeListerner(); + FocusChangeListerner listener2 = new FocusChangeListerner(); + FocusOwnershipChangeListerner owner = new FocusOwnershipChangeListerner(); + mManager.registerFocusListener(listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + mManager.registerFocusListener(listener, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + mManager.registerFocusListener(listener2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + + mManager.requestAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true)); + assertTrue(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true)); + + listener.reset(); + listener2.reset(); + mManager.requestAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, true)); + assertFalse(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, true)); + + listener.reset(); + listener2.reset(); + mManager.abandonAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, false)); + assertFalse(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, false)); + + listener.reset(); + listener2.reset(); + mManager.abandonAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false)); + assertTrue(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false)); + } + + private class FocusChangeListerner implements CarAppFocusManager.AppFocusChangeListener { + private int mLastChangeAppType; + private boolean mLastChangeAppActive; + private final Semaphore mChangeWait = new Semaphore(0); + + public boolean waitForFocusChangeAndAssert(long timeoutMs, int expectedAppType, + boolean expectedAppActive) throws Exception { + if (!mChangeWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { + return false; + } + assertEquals(expectedAppType, mLastChangeAppType); + assertEquals(expectedAppActive, mLastChangeAppActive); + return true; + } + + public void reset() { + mLastChangeAppType = 0; + mLastChangeAppActive = false; + } + + @Override + public void onAppFocusChange(int appType, boolean active) { + Log.i(TAG, "onAppFocusChange appType=" + appType + " active=" + active); + assertMainThread(); + mLastChangeAppType = appType; + mLastChangeAppActive = active; + mChangeWait.release(); + } + } + + private class FocusOwnershipChangeListerner + implements CarAppFocusManager.AppFocusOwnershipChangeListener { + private int mLastLossEvent; + private final Semaphore mLossEventWait = new Semaphore(0); + + public boolean waitForOwnershipLossAndAssert(long timeoutMs, int expectedAppType) + throws Exception { + if (!mLossEventWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { + return false; + } + assertEquals(expectedAppType, mLastLossEvent); + return true; + } + + @Override + public void onAppFocusOwnershipLoss(int appType) { + Log.i(TAG, "onAppFocusOwnershipLoss " + appType); + assertMainThread(); + mLastLossEvent = appType; + mLossEventWait.release(); + } + } +} diff --git a/tests/android_car_api_test/src/com/android/car/apitest/CarNavigationManagerTest.java b/tests/android_car_api_test/src/com/android/car/apitest/CarNavigationManagerTest.java index 2eb37eba86..a92b3804a3 100644 --- a/tests/android_car_api_test/src/com/android/car/apitest/CarNavigationManagerTest.java +++ b/tests/android_car_api_test/src/com/android/car/apitest/CarNavigationManagerTest.java @@ -16,18 +16,13 @@ package com.android.car.apitest; import android.car.Car; -import android.car.CarAppContextManager; -import android.car.CarAppContextManager.AppContextChangeListener; -import android.car.CarAppContextManager.AppContextOwnershipChangeListener; -import android.car.navigation.CarNavigationInstrumentCluster; +import android.car.CarAppFocusManager; +import android.car.CarAppFocusManager.AppFocusChangeListener; +import android.car.CarAppFocusManager.AppFocusOwnershipChangeListener; import android.car.navigation.CarNavigationManager; -import android.car.navigation.CarNavigationManager.CarNavigationListener; import android.test.suitebuilder.annotation.MediumTest; import android.util.Log; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - /** * Unit tests for {@link CarNavigationManager} */ @@ -37,7 +32,7 @@ public class CarNavigationManagerTest extends CarApiTestBase { private static final String TAG = CarNavigationManagerTest.class.getSimpleName(); private CarNavigationManager mCarNavigationManager; - private CarAppContextManager mCarAppContextManager; + private CarAppFocusManager mCarAppFocusManager; @Override protected void setUp() throws Exception { @@ -45,9 +40,9 @@ public class CarNavigationManagerTest extends CarApiTestBase { mCarNavigationManager = (CarNavigationManager) getCar().getCarManager(Car.CAR_NAVIGATION_SERVICE); assertNotNull(mCarNavigationManager); - mCarAppContextManager = - (CarAppContextManager) getCar().getCarManager(Car.APP_CONTEXT_SERVICE); - assertNotNull(mCarAppContextManager); + mCarAppFocusManager = + (CarAppFocusManager) getCar().getCarManager(Car.APP_FOCUS_SERVICE); + assertNotNull(mCarAppFocusManager); } public void testStart() throws Exception { @@ -56,47 +51,29 @@ public class CarNavigationManagerTest extends CarApiTestBase { return; } - final CountDownLatch onStartLatch = new CountDownLatch(1); - - mCarNavigationManager.registerListener(new CarNavigationListener() { - @Override - public void onInstrumentClusterStart(CarNavigationInstrumentCluster instrumentCluster) { - // TODO: we should use VehicleHalMock once we implement HAL support in - // CarNavigationStatusService. - assertFalse(instrumentCluster.supportsCustomImages()); - assertEquals(1000, instrumentCluster.getMinIntervalMs()); - onStartLatch.countDown(); - } - - @Override - public void onInstrumentClusterStop() { - // TODO - } - }); - - assertTrue(onStartLatch.await(DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)); - try { mCarNavigationManager.sendNavigationStatus(1); fail(); } catch (IllegalStateException expected) { - // Expected. Client should acquire context ownership for APP_CONTEXT_NAVIGATION. + // Expected. Client should acquire focus ownership for APP_FOCUS_TYPE_NAVIGATION. } - mCarAppContextManager.registerContextListener(new AppContextChangeListener() { + mCarAppFocusManager.registerFocusListener(new AppFocusChangeListener() { @Override - public void onAppContextChange(int activeContexts) { + public void onAppFocusChange(int appType, boolean active) { // Nothing to do here. } - }, CarAppContextManager.APP_CONTEXT_NAVIGATION); - mCarAppContextManager.setActiveContexts(new AppContextOwnershipChangeListener() { + }, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + AppFocusOwnershipChangeListener ownershipListener = new AppFocusOwnershipChangeListener() { @Override - public void onAppContextOwnershipLoss(int context) { + public void onAppFocusOwnershipLoss(int focus) { // Nothing to do here. } - }, CarAppContextManager.APP_CONTEXT_NAVIGATION); - assertTrue(mCarAppContextManager.isOwningContext( - CarAppContextManager.APP_CONTEXT_NAVIGATION)); + }; + mCarAppFocusManager.requestAppFocus(ownershipListener, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + assertTrue(mCarAppFocusManager.isOwningFocus(ownershipListener, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); // TODO: we should use mocked HAL to be able to verify this, right now just make sure that // it is not crashing and logcat has appropriate traces. diff --git a/tests/android_support_car_api_test/Android.mk b/tests/android_support_car_api_test/Android.mk index fb6629626f..98f0f96196 100644 --- a/tests/android_support_car_api_test/Android.mk +++ b/tests/android_support_car_api_test/Android.mk @@ -32,7 +32,7 @@ LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) LOCAL_PROGUARD_ENABLED := disabled -LOCAL_STATIC_JAVA_LIBRARIES += android.support.car +LOCAL_STATIC_JAVA_LIBRARIES += android.support.car android-support-test LOCAL_JAVA_LIBRARIES := android.car android.test.runner diff --git a/tests/android_support_car_api_test/src/com/android/support/car/apitest/CarAppContextManagerTest.java b/tests/android_support_car_api_test/src/com/android/support/car/apitest/CarAppContextManagerTest.java deleted file mode 100644 index 9d164b40b8..0000000000 --- a/tests/android_support_car_api_test/src/com/android/support/car/apitest/CarAppContextManagerTest.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.support.car.apitest; - -import android.support.car.Car; -import android.support.car.CarAppContextManager; -import android.test.suitebuilder.annotation.MediumTest; -import android.util.Log; - -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -@MediumTest -public class CarAppContextManagerTest extends CarApiTestBase { - private static final String TAG = CarAppContextManager.class.getSimpleName(); - private CarAppContextManager mManager; - - @Override - protected void setUp() throws Exception { - super.setUp(); - mManager = (CarAppContextManager) getCar().getCarManager(Car.APP_CONTEXT_SERVICE); - assertNotNull(mManager); - } - - public void testSetActiveNullListener() throws Exception { - try { - mManager.setActiveContexts(null, CarAppContextManager.APP_CONTEXT_NAVIGATION); - fail(); - } catch (IllegalArgumentException e) { - // expected - } - } - - public void testRegisterNull() throws Exception { - try { - mManager.registerContextListener(null, 0); - fail(); - } catch (IllegalArgumentException e) { - // expected - } - } - - public void testRegisterUnregister() throws Exception { - ContextChangeListerner listener = new ContextChangeListerner(); - ContextChangeListerner listener2 = new ContextChangeListerner(); - mManager.registerContextListener(listener, 0); - mManager.registerContextListener(listener2, 0); - mManager.unregisterContextListener(); - // this one is no-op - mManager.unregisterContextListener(); - } - - public void testContextChange() throws Exception { - DefaultServiceConnectionListener connectionListener = - new DefaultServiceConnectionListener(); - Car car2 = Car.createCar(getContext(), connectionListener, null); - car2.connect(); - connectionListener.waitForConnection(DEFAULT_WAIT_TIMEOUT_MS); - CarAppContextManager manager2 = (CarAppContextManager) - car2.getCarManager(Car.APP_CONTEXT_SERVICE); - assertNotNull(manager2); - - assertEquals(0, mManager.getActiveAppContexts()); - ContextChangeListerner change = new ContextChangeListerner(); - ContextChangeListerner change2 = new ContextChangeListerner(); - ContextOwnershipChangeListerner owner = new ContextOwnershipChangeListerner(); - ContextOwnershipChangeListerner owner2 = new ContextOwnershipChangeListerner(); - mManager.registerContextListener(change, CarAppContextManager.APP_CONTEXT_NAVIGATION | - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); - manager2.registerContextListener(change2, CarAppContextManager.APP_CONTEXT_NAVIGATION | - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); - - mManager.setActiveContexts(owner, CarAppContextManager.APP_CONTEXT_NAVIGATION); - int expectedContexts = CarAppContextManager.APP_CONTEXT_NAVIGATION; - assertEquals(expectedContexts, mManager.getActiveAppContexts()); - assertEquals(expectedContexts, manager2.getActiveAppContexts()); - assertTrue(mManager.isOwningContext(expectedContexts)); - assertFalse(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertFalse(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION | - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertFalse(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertFalse(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertTrue(change2.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, - expectedContexts)); - // change should not get notification for its own change - assertFalse(change.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 0)); - - mManager.setActiveContexts(owner, CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); - expectedContexts = CarAppContextManager.APP_CONTEXT_NAVIGATION | - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND; - assertTrue(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertTrue(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertTrue(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION | - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertFalse(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertFalse(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertEquals(expectedContexts, mManager.getActiveAppContexts()); - assertEquals(expectedContexts, manager2.getActiveAppContexts()); - assertTrue(change2.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, - expectedContexts)); - // change should not get notification for its own change - assertFalse(change.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 0)); - - // this should be no-op - mManager.setActiveContexts(owner, CarAppContextManager.APP_CONTEXT_NAVIGATION); - assertEquals(expectedContexts, mManager.getActiveAppContexts()); - assertEquals(expectedContexts, manager2.getActiveAppContexts()); - assertFalse(change2.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 0)); - assertFalse(change.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 0)); - - manager2.setActiveContexts(owner2, CarAppContextManager.APP_CONTEXT_NAVIGATION); - assertFalse(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertTrue(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertTrue(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertFalse(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertEquals(expectedContexts, mManager.getActiveAppContexts()); - assertEquals(expectedContexts, manager2.getActiveAppContexts()); - assertTrue(owner.waitForOwnershipLossAndAssert(DEFAULT_WAIT_TIMEOUT_MS, - CarAppContextManager.APP_CONTEXT_NAVIGATION)); - - // no-op as it is not owning it - mManager.resetActiveContexts(CarAppContextManager.APP_CONTEXT_NAVIGATION); - assertFalse(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertTrue(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertTrue(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertFalse(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertEquals(expectedContexts, mManager.getActiveAppContexts()); - assertEquals(expectedContexts, manager2.getActiveAppContexts()); - - mManager.resetActiveContexts(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); - assertFalse(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertFalse(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertTrue(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertFalse(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - expectedContexts = CarAppContextManager.APP_CONTEXT_NAVIGATION; - assertEquals(expectedContexts, mManager.getActiveAppContexts()); - assertEquals(expectedContexts, manager2.getActiveAppContexts()); - assertTrue(change2.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, - CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertFalse(change.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 0)); - - manager2.resetActiveContexts(CarAppContextManager.APP_CONTEXT_NAVIGATION); - assertFalse(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertFalse(mManager.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - assertFalse(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_NAVIGATION)); - assertFalse(manager2.isOwningContext(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND)); - expectedContexts = 0; - assertEquals(expectedContexts, mManager.getActiveAppContexts()); - assertEquals(expectedContexts, manager2.getActiveAppContexts()); - assertTrue(change.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 0)); - mManager.unregisterContextListener(); - manager2.unregisterContextListener(); - } - - public void testFilter() throws Exception { - DefaultServiceConnectionListener connectionListener = - new DefaultServiceConnectionListener(); - Car car2 = Car.createCar(getContext(), connectionListener); - car2.connect(); - connectionListener.waitForConnection(DEFAULT_WAIT_TIMEOUT_MS); - CarAppContextManager manager2 = (CarAppContextManager) - car2.getCarManager(Car.APP_CONTEXT_SERVICE); - assertNotNull(manager2); - - assertEquals(0, mManager.getActiveAppContexts()); - ContextChangeListerner change = new ContextChangeListerner(); - ContextChangeListerner listener = new ContextChangeListerner(); - ContextOwnershipChangeListerner owner = new ContextOwnershipChangeListerner(); - mManager.registerContextListener(change, CarAppContextManager.APP_CONTEXT_NAVIGATION | - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); - manager2.registerContextListener(listener, CarAppContextManager.APP_CONTEXT_NAVIGATION); - mManager.setActiveContexts(owner, CarAppContextManager.APP_CONTEXT_NAVIGATION); - assertTrue(listener.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, - CarAppContextManager.APP_CONTEXT_NAVIGATION)); - mManager.setActiveContexts(owner, CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); - assertFalse(listener.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 0)); - mManager.resetActiveContexts(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); - assertFalse(listener.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 0)); - mManager.resetActiveContexts(CarAppContextManager.APP_CONTEXT_NAVIGATION); - assertTrue(listener.waitForContextChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, 0)); - } - - private class ContextChangeListerner implements CarAppContextManager.AppContextChangeListener { - private int mLastChangeEvent; - private final Semaphore mChangeWait = new Semaphore(0); - - public boolean waitForContextChangeAndAssert(long timeoutMs, int expectedContexts) - throws Exception { - if (!mChangeWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { - return false; - } - assertEquals(expectedContexts, mLastChangeEvent); - return true; - } - @Override - public void onAppContextChange(int activeContexts) { - Log.i(TAG, "onAppContextChange " + Integer.toHexString(activeContexts)); - assertMainThread(); - mLastChangeEvent = activeContexts; - mChangeWait.release(); - } - } - - private class ContextOwnershipChangeListerner - implements CarAppContextManager.AppContextOwnershipChangeListener { - private int mLastLossEvent; - private final Semaphore mLossEventWait = new Semaphore(0); - - public boolean waitForOwnershipLossAndAssert(long timeoutMs, int expectedContexts) - throws Exception { - if (!mLossEventWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { - return false; - } - assertEquals(expectedContexts, mLastLossEvent); - return true; - } - - @Override - public void onAppContextOwnershipLoss(int context) { - Log.i(TAG, "onAppContextOwnershipLoss " + Integer.toHexString(context)); - assertMainThread(); - mLastLossEvent = context; - mLossEventWait.release(); - } - } -} diff --git a/tests/android_support_car_api_test/src/com/android/support/car/apitest/CarAppFocusManagerTest.java b/tests/android_support_car_api_test/src/com/android/support/car/apitest/CarAppFocusManagerTest.java new file mode 100644 index 0000000000..1e5491c78a --- /dev/null +++ b/tests/android_support_car_api_test/src/com/android/support/car/apitest/CarAppFocusManagerTest.java @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.support.car.apitest; + +import android.support.car.Car; +import android.support.car.CarAppFocusManager; +import android.test.suitebuilder.annotation.MediumTest; +import android.util.Log; + +import org.junit.Assert; + +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +@MediumTest +public class CarAppFocusManagerTest extends CarApiTestBase { + private static final String TAG = CarAppFocusManagerTest.class.getSimpleName(); + private CarAppFocusManager mManager; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mManager = (CarAppFocusManager) getCar().getCarManager(Car.APP_FOCUS_SERVICE); + assertNotNull(mManager); + + // Request all application focuses and abandon them to ensure no active context is present + // when test starts. + FocusOwnershipChangeListerner owner = new FocusOwnershipChangeListerner(); + mManager.requestAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + mManager.requestAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + mManager.abandonAppFocus(owner); + } + + public void testSetActiveNullListener() throws Exception { + try { + mManager.requestAppFocus(null, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + } + + public void testRegisterNull() throws Exception { + try { + mManager.registerFocusListener(null, 0); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + } + + public void testRegisterUnregister() throws Exception { + FocusChangeListerner listener = new FocusChangeListerner(); + FocusChangeListerner listener2 = new FocusChangeListerner(); + mManager.registerFocusListener(listener, 1); + mManager.registerFocusListener(listener2, 1); + mManager.unregisterFocusListener(listener); + mManager.unregisterFocusListener(listener2); + } + + public void testFocusChange() throws Exception { + DefaultServiceConnectionListener connectionListener = + new DefaultServiceConnectionListener(); + Car car2 = Car.createCar(getContext(), connectionListener, null); + car2.connect(); + connectionListener.waitForConnection(DEFAULT_WAIT_TIMEOUT_MS); + CarAppFocusManager manager2 = (CarAppFocusManager) + car2.getCarManager(Car.APP_FOCUS_SERVICE); + assertNotNull(manager2); + final int[] emptyFocus = new int[0]; + + Assert.assertArrayEquals(emptyFocus, mManager.getActiveAppTypes()); + FocusChangeListerner change = new FocusChangeListerner(); + FocusChangeListerner change2 = new FocusChangeListerner(); + FocusOwnershipChangeListerner owner = new FocusOwnershipChangeListerner(); + FocusOwnershipChangeListerner owner2 = new FocusOwnershipChangeListerner(); + mManager.registerFocusListener(change, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + mManager.registerFocusListener(change, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + manager2.registerFocusListener(change2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + manager2.registerFocusListener(change2, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + + mManager.requestAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + int[] expectedFocuses = new int[] {CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION}; + Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes()); + Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes()); + assertTrue(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertFalse(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + assertFalse(manager2.isOwningFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertFalse(manager2.isOwningFocus(owner2, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + assertTrue(change2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true)); + assertTrue(change.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true)); + + mManager.requestAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + expectedFocuses = new int[] { + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND }; + assertTrue(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertTrue(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + assertFalse(manager2.isOwningFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertFalse(manager2.isOwningFocus(owner2, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes()); + Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes()); + assertTrue(change2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, true)); + assertTrue(change.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, true)); + + // this should be no-op + change.reset(); + change2.reset(); + mManager.requestAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes()); + Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes()); + assertFalse(change2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true)); + assertFalse(change.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true)); + + manager2.requestAppFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + assertFalse(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertTrue(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + assertTrue(manager2.isOwningFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertFalse(manager2.isOwningFocus(owner2, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes()); + Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes()); + assertTrue(owner.waitForOwnershipLossAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + + // no-op as it is not owning it + change.reset(); + change2.reset(); + mManager.abandonAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + assertFalse(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertTrue(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + assertTrue(manager2.isOwningFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertFalse(manager2.isOwningFocus(owner2, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes()); + Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes()); + + change.reset(); + change2.reset(); + mManager.abandonAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + assertFalse(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertFalse(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + assertTrue(manager2.isOwningFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertFalse(manager2.isOwningFocus(owner2, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + expectedFocuses = new int[] {CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION}; + Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes()); + Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes()); + assertTrue(change2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, false)); + assertTrue(change.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, false)); + + change.reset(); + change2.reset(); + manager2.abandonAppFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + assertFalse(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertFalse(mManager.isOwningFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + assertFalse(manager2.isOwningFocus(owner2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)); + assertFalse(manager2.isOwningFocus(owner2, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND)); + expectedFocuses = emptyFocus; + Assert.assertArrayEquals(expectedFocuses, mManager.getActiveAppTypes()); + Assert.assertArrayEquals(expectedFocuses, manager2.getActiveAppTypes()); + assertTrue(change.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false)); + mManager.unregisterFocusListener(change); + manager2.unregisterFocusListener(change2); + } + + public void testFilter() throws Exception { + DefaultServiceConnectionListener connectionListener = + new DefaultServiceConnectionListener(); + Car car2 = Car.createCar(getContext(), connectionListener); + car2.connect(); + connectionListener.waitForConnection(DEFAULT_WAIT_TIMEOUT_MS); + CarAppFocusManager manager2 = (CarAppFocusManager) + car2.getCarManager(Car.APP_FOCUS_SERVICE); + assertNotNull(manager2); + + Assert.assertArrayEquals(new int[0], mManager.getActiveAppTypes()); + + FocusChangeListerner listener = new FocusChangeListerner(); + FocusChangeListerner listener2 = new FocusChangeListerner(); + FocusOwnershipChangeListerner owner = new FocusOwnershipChangeListerner(); + mManager.registerFocusListener(listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + mManager.registerFocusListener(listener, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + manager2.registerFocusListener(listener2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + + mManager.requestAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true)); + assertTrue(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true)); + + listener.reset(); + listener2.reset(); + mManager.requestAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, true)); + assertFalse(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, true)); + + listener.reset(); + listener2.reset(); + mManager.abandonAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, false)); + assertFalse(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, false)); + + listener.reset(); + listener2.reset(); + mManager.abandonAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false)); + assertTrue(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false)); + } + + public void testMultipleChangeListenersPerManager() throws Exception { + FocusChangeListerner listener = new FocusChangeListerner(); + FocusChangeListerner listener2 = new FocusChangeListerner(); + FocusOwnershipChangeListerner owner = new FocusOwnershipChangeListerner(); + mManager.registerFocusListener(listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + mManager.registerFocusListener(listener, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + mManager.registerFocusListener(listener2, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + + mManager.requestAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true)); + assertTrue(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true)); + + listener.reset(); + listener2.reset(); + mManager.requestAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, true)); + assertFalse(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, true)); + + listener.reset(); + listener2.reset(); + mManager.abandonAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, false)); + assertFalse(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, false)); + + listener.reset(); + listener2.reset(); + mManager.abandonAppFocus(owner, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + assertTrue(listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false)); + assertTrue(listener2.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false)); + } + + private class FocusChangeListerner implements CarAppFocusManager.AppFocusChangeListener { + private int mLastChangeAppType; + private boolean mLastChangeAppActive; + private final Semaphore mChangeWait = new Semaphore(0); + + public boolean waitForFocusChangeAndAssert(long timeoutMs, int expectedAppType, + boolean expectedAppActive) throws Exception { + if (!mChangeWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { + return false; + } + assertEquals(expectedAppType, mLastChangeAppType); + assertEquals(expectedAppActive, mLastChangeAppActive); + return true; + } + + public void reset() { + mLastChangeAppType = 0; + mLastChangeAppActive = false; + } + + @Override + public void onAppFocusChange(int appType, boolean active) { + Log.i(TAG, "onAppFocusChange appType=" + appType + " active=" + active); + assertMainThread(); + mLastChangeAppType = appType; + mLastChangeAppActive = active; + mChangeWait.release(); + } + } + + private class FocusOwnershipChangeListerner + implements CarAppFocusManager.AppFocusOwnershipChangeListener { + private int mLastLossEvent; + private final Semaphore mLossEventWait = new Semaphore(0); + + public boolean waitForOwnershipLossAndAssert(long timeoutMs, int expectedAppType) + throws Exception { + if (!mLossEventWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { + return false; + } + assertEquals(expectedAppType, mLastLossEvent); + return true; + } + + @Override + public void onAppFocusOwnershipLoss(int appType) { + Log.i(TAG, "onAppFocusOwnershipLoss " + appType); + assertMainThread(); + mLastLossEvent = appType; + mLossEventWait.release(); + } + } +} diff --git a/tests/android_support_car_api_test/src/com/android/support/car/apitest/CarNavigationManagerTest.java b/tests/android_support_car_api_test/src/com/android/support/car/apitest/CarNavigationStatusManagerTest.java index 1d1f47656b..1a1198d476 100644 --- a/tests/android_support_car_api_test/src/com/android/support/car/apitest/CarNavigationManagerTest.java +++ b/tests/android_support_car_api_test/src/com/android/support/car/apitest/CarNavigationStatusManagerTest.java @@ -15,17 +15,15 @@ */ package com.android.support.car.apitest; -import static android.support.car.CarAppContextManager.APP_CONTEXT_NAVIGATION; +import static android.support.car.CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION; -import android.os.Looper; import android.support.car.Car; -import android.support.car.CarAppContextManager; -import android.support.car.CarAppContextManager.AppContextChangeListener; -import android.support.car.CarAppContextManager.AppContextOwnershipChangeListener; +import android.support.car.CarAppFocusManager; +import android.support.car.CarAppFocusManager.AppFocusChangeListener; +import android.support.car.CarAppFocusManager.AppFocusOwnershipChangeListener; import android.support.car.navigation.CarNavigationInstrumentCluster; -import android.support.car.CarNotConnectedException; -import android.support.car.navigation.CarNavigationManager; -import android.support.car.navigation.CarNavigationManager.CarNavigationListener; +import android.support.car.navigation.CarNavigationStatusManager; +import android.support.car.navigation.CarNavigationStatusManager.CarNavigationListener; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -33,26 +31,26 @@ import java.util.concurrent.TimeUnit; /** * Unit tests for {@link android.support.car.navigation.CarNavigationStatusManager} */ -public class CarNavigationManagerTest extends CarApiTestBase { +public class CarNavigationStatusManagerTest extends CarApiTestBase { - private CarNavigationManager mCarNavigationManager; - private CarAppContextManager mCarAppContextManager; + private CarNavigationStatusManager mCarNavigationStatusManager; + private CarAppFocusManager mCarAppFocusManager; @Override protected void setUp() throws Exception { super.setUp(); - mCarNavigationManager = - (CarNavigationManager) getCar().getCarManager(Car.CAR_NAVIGATION_SERVICE); - assertNotNull(mCarNavigationManager); - mCarAppContextManager = - (CarAppContextManager) getCar().getCarManager(Car.APP_CONTEXT_SERVICE); - assertNotNull(mCarAppContextManager); + mCarNavigationStatusManager = + (CarNavigationStatusManager) getCar().getCarManager(Car.CAR_NAVIGATION_SERVICE); + assertNotNull(mCarNavigationStatusManager); + mCarAppFocusManager = + (CarAppFocusManager) getCar().getCarManager(Car.APP_FOCUS_SERVICE); + assertNotNull(mCarAppFocusManager); } public void testStart() throws Exception { final CountDownLatch onStartLatch = new CountDownLatch(1); - mCarNavigationManager.registerListener(new CarNavigationListener() { + mCarNavigationStatusManager.registerListener(new CarNavigationListener() { @Override public void onInstrumentClusterStart(CarNavigationInstrumentCluster instrumentCluster) { // TODO: we should use VehicleHalMock once we implement HAL support in @@ -71,28 +69,29 @@ public class CarNavigationManagerTest extends CarApiTestBase { assertTrue(onStartLatch.await(DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)); try { - mCarNavigationManager.sendNavigationStatus(1); + mCarNavigationStatusManager.sendNavigationStatus(1); fail(); } catch (IllegalStateException expected) { - // Expected. Client should acquire context ownership for APP_CONTEXT_NAVIGATION. + // Expected. Client should acquire focus ownership for APP_FOCUS_TYPE_NAVIGATION. } - mCarAppContextManager.registerContextListener(new AppContextChangeListener() { + mCarAppFocusManager.registerFocusListener(new AppFocusChangeListener() { @Override - public void onAppContextChange(int activeContexts) { + public void onAppFocusChange(int appType, boolean active) { // Nothing to do here. } - }, APP_CONTEXT_NAVIGATION); - mCarAppContextManager.setActiveContexts(new AppContextOwnershipChangeListener() { + }, APP_FOCUS_TYPE_NAVIGATION); + AppFocusOwnershipChangeListener ownershipListener = new AppFocusOwnershipChangeListener() { @Override - public void onAppContextOwnershipLoss(int context) { + public void onAppFocusOwnershipLoss(int focus) { // Nothing to do here. } - }, APP_CONTEXT_NAVIGATION); - assertTrue(mCarAppContextManager.isOwningContext(APP_CONTEXT_NAVIGATION)); + }; + mCarAppFocusManager.requestAppFocus(ownershipListener, APP_FOCUS_TYPE_NAVIGATION); + assertTrue(mCarAppFocusManager.isOwningFocus(ownershipListener, APP_FOCUS_TYPE_NAVIGATION)); // TODO: we should use mocked HAL to be able to verify this, right now just make sure that // it is not crashing and logcat has appropriate traces. - mCarNavigationManager.sendNavigationStatus(1); + mCarNavigationStatusManager.sendNavigationStatus(1); } } diff --git a/tests/carservice_test/src/com/android/car/test/CarAudioExtFocusTest.java b/tests/carservice_test/src/com/android/car/test/CarAudioExtFocusTest.java new file mode 100644 index 0000000000..0b6268b096 --- /dev/null +++ b/tests/carservice_test/src/com/android/car/test/CarAudioExtFocusTest.java @@ -0,0 +1,1047 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.car.test; + +import android.car.Car; +import android.car.media.CarAudioManager; +import android.car.test.VehicleHalEmulator.VehicleHalPropertyHandler; +import android.content.Context; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.os.SystemClock; +import android.test.suitebuilder.annotation.MediumTest; +import android.util.Log; + +import com.android.car.vehiclenetwork.VehicleNetworkConsts; +import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioContextFlag; +import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioExtFocusFlag; +import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusIndex; +import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusRequest; +import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusState; +import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioStream; +import com.android.car.vehiclenetwork.VehiclePropConfigUtil; +import com.android.car.vehiclenetwork.VehiclePropValueUtil; +import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePermissionModel; +import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropAccess; +import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropChangeMode; +import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleValueType; +import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValue; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +@MediumTest +public class CarAudioExtFocusTest extends MockedCarTestBase { + private static final String TAG = CarAudioExtFocusTest.class.getSimpleName(); + + private static final long TIMEOUT_MS = 3000; + + private final VehicleHalPropertyHandler mAudioRoutingPolicyPropertyHandler = + new VehicleHalPropertyHandler() { + @Override + public void onPropertySet(VehiclePropValue value) { + //TODO + } + + @Override + public VehiclePropValue onPropertyGet(VehiclePropValue value) { + fail("cannot get"); + return null; + } + + @Override + public void onPropertySubscribe(int property, float sampleRate, int zones) { + fail("cannot subscribe"); + } + + @Override + public void onPropertyUnsubscribe(int property) { + fail("cannot unsubscribe"); + } + }; + + private final FocusPropertyHandler mAudioFocusPropertyHandler = + new FocusPropertyHandler(this); + + private final ExtRoutingHintPropertyHandler mExtRoutingHintPropertyHandler = + new ExtRoutingHintPropertyHandler(); + + private static final String EXT_ROUTING_CONFIG = + "0:RADIO_AM_FM:0,1:RADIO_SATELLITE:0,33:CD_DVD:0," + + "64:com.google.test.SOMETHING_SPECIAL," + + "4:EXT_NAV_GUIDANCE:1," + + "5:AUX_IN0:0"; + + private final Semaphore mWaitSemaphore = new Semaphore(0); + private final LinkedList<VehiclePropValue> mEvents = new LinkedList<VehiclePropValue>(); + private AudioManager mAudioManager; + private CarAudioManager mCarAudioManager; + + @Override + protected void setUp() throws Exception { + super.setUp(); + // AudioManager should be created in main thread to get focus event. :( + runOnMainSync(new Runnable() { + @Override + public void run() { + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + } + }); + + getVehicleHalEmulator().addProperty( + VehiclePropConfigUtil.getBuilder( + VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY, + VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE, + VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE, + VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2, + VehiclePermissionModel.VEHICLE_PERMISSION_SYSTEM_APP_ONLY, + 0 /*configFlags*/, 0 /*sampleRateMax*/, 0 /*sampleRateMin*/).build(), + mAudioRoutingPolicyPropertyHandler); + getVehicleHalEmulator().addProperty( + VehiclePropConfigUtil.getBuilder( + VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, + VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE, + VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE, + VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC4, + VehiclePermissionModel.VEHICLE_PERMISSION_SYSTEM_APP_ONLY, + 0 /*configFlags*/, 0 /*sampleRateMax*/, 0 /*sampleRateMin*/).build(), + mAudioFocusPropertyHandler); + getVehicleHalEmulator().addStaticProperty( + VehiclePropConfigUtil.createStaticStringProperty( + VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_HW_VARIANT), + VehiclePropValueUtil.createIntValue( + VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_HW_VARIANT, 1, 0)); + getVehicleHalEmulator().addProperty( + VehiclePropConfigUtil.getBuilder( + VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT, + VehiclePropAccess.VEHICLE_PROP_ACCESS_WRITE, + VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE, + VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC4, + VehiclePermissionModel.VEHICLE_PERMISSION_SYSTEM_APP_ONLY, + 0 /*configFlags*/, 0 /*sampleRateMax*/, 0 /*sampleRateMin*/). + setConfigString(EXT_ROUTING_CONFIG).build(), + mExtRoutingHintPropertyHandler); + getVehicleHalEmulator().start(); + mCarAudioManager = (CarAudioManager) getCar().getCarManager(Car.AUDIO_SERVICE); + assertNotNull(mCarAudioManager); + } + + public void testExtRoutings() throws Exception { + String[] radioTypes = mCarAudioManager.getSupportedRadioTypes(); + assertNotNull(radioTypes); + checkStringArrayContents(new String[] {"RADIO_AM_FM", "RADIO_SATELLITE"}, radioTypes); + + String[] nonRadioTypes = mCarAudioManager.getSupportedExternalSourceTypes(); + assertNotNull(nonRadioTypes); + checkStringArrayContents(new String[] {"CD_DVD", "com.google.test.SOMETHING_SPECIAL", + "EXT_NAV_GUIDANCE", "AUX_IN0"}, nonRadioTypes); + } + + private void checkStringArrayContents(String[] expected, String[] actual) throws Exception { + Arrays.sort(expected); + Arrays.sort(actual); + assertEquals(expected.length, actual.length); + for (int i = 0; i < expected.length; i++) { + assertEquals(expected[i], actual[i]); + } + } + + public void testRadioAttributeCreation() throws Exception { + AudioAttributes attrb = mCarAudioManager.getAudioAttributesForRadio( + CarAudioManager.CAR_RADIO_TYPE_AM_FM); + assertNotNull(attrb); + + attrb = mCarAudioManager.getAudioAttributesForRadio( + CarAudioManager.CAR_RADIO_TYPE_SATELLITE); + assertNotNull(attrb); + + try { + attrb = mCarAudioManager.getAudioAttributesForRadio( + CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + } + + public void testExtSourceAttributeCreation() throws Exception { + AudioAttributes attrb = mCarAudioManager.getAudioAttributesForExternalSource( + CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_CD_DVD); + assertNotNull(attrb); + + attrb = mCarAudioManager.getAudioAttributesForExternalSource( + CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE); + assertNotNull(attrb); + + attrb = mCarAudioManager.getAudioAttributesForExternalSource( + "com.google.test.SOMETHING_SPECIAL"); + assertNotNull(attrb); + + try { + attrb = mCarAudioManager.getAudioAttributesForExternalSource( + CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + } + + public void testRadioAmFmGainFocus() throws Exception { + AudioAttributes attrb = mCarAudioManager.getAudioAttributesForRadio( + CarAudioManager.CAR_RADIO_TYPE_AM_FM); + assertNotNull(attrb); + checkSingleRequestRelease(attrb, AudioManager.AUDIOFOCUS_GAIN, new int[] {1, 0, 0, 0}, + 0, VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG, + VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG); + } + + public void testRadioSatelliteGainFocus() throws Exception { + AudioAttributes attrb = mCarAudioManager.getAudioAttributesForRadio( + CarAudioManager.CAR_RADIO_TYPE_SATELLITE); + assertNotNull(attrb); + checkSingleRequestRelease(attrb, AudioManager.AUDIOFOCUS_GAIN, new int[] {2, 0, 0, 0}, + 0, VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG, + VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG); + } + + public void testCdGainFocus() throws Exception { + AudioAttributes attrb = mCarAudioManager.getAudioAttributesForExternalSource( + CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_CD_DVD); + assertNotNull(attrb); + checkSingleRequestRelease(attrb, AudioManager.AUDIOFOCUS_GAIN, new int[] {0, 2, 0, 0}, + 0, VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG, + VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_CD_ROM_FLAG); + } + + public void testAuxInFocus() throws Exception { + AudioAttributes attrb = mCarAudioManager.getAudioAttributesForExternalSource( + CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_AUX_IN0); + assertNotNull(attrb); + checkSingleRequestRelease(attrb, AudioManager.AUDIOFOCUS_GAIN, new int[] {0x1<<5, 0, 0, 0}, + 0, VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG, + VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_AUX_AUDIO_FLAG); + } + + public void testExtNavInFocus() throws Exception { + AudioAttributes attrb = mCarAudioManager.getAudioAttributesForExternalSource( + CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE); + assertNotNull(attrb); + checkSingleRequestRelease(attrb, AudioManager.AUDIOFOCUS_GAIN, new int[] {0x1<<4, 0, 0, 0}, + 0, VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG, + VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG); + } + + public void testCustomInFocus() throws Exception { + AudioAttributes attrb = mCarAudioManager.getAudioAttributesForExternalSource( + "com.google.test.SOMETHING_SPECIAL"); + assertNotNull(attrb); + checkSingleRequestRelease(attrb, AudioManager.AUDIOFOCUS_GAIN, new int[] {0, 0, 1, 0}, + 0, VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG, + VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG); + } + + public void testMediaNavFocus() throws Exception { + //music start + AudioFocusListener listenerMusic = new AudioFocusListener(); + int res = mAudioManager.requestAudioFocus(listenerMusic, + AudioManager.STREAM_MUSIC, + AudioManager.AUDIOFOCUS_GAIN); + assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); + int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]); + assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]); + assertEquals(0, request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + request[1], + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG); + + // nav guidance start + AudioFocusListener listenerNav = new AudioFocusListener(); + AudioAttributes navAttrib = (new AudioAttributes.Builder()). + setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION). + setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE). + build(); + res = mAudioManager.requestAudioFocus(listenerNav, navAttrib, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]); + assertEquals(0x3, request[1]); + assertEquals(0, request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG | + VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NAVIGATION_FLAG, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, request[1], + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG); + + // nav guidance done + mAudioManager.abandonAudioFocus(listenerNav); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]); + assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]); + assertEquals(0, request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, request[1], + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG); + + // music done + mAudioManager.abandonAudioFocus(listenerMusic); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]); + assertEquals(0, request[1]); + assertEquals(0, request[2]); + assertEquals(0, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS, request[1], + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG); + } + + public void testMediaExternalMediaNavFocus() throws Exception { + // android music + AudioFocusListener listenerMusic = new AudioFocusListener(); + int res = mAudioManager.requestAudioFocus(listenerMusic, + AudioManager.STREAM_MUSIC, + AudioManager.AUDIOFOCUS_GAIN); + assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); + int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]); + assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]); + assertEquals(0, request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + request[1], + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG); + + // car plays external media (=outside Android) + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS, + 0, + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG); + int focusChange = listenerMusic.waitAndGetFocusChange(TIMEOUT_MS); + assertEquals(AudioManager.AUDIOFOCUS_LOSS, focusChange); + + // nav guidance start + AudioFocusListener listenerNav = new AudioFocusListener(); + AudioAttributes navAttrib = (new AudioAttributes.Builder()). + setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION). + setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE). + build(); + res = mAudioManager.requestAudioFocus(listenerNav, navAttrib, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK, + request[0]); + assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM1, request[1]); + assertEquals(0, request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NAVIGATION_FLAG, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT, + 0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM1, + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG); + + // nav guidance ends + mAudioManager.abandonAudioFocus(listenerNav); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]); + assertEquals(0, request[1]); + assertEquals(0, request[2]); + assertEquals(0, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS, + 0, + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG); + + // now ends external play + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS, + 0, + 0); + mAudioManager.abandonAudioFocus(listenerMusic); + //TODO how to check this? + } + + public void testExternalRadioExternalNav() throws Exception { + // android radio + AudioFocusListener listenerRadio = new AudioFocusListener(); + CarAudioManager carAudioManager = (CarAudioManager) getCar().getCarManager( + Car.AUDIO_SERVICE); + assertNotNull(carAudioManager); + AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage( + CarAudioManager.CAR_AUDIO_USAGE_RADIO); + int res = mAudioManager.requestAudioFocus(listenerRadio, + radioAttributes, AudioManager.AUDIOFOCUS_GAIN, 0); + assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); + int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]); + assertEquals(0, request[1]); + assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG, + request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG, request[3]); + assertArrayEquals(new int[] {1, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + 0, + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG); + + //external nav + AudioFocusListener listenerNav = new AudioFocusListener(); + AudioAttributes extNavAttributes = mCarAudioManager.getAudioAttributesForExternalSource( + CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE); + res = mAudioManager.requestAudioFocus(listenerNav, + extNavAttributes, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, + request[0]); + assertEquals(0, request[1]); + assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG, + request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG | + VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG, request[3]); + assertArrayEquals(new int[] {1 | 1<<4, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + 0, + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG); + + mAudioManager.abandonAudioFocus(listenerNav); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]); + assertEquals(0, request[1]); + assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG, + request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG, request[3]); + assertArrayEquals(new int[] {1, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + 0, + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG); + + mAudioManager.abandonAudioFocus(listenerRadio); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]); + assertEquals(0, request[1]); + assertEquals(0, request[2]); + assertEquals(0, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS, + 0, + 0); + } + + public void testMediaExternalNav() throws Exception { + // android music + AudioFocusListener listenerMusic = new AudioFocusListener(); + int res = mAudioManager.requestAudioFocus(listenerMusic, + AudioManager.STREAM_MUSIC, + AudioManager.AUDIOFOCUS_GAIN); + assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); + int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]); + assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]); + assertEquals(0, request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + request[1], + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG); + + //external nav + AudioFocusListener listenerNav = new AudioFocusListener(); + AudioAttributes extNavAttributes = mCarAudioManager.getAudioAttributesForExternalSource( + CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE); + res = mAudioManager.requestAudioFocus(listenerNav, + extNavAttributes, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, + request[0]); + assertEquals(0x1, request[1]); + assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG, + request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG | + VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG, request[3]); + assertArrayEquals(new int[] {1<<4, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + 0x1, + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG); + + mAudioManager.abandonAudioFocus(listenerNav); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]); + assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]); + assertEquals(0, request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + request[1], + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG); + + mAudioManager.abandonAudioFocus(listenerMusic); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]); + assertEquals(0, request[1]); + assertEquals(0, request[2]); + assertEquals(0, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS, + 0, + 0); + } + + /** + * Test internal nav - external nav case. + * External nav takes the same physical stream as internal nav. So internal nav + * will be lost while external nav is played. This should not happen in real case when + * AppFocus is used, but this test is to make sure that audio focus works as expected. + */ + public void testNavExternalNav() throws Exception { + // android nav + AudioFocusListener listenerIntNav = new AudioFocusListener(); + AudioAttributes intNavAttributes = mCarAudioManager.getAudioAttributesForCarUsage( + CarAudioManager.CAR_AUDIO_USAGE_NAVIGATION_GUIDANCE); + int res = mAudioManager.requestAudioFocus(listenerIntNav, intNavAttributes, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); + int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK, + request[0]); + assertEquals(0x2, request[1]); + assertEquals(0, request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NAVIGATION_FLAG, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + request[1], + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG); + + //external nav + AudioFocusListener listenerExtNav = new AudioFocusListener(); + AudioAttributes extNavAttributes = mCarAudioManager.getAudioAttributesForExternalSource( + CarAudioManager.CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE); + res = mAudioManager.requestAudioFocus(listenerExtNav, + extNavAttributes, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, + request[0]); + assertEquals(0, request[1]); + assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG, + request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_EXT_SOURCE_FLAG, request[3]); + assertArrayEquals(new int[] {1<<4, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + 0x1, + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG); + + mAudioManager.abandonAudioFocus(listenerExtNav); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK, + request[0]); + assertEquals(0x2, request[1]); + assertEquals(0, request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NAVIGATION_FLAG, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + request[1], + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG); + + mAudioManager.abandonAudioFocus(listenerIntNav); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]); + assertEquals(0, request[1]); + assertEquals(0, request[2]); + assertEquals(0, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS, + 0, + 0); + } + + public void testMediaExternalRadioNavMediaFocus() throws Exception { + // android music + AudioFocusListener listenerMusic = new AudioFocusListener(); + int res = mAudioManager.requestAudioFocus(listenerMusic, + AudioManager.STREAM_MUSIC, + AudioManager.AUDIOFOCUS_GAIN); + assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); + int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]); + assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]); + assertEquals(0, request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + request[1], + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG); + + // android radio + AudioFocusListener listenerRadio = new AudioFocusListener(); + CarAudioManager carAudioManager = (CarAudioManager) getCar().getCarManager( + Car.AUDIO_SERVICE); + assertNotNull(carAudioManager); + AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage( + CarAudioManager.CAR_AUDIO_USAGE_RADIO); + res = mAudioManager.requestAudioFocus(listenerRadio, + radioAttributes, AudioManager.AUDIOFOCUS_GAIN, 0); + assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]); + assertEquals(0, request[1]); + assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG, + request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG, request[3]); + assertArrayEquals(new int[] {1, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + 0, + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG); + + // nav guidance start + AudioFocusListener listenerNav = new AudioFocusListener(); + AudioAttributes navAttrib = (new AudioAttributes.Builder()). + setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION). + setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE). + build(); + res = mAudioManager.requestAudioFocus(listenerNav, navAttrib, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, + request[0]); + assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM1, request[1]); + assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG, + request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NAVIGATION_FLAG | + VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG, request[3]); + assertArrayEquals(new int[] {1, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + 0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM1, + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG); + + // nav guidance ends + mAudioManager.abandonAudioFocus(listenerNav); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, + request[0]); + assertEquals(0, request[1]); + assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG, + request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG, request[3]); + assertArrayEquals(new int[] {1, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + 0, + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG); + + // ends radio. music will get the focus GAIN. + // Music app is supposed to stop and release focus when it has lost focus, but here just + // check if focus is working. + mAudioManager.abandonAudioFocus(listenerRadio); + listenerMusic.waitForFocus(TIMEOUT_MS, AudioManager.AUDIOFOCUS_GAIN); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]); + assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, request[1]); + assertEquals(0, request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + 0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM0, + 0); + + // now music release focus. + mAudioManager.abandonAudioFocus(listenerMusic); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]); + assertEquals(0, request[1]); + assertEquals(0, request[2]); + assertEquals(0, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS, + 0, + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG); + } + + private void checkSingleRequestRelease(AudioAttributes attrb, int androidFocusToRequest, + int[] expectedExtRouting, int expectedStreams, + int expectedExtState, int expectedContexts) throws Exception { + AudioFocusListener lister = new AudioFocusListener(); + int res = mCarAudioManager.requestAudioFocus(lister, attrb, androidFocusToRequest, 0); + assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); + int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + int expectedFocusRequest = VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE; + int response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS; + switch (androidFocusToRequest) { + case AudioManager.AUDIOFOCUS_GAIN: + expectedFocusRequest = VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN; + response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN; + break; + case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: + expectedFocusRequest = + VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT; + response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT; + break; + case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: + expectedFocusRequest = + VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK; + response = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT; + break; + } + assertEquals(expectedFocusRequest, request[0]); + assertEquals(expectedStreams, request[1]); + assertEquals(expectedExtState, request[2]); + assertEquals(expectedContexts, request[3]); + assertArrayEquals(expectedExtRouting, mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + response, + request[1], + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG); + mAudioManager.abandonAudioFocus(lister); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, request[0]); + assertEquals(0, request[1]); + assertEquals(0, request[2]); + assertEquals(0, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS, + request[1], + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG); + } + + public void testRadioMute() throws Exception { + testMediaMute(CarAudioManager.CAR_AUDIO_USAGE_RADIO, + 0, + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG, + VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG); + } + + public void testMusicMute() throws Exception { + testMediaMute(CarAudioManager.CAR_AUDIO_USAGE_MUSIC, + 0x1, + 0, + VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG); + } + + private void testMediaMute(int mediaUsage, int primaryStream, int extFocusFlag, + int mediaContext) throws Exception { + // android radio + AudioFocusListener listenerMedia = new AudioFocusListener(); + CarAudioManager carAudioManager = (CarAudioManager) getCar().getCarManager( + Car.AUDIO_SERVICE); + assertNotNull(carAudioManager); + AudioAttributes radioAttributes = carAudioManager.getAudioAttributesForCarUsage(mediaUsage); + Log.i(TAG, "request media Focus"); + int res = mAudioManager.requestAudioFocus(listenerMedia, + radioAttributes, AudioManager.AUDIOFOCUS_GAIN, 0); + assertEquals(AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res); + int[] request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]); + assertEquals(primaryStream, request[1]); + assertEquals(extFocusFlag, request[2]); + assertEquals(mediaContext, request[3]); + if (mediaUsage == CarAudioManager.CAR_AUDIO_USAGE_RADIO) { + assertArrayEquals(new int[] {1, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + } else { + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + } + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + primaryStream, + extFocusFlag); + // now mute it. + assertFalse(carAudioManager.isMediaMuted()); + Log.i(TAG, "mute media"); + assertTrue(carAudioManager.setMediaMute(true)); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT, + request[0]); + assertEquals(0, request[1]); + assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG, + request[2]); + assertEquals(0, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + 0, + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG); + assertTrue(carAudioManager.isMediaMuted()); + // nav guidance on top of it + AudioFocusListener listenerNav = new AudioFocusListener(); + AudioAttributes navAttrib = (new AudioAttributes.Builder()). + setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION). + setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE). + build(); + Log.i(TAG, "request nav Focus"); + res = mAudioManager.requestAudioFocus(listenerNav, navAttrib, + AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK, + request[0]); + assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM1, request[1]); + assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG, + request[2]); + assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NAVIGATION_FLAG, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + assertTrue(carAudioManager.isMediaMuted()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + 0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM1, + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG); + assertTrue(carAudioManager.isMediaMuted()); + // nav guidance ends + mAudioManager.abandonAudioFocus(listenerNav); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT, + request[0]); + assertEquals(0, request[1]); + assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG, + request[2]); + assertEquals(0, request[3]); + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + assertTrue(carAudioManager.isMediaMuted()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + 0, + VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG); + // now unmute it. media should resume. + assertTrue(carAudioManager.isMediaMuted()); + assertFalse(carAudioManager.setMediaMute(false)); + assertFalse(carAudioManager.isMediaMuted()); + request = mAudioFocusPropertyHandler.waitForAudioFocusRequest(TIMEOUT_MS); + assertEquals(VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN, request[0]); + assertEquals(primaryStream, request[1]); + assertEquals(extFocusFlag, + request[2]); + assertEquals(mediaContext, request[3]); + if (mediaUsage == CarAudioManager.CAR_AUDIO_USAGE_RADIO) { + assertArrayEquals(new int[] {1, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + } else { + assertArrayEquals(new int[] {0, 0, 0, 0}, + mExtRoutingHintPropertyHandler.getLastHint()); + } + assertFalse(carAudioManager.isMediaMuted()); + mAudioFocusPropertyHandler.sendAudioFocusState( + VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN, + primaryStream, + extFocusFlag); + assertFalse(carAudioManager.isMediaMuted()); + // release focus + mAudioManager.abandonAudioFocus(listenerMedia); + } + + protected static class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener { + private final Semaphore mFocusChangeWait = new Semaphore(0); + private int mLastFocusChange; + + public int waitAndGetFocusChange(long timeoutMs) throws Exception { + if (!mFocusChangeWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { + fail("timeout waiting for focus change"); + } + return mLastFocusChange; + } + + public void waitForFocus(long timeoutMs, int expectedFocus) throws Exception { + while (mLastFocusChange != expectedFocus) { + if (!mFocusChangeWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { + fail("timeout waiting for focus change"); + } + } + } + + @Override + public void onAudioFocusChange(int focusChange) { + mLastFocusChange = focusChange; + mFocusChangeWait.release(); + } + } + + protected static class FocusPropertyHandler implements VehicleHalPropertyHandler { + + private int mState = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS; + private int mStreams = 0; + private int mExtFocus = 0; + private int mRequest; + private int mRequestedStreams; + private int mRequestedExtFocus; + private int mRequestedAudioContexts; + private final MockedCarTestBase mCarTest; + + private final Semaphore mSetWaitSemaphore = new Semaphore(0); + + public FocusPropertyHandler(MockedCarTestBase carTest) { + mCarTest = carTest; + } + + public void sendAudioFocusState(int state, int streams, int extFocus) { + synchronized (this) { + mState = state; + mStreams = streams; + mExtFocus = extFocus; + } + int[] values = { state, streams, extFocus, 0 }; + mCarTest.getVehicleHalEmulator().injectEvent(VehiclePropValueUtil.createIntVectorValue( + VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, values, + SystemClock.elapsedRealtimeNanos())); + } + + public int[] waitForAudioFocusRequest(long timeoutMs) throws Exception { + if (!mSetWaitSemaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { + fail("timeout"); + } + synchronized (this) { + return new int[] { mRequest, mRequestedStreams, mRequestedExtFocus, + mRequestedAudioContexts }; + } + } + + @Override + public void onPropertySet(VehiclePropValue value) { + assertEquals(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, value.getProp()); + synchronized (this) { + mRequest = value.getInt32Values( + VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_FOCUS); + mRequestedStreams = value.getInt32Values( + VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_STREAMS); + mRequestedExtFocus = value.getInt32Values( + VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_EXTERNAL_FOCUS_STATE); + mRequestedAudioContexts = value.getInt32Values( + VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_AUDIO_CONTEXTS); + } + mSetWaitSemaphore.release(); + } + + @Override + public VehiclePropValue onPropertyGet(VehiclePropValue value) { + assertEquals(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, value.getProp()); + int state, streams, extFocus; + synchronized (this) { + state = mState; + streams = mStreams; + extFocus = mExtFocus; + } + int[] values = { state, streams, extFocus, 0 }; + return VehiclePropValueUtil.createIntVectorValue( + VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, values, + SystemClock.elapsedRealtimeNanos()); + } + + @Override + public void onPropertySubscribe(int property, float sampleRate, int zones) { + assertEquals(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, property); + } + + @Override + public void onPropertyUnsubscribe(int property) { + assertEquals(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, property); + } + } + + private static class ExtRoutingHintPropertyHandler implements VehicleHalPropertyHandler { + private int[] mLastHint = {0, 0, 0, 0}; + + public int[] getLastHint() { + int[] lastHint = new int[mLastHint.length]; + synchronized (this) { + System.arraycopy(mLastHint, 0, lastHint, 0, mLastHint.length); + } + return lastHint; + } + + @Override + public void onPropertySet(VehiclePropValue value) { + assertEquals(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT, + value.getProp()); + assertEquals(mLastHint.length, value.getInt32ValuesCount()); + synchronized (this) { + for (int i = 0; i < mLastHint.length; i++) { + mLastHint[i] = value.getInt32Values(i); + } + } + } + + @Override + public VehiclePropValue onPropertyGet(VehiclePropValue value) { + fail("write only"); + return null; + } + + @Override + public void onPropertySubscribe(int property, float sampleRate, int zones) { + fail("cannot subsctibe"); + } + + @Override + public void onPropertyUnsubscribe(int property) { + fail("cannot subsctibe"); + } + } +} diff --git a/tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java b/tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java index 5a978fd489..df1db9a128 100644 --- a/tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java +++ b/tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java @@ -77,34 +77,6 @@ public class CarAudioFocusTest extends MockedCarTestBase { private final FocusPropertyHandler mAudioFocusPropertyHandler = new FocusPropertyHandler(this); - private final VehicleHalPropertyHandler mAppContextPropertyHandler = - new VehicleHalPropertyHandler() { - - @Override - public void onPropertySet(VehiclePropValue value) { - // TODO Auto-generated method stub - - } - - @Override - public VehiclePropValue onPropertyGet(VehiclePropValue value) { - // TODO Auto-generated method stub - return null; - } - - @Override - public void onPropertySubscribe(int property, float sampleRate, int zones) { - // TODO Auto-generated method stub - - } - - @Override - public void onPropertyUnsubscribe(int property) { - // TODO Auto-generated method stub - - } - }; - private final Semaphore mWaitSemaphore = new Semaphore(0); private final LinkedList<VehiclePropValue> mEvents = new LinkedList<VehiclePropValue>(); private AudioManager mAudioManager; diff --git a/tests/carservice_test/src/com/android/car/test/MockedCarTestBase.java b/tests/carservice_test/src/com/android/car/test/MockedCarTestBase.java index 9c8ef3d7fb..adfc3f355a 100644 --- a/tests/carservice_test/src/com/android/car/test/MockedCarTestBase.java +++ b/tests/carservice_test/src/com/android/car/test/MockedCarTestBase.java @@ -26,6 +26,7 @@ import android.support.car.ServiceConnectionListener; import android.test.AndroidTestCase; import android.util.Log; +import java.util.Arrays; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; @@ -68,6 +69,20 @@ public class MockedCarTestBase extends AndroidTestCase { } }; + public static <T> void assertArrayEquals(T[] expected, T[] actual) { + if (!Arrays.equals(expected, actual)) { + fail("expected:<" + Arrays.toString(expected) + + "> but was:<" + Arrays.toString(actual) + ">"); + } + } + + public static void assertArrayEquals(int[] expected, int[] actual) { + if (!Arrays.equals(expected, actual)) { + fail("expected:<" + Arrays.toString(expected) + + "> but was:<" + Arrays.toString(actual) + ">"); + } + } + @Override protected synchronized void setUp() throws Exception { super.setUp(); diff --git a/tests/carservice_test/src/com/android/support/car/test/AppContextTest.java b/tests/carservice_test/src/com/android/support/car/test/AppContextTest.java deleted file mode 100644 index 7711d5038d..0000000000 --- a/tests/carservice_test/src/com/android/support/car/test/AppContextTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.support.car.test; - -import android.car.test.VehicleHalEmulator.VehicleHalPropertyHandler; -import android.content.Context; -import android.media.AudioManager; -import android.support.car.Car; -import android.support.car.CarAppContextManager; -import android.test.suitebuilder.annotation.MediumTest; -import android.util.Log; - -import com.android.car.test.MockedCarTestBase; -import com.android.car.vehiclenetwork.VehicleNetworkConsts; -import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioContextFlag; -import com.android.car.vehiclenetwork.VehiclePropConfigUtil; -import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePermissionModel; -import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropAccess; -import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropChangeMode; -import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleValueType; -import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValue; - -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -@MediumTest -public class AppContextTest extends MockedCarTestBase { - private static final String TAG = AppContextTest.class.getSimpleName(); - private static final long DEFAULT_WAIT_TIMEOUT_MS = 1000; - - @Override - protected void setUp() throws Exception { - super.setUp(); - getVehicleHalEmulator().start(); - } - - public void testContextChange() throws Exception { - CarAppContextManager manager = (CarAppContextManager) getSupportCar().getCarManager( - Car.APP_CONTEXT_SERVICE); - ContextChangeListener listener = new ContextChangeListener(); - ContextOwnershipChangeListerner ownershipListener = new ContextOwnershipChangeListerner(); - manager.registerContextListener(listener, CarAppContextManager.APP_CONTEXT_NAVIGATION | - CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); - manager.setActiveContexts(ownershipListener, CarAppContextManager.APP_CONTEXT_NAVIGATION); - manager.setActiveContexts(ownershipListener, CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); - manager.resetActiveContexts(CarAppContextManager.APP_CONTEXT_NAVIGATION); - manager.resetActiveContexts(CarAppContextManager.APP_CONTEXT_VOICE_COMMAND); - manager.unregisterContextListener(); - } - - private class ContextChangeListener implements CarAppContextManager.AppContextChangeListener { - private int mLastChangeEvent; - private final Semaphore mChangeWait = new Semaphore(0); - - public boolean waitForContextChangeAndAssert(long timeoutMs, int expectedContexts) - throws Exception { - if (!mChangeWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { - return false; - } - assertEquals(expectedContexts, mLastChangeEvent); - return true; - } - - @Override - public void onAppContextChange(int activeContexts) { - Log.i(TAG, "onAppContextChange " + Integer.toHexString(activeContexts)); - mLastChangeEvent = activeContexts; - mChangeWait.release(); - } - } - - private class ContextOwnershipChangeListerner - implements CarAppContextManager.AppContextOwnershipChangeListener { - private int mLastLossEvent; - private final Semaphore mLossEventWait = new Semaphore(0); - - public boolean waitForOwnershipLossAndAssert(long timeoutMs, int expectedContexts) - throws Exception { - if (!mLossEventWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { - return false; - } - assertEquals(expectedContexts, mLastLossEvent); - return true; - } - - @Override - public void onAppContextOwnershipLoss(int context) { - Log.i(TAG, "onAppContextOwnershipLoss " + Integer.toHexString(context)); - mLastLossEvent = context; - mLossEventWait.release(); - } - } -} diff --git a/tests/carservice_test/src/com/android/support/car/test/AppFocusTest.java b/tests/carservice_test/src/com/android/support/car/test/AppFocusTest.java new file mode 100644 index 0000000000..d80063d0f6 --- /dev/null +++ b/tests/carservice_test/src/com/android/support/car/test/AppFocusTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.support.car.test; + +import android.support.car.Car; +import android.support.car.CarAppFocusManager; +import android.test.suitebuilder.annotation.MediumTest; +import android.util.Log; + +import com.android.car.test.MockedCarTestBase; + +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +@MediumTest +public class AppFocusTest extends MockedCarTestBase { + private static final String TAG = AppFocusTest.class.getSimpleName(); + private static final long DEFAULT_WAIT_TIMEOUT_MS = 1000; + + @Override + protected void setUp() throws Exception { + super.setUp(); + getVehicleHalEmulator().start(); + } + + public void testFocusChange() throws Exception { + CarAppFocusManager manager = (CarAppFocusManager) getSupportCar().getCarManager( + Car.APP_FOCUS_SERVICE); + FocusChangeListener listener = new FocusChangeListener(); + FocusOwnershipChangeListerner ownershipListener = new FocusOwnershipChangeListerner(); + manager.registerFocusListener(listener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + manager.registerFocusListener(listener, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + manager.requestAppFocus(ownershipListener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, true); + manager.requestAppFocus(ownershipListener, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, true); + manager.abandonAppFocus(ownershipListener, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION); + listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION, false); + manager.abandonAppFocus(ownershipListener, CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND); + listener.waitForFocusChangeAndAssert(DEFAULT_WAIT_TIMEOUT_MS, + CarAppFocusManager.APP_FOCUS_TYPE_VOICE_COMMAND, false); + manager.unregisterFocusListener(listener); + } + + private class FocusChangeListener implements CarAppFocusManager.AppFocusChangeListener { + private int mLastChangeAppType; + private boolean mLastChangeAppActive; + private final Semaphore mChangeWait = new Semaphore(0); + + public boolean waitForFocusChangeAndAssert(long timeoutMs, int expectedAppType, + boolean expectedAppActive) throws Exception { + if (!mChangeWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { + return false; + } + assertEquals(expectedAppType, mLastChangeAppType); + assertEquals(expectedAppActive, mLastChangeAppActive); + return true; + } + + @Override + public void onAppFocusChange(int appType, boolean active) { + Log.i(TAG, "onAppFocusChange appType=" + appType + " active=" + active); + mLastChangeAppType = appType; + mLastChangeAppActive = active; + mChangeWait.release(); + } + } + + private class FocusOwnershipChangeListerner + implements CarAppFocusManager.AppFocusOwnershipChangeListener { + private int mLastLossEvent; + private final Semaphore mLossEventWait = new Semaphore(0); + + public boolean waitForOwnershipLossAndAssert(long timeoutMs, int expectedLossAppType) + throws Exception { + if (!mLossEventWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS)) { + return false; + } + assertEquals(expectedLossAppType, mLastLossEvent); + return true; + } + + @Override + public void onAppFocusOwnershipLoss(int appType) { + Log.i(TAG, "onAppFocusOwnershipLoss " + appType); + mLastLossEvent = appType; + mLossEventWait.release(); + } + } +} diff --git a/vehicle_network_service/VehicleHalPropertyUtil.h b/vehicle_network_service/VehicleHalPropertyUtil.h index 0a9eb31744..17319696b4 100644 --- a/vehicle_network_service/VehicleHalPropertyUtil.h +++ b/vehicle_network_service/VehicleHalPropertyUtil.h @@ -30,13 +30,25 @@ namespace android { +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif + class VechilePropertyUtil { public: static void dumpProperty(String8& msg, const vehicle_prop_config_t& config) { msg.appendFormat("property 0x%x, access:0x%x, change_mode:0x%x, value_type:0x%x", config.prop, config.access, config.change_mode, config.value_type); - msg.appendFormat(",permission:0x%x, zones:0x%x, conflg_flag:0x%x, fsmin:%f, fsmax:%f", + char configString[100]; + if (config.config_string.len > 0 && config.config_string.data != NULL) { + int stringLen = MIN(config.config_string.len, (int32_t) sizeof(configString) - 1); + memcpy(configString, config.config_string.data, stringLen); + configString[stringLen] = 0; + } + msg.appendFormat(",permission:0x%x, zones:0x%x, conflg_flag:0x%x, config_string:%s, " \ + "fsmin:%f, fsmax:%f", config.permission_model, config.vehicle_zone_flags, config.config_flags, + (config.config_string.len > 0) ? configString : "N/A", config.min_sample_rate, config.max_sample_rate); switch (config.value_type) { case VEHICLE_VALUE_TYPE_FLOAT: diff --git a/vehicle_network_service/VehicleNetworkService.cpp b/vehicle_network_service/VehicleNetworkService.cpp index dee0c25b90..c46a96730a 100644 --- a/vehicle_network_service/VehicleNetworkService.cpp +++ b/vehicle_network_service/VehicleNetworkService.cpp @@ -382,6 +382,11 @@ void VehicleNetworkService::onFirstRef() { *this)); ASSERT_ALWAYS_ON_NO_MEMORY(handler.get()); mHandler = handler; + + // populate empty list before hal init. + mProperties = new VehiclePropertiesHolder(false /* deleteConfigsInDestructor */); + ASSERT_ALWAYS_ON_NO_MEMORY(mProperties); + r = mDevice->init(mDevice, eventCallback, errorCallback); if (r != NO_ERROR) { ALOGE("HAL init failed:%d", r); @@ -389,8 +394,6 @@ void VehicleNetworkService::onFirstRef() { } int numConfigs = 0; vehicle_prop_config_t const* configs = mDevice->list_properties(mDevice, &numConfigs); - mProperties = new VehiclePropertiesHolder(false /* deleteConfigsInDestructor */); - ASSERT_ALWAYS_ON_NO_MEMORY(mProperties); for (int i = 0; i < numConfigs; i++) { mProperties->getList().push_back(&configs[i]); } @@ -648,6 +651,7 @@ status_t VehicleNetworkService::subscribe(const sp<IVehicleNetworkListener> &lis int32_t newZones = zones; vehicle_prop_config_t const * config = NULL; sp<HalClient> client; + bool autoGetEnabled = false; do { Mutex::Autolock autoLock(mLock); if (!isSubscribableLocked(prop)) { @@ -709,6 +713,7 @@ status_t VehicleNetworkService::subscribe(const sp<IVehicleNetworkListener> &lis } client->setSubscriptionInfo(prop, sampleRate, zones); if (shouldSubscribe) { + autoGetEnabled = mVehiclePropertyAccessControl.isAutoGetEnabled(prop); inMock = mMockingEnabled; SubscriptionInfo info(sampleRate, newZones); mSubscriptionInfos.add(prop, info); @@ -736,7 +741,7 @@ status_t VehicleNetworkService::subscribe(const sp<IVehicleNetworkListener> &lis } } } - if (isSampleRateFixed(config->change_mode)) { + if (autoGetEnabled && isSampleRateFixed(config->change_mode)) { status_t r = notifyClientWithCurrentValue(inMock, config, zones); if (r != NO_ERROR) { return r; @@ -772,6 +777,7 @@ status_t VehicleNetworkService::notifyClientWithCurrentValue(bool isMocking, status_t VehicleNetworkService::notifyClientWithCurrentValue(bool isMocking, int32_t prop, int32_t valueType, int32_t zone) { vehicle_prop_value_t value; + memset(&value, 0, sizeof(value)); value.prop = prop; value.value_type = valueType; value.zone = zone; diff --git a/vehicle_network_service/VehiclePropertyAccessControl.cpp b/vehicle_network_service/VehiclePropertyAccessControl.cpp index 3fe5f157c9..4adb55f5f0 100644 --- a/vehicle_network_service/VehiclePropertyAccessControl.cpp +++ b/vehicle_network_service/VehiclePropertyAccessControl.cpp @@ -171,6 +171,14 @@ bool VehiclePropertyAccessControl::populate(xmlNode * a_node) { property_value = std::stoul(tmp_str, nullptr, 10); } + // property with this set to true will not call get when it is subscribed. + property_value_str = xmlGetProp(cur_node, (const xmlChar*)"no_auto_get"); + if (property_value_str) { + if (xmlStrcmp(property_value_str, (const xmlChar*)"true")==0) { + mPropertiesWithNoAutoGet.insert(property_value); + } + } + // Loop over all UID tags for (child = cur_node->children; child; child = child->next) { if ((xmlStrcmp(child->name, (const xmlChar*)"UID")==0) && @@ -354,4 +362,8 @@ bool VehiclePropertyAccessControl::testAccess(int32_t property, int32_t uid, } } +bool VehiclePropertyAccessControl::isAutoGetEnabled(int32_t property) { + return mPropertiesWithNoAutoGet.count(property) == 0; +} + }; diff --git a/vehicle_network_service/VehiclePropertyAccessControl.h b/vehicle_network_service/VehiclePropertyAccessControl.h index c32209b16d..c079c935d1 100644 --- a/vehicle_network_service/VehiclePropertyAccessControl.h +++ b/vehicle_network_service/VehiclePropertyAccessControl.h @@ -21,6 +21,7 @@ #include <libxml/parser.h> #include <libxml/tree.h> #include <map> +#include <set> #include <string> #include <private/android_filesystem_config.h> #include <vehicle-internal.h> @@ -38,6 +39,7 @@ public: ~VehiclePropertyAccessControl(); bool init(); bool testAccess(int32_t property, int32_t uid, bool isWrite); + bool isAutoGetEnabled(int32_t property); void dump(String8& msg); // protected for testing protected: @@ -56,6 +58,7 @@ protected: // // So "property" is used to find "uid" and "uid" is used to find "access". std::map<int32_t, std::map<int32_t, int32_t>*> mVehicleAccessControlMap; + std::set<int32_t> mPropertiesWithNoAutoGet; }; }; diff --git a/vns_policy/vns_policy.xml b/vns_policy/vns_policy.xml index 75f9bb5607..c20ac664fe 100644 --- a/vns_policy/vns_policy.xml +++ b/vns_policy/vns_policy.xml @@ -130,11 +130,11 @@ <UID name="AID_AUDIOSERVER" access="r" value="1041"/> </PROPERTY> - <PROPERTY name="VEHICLE_PROPERTY_AUDIO_VOLUME" value = "0x00000901"> + <PROPERTY name="VEHICLE_PROPERTY_AUDIO_VOLUME" value = "0x00000901" no_auto_get="true"> <UID name="AID_SYSTEM" access="rw" value="1000"/> </PROPERTY> - <PROPERTY name="VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT" value = "0x00000902"> + <PROPERTY name="VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT" value = "0x00000902" no_auto_get="true"> <UID name="AID_SYSTEM" access="rw" value="1000"/> </PROPERTY> @@ -146,6 +146,10 @@ <UID name="AID_SYSTEM" access="r" value="1000"/> </PROPERTY> + <PROPERTY name="VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT" value = "0x00000905"> + <UID name="AID_SYSTEM" access="rw" value="1000"/> + </PROPERTY> + <PROPERTY name="VEHICLE_PROPERTY_AP_POWER_STATE" value = "0x00000A00"> <UID name="AID_SYSTEM" access="rw" value="1000"/> </PROPERTY> |