[automerger skipped] DO NOT MERGE: Prevent cluster service crash if video codec initialization fails am: 91e0357069 -s ours am: e268ad6100 am: 7e385ab97f -s ours
am skip reason: subject contains skip directive
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Car/Cluster/+/11977380
Change-Id: I8b521a121df43f26529c03f9b7fd58a5e3903251
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..2103020
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,54 @@
+// 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.
+//
+//
+
+android_app {
+ name: "DirectRenderingCluster",
+
+ srcs: ["src/**/*.java"],
+
+ platform_apis: true,
+
+ // Each update should be signed by OEMs
+ certificate: "platform",
+ privileged: true,
+
+ optimize: {
+ proguard_flags_files: ["proguard.flags"],
+ enabled: false,
+ },
+
+ resource_dirs: ["res"],
+
+ static_libs: [
+ "android.car.cluster.navigation",
+ "androidx.legacy_legacy-support-v4",
+ "androidx-constraintlayout_constraintlayout",
+ "car-arch-common",
+ "car-media-common",
+ "car-telephony-common",
+ "car-apps-common",
+ ],
+
+ libs: ["android.car"],
+
+ required: ["privapp_whitelist_android.car.cluster"],
+
+ product_variables: {
+ pdk: {
+ enabled: false,
+ },
+ },
+}
diff --git a/Android.mk b/Android.mk
deleted file mode 100644
index 8da51c3..0000000
--- a/Android.mk
+++ /dev/null
@@ -1,60 +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.
-#
-#
-ifneq ($(TARGET_BUILD_PDK), true)
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := DirectRenderingCluster
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-# 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_USE_AAPT2 := true
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android.car.cluster.navigation \
- androidx.car_car-cluster
-
-LOCAL_JAVA_LIBRARIES += android.car
-LOCAL_STATIC_ANDROID_LIBRARIES += \
- androidx.legacy_legacy-support-v4 \
- androidx-constraintlayout_constraintlayout \
- car-arch-common \
- car-media-common \
- car-telephony-common \
- car-apps-common
-
-LOCAL_REQUIRED_MODULES := privapp_whitelist_android.car.cluster
-
-include $(BUILD_PACKAGE)
-
-# Use the following include to make our test apk.
-ifeq (,$(ONE_SHOT_MAKEFILE))
-include $(call all-makefiles-under,$(LOCAL_PATH))
-endif
-
-endif
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7dc2bb6..1cac376 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -16,7 +16,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="android.car.cluster"
- android:sharedUserId="android.uid.system">
+ coreApp="true"
+ android:process="android.car.cluster"
+ android:sharedUserId="android.uid.cluster">
<uses-sdk android:targetSdkVersion="25" android:minSdkVersion="25"/>
@@ -67,6 +69,7 @@
android:exported="false"
android:showForAllUsers="true"
android:theme="@style/Theme.ClusterTheme">
+ <meta-data android:name="distractionOptimized" android:value="true"/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.DEFAULT"/>
@@ -78,8 +81,8 @@
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:launchMode="singleInstance"
android:resizeableActivity="true"
- android:permission="android.car.permission.CAR_DISPLAY_IN_CLUSTER"
android:allowEmbedded="true">
+ <meta-data android:name="distractionOptimized" android:value="true"/>
</activity>
</application>
</manifest>
diff --git a/res/layout/include_navigation_state.xml b/res/layout/include_navigation_state.xml
index 5cd6ff5..d5f227c 100644
--- a/res/layout/include_navigation_state.xml
+++ b/res/layout/include_navigation_state.xml
@@ -28,7 +28,8 @@
android:id="@+id/section_service_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:visibility="invisible">
<TextView
android:id="@+id/service_status"
diff --git a/src/android/car/cluster/ActivityMonitor.java b/src/android/car/cluster/ActivityMonitor.java
index 28c8147..f61572c 100644
--- a/src/android/car/cluster/ActivityMonitor.java
+++ b/src/android/car/cluster/ActivityMonitor.java
@@ -166,6 +166,9 @@
}
List<StackInfo> infos = mActivityManager.getAllStackInfos();
for (StackInfo info : infos) {
+ if (!info.visible) {
+ continue;
+ }
Set<ActivityListener> listeners = mListeners.get(info.displayId);
if (listeners != null && !listeners.isEmpty()) {
for (ActivityListener listener : listeners) {
diff --git a/src/android/car/cluster/ClusterDisplayProvider.java b/src/android/car/cluster/ClusterDisplayProvider.java
index 78be39b..4050099 100644
--- a/src/android/car/cluster/ClusterDisplayProvider.java
+++ b/src/android/car/cluster/ClusterDisplayProvider.java
@@ -16,140 +16,111 @@
package android.car.cluster;
-import android.annotation.Nullable;
+import android.car.Car;
+import android.car.CarOccupantZoneManager;
+import android.car.CarOccupantZoneManager.OccupantZoneInfo;
import android.content.Context;
-import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
-import android.os.SystemProperties;
-import android.text.TextUtils;
import android.util.Log;
import android.view.Display;
-import android.view.DisplayAddress;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
/**
* This class provides a display for instrument cluster renderer.
* <p>
* By default it will try to provide physical secondary display if it is connected, if secondary
- * display is not connected during creation of this class then it will start networked virtual
- * display and listens for incoming connections.
- *
- * @see {@link NetworkedVirtualDisplay}
+ * display is not connected during creation of this class then it will wait for the display will
+ * be added.
*/
public class ClusterDisplayProvider {
private static final String TAG = "Cluster.DisplayProvider";
-
- private static final String RO_CLUSTER_DISPLAY_PORT = "ro.car.cluster.displayport";
- private static final String PERSIST_CLUSTER_DISPLAY_PORT =
- "persist.car.cluster.displayport";
- private static final int NETWORKED_DISPLAY_WIDTH = 1280;
- private static final int NETWORKED_DISPLAY_HEIGHT = 720;
- private static final int NETWORKED_DISPLAY_DPI = 320;
+ private static final boolean DEBUG = false;
private final DisplayListener mListener;
- private final DisplayManager mDisplayManager;
+ private final Car mCar;
+ private CarOccupantZoneManager mOccupantZoneManager;
- private NetworkedVirtualDisplay mNetworkedVirtualDisplay;
- private int mClusterDisplayId = -1;
+ private int mClusterDisplayId = Display.INVALID_DISPLAY;
ClusterDisplayProvider(Context context, DisplayListener clusterDisplayListener) {
mListener = clusterDisplayListener;
- mDisplayManager = context.getSystemService(DisplayManager.class);
+ mCar = Car.createCar(context, null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
+ (car, ready) -> {
+ if (!ready) return;
+ initClusterDisplayProvider(context, (CarOccupantZoneManager) car.getCarManager(
+ Car.CAR_OCCUPANT_ZONE_SERVICE));
+ });
+ }
- Display clusterDisplay = getInstrumentClusterDisplay(mDisplayManager);
+ void release() {
+ if (mCar != null && mCar.isConnected()) {
+ mCar.disconnect();
+ }
+ }
+
+ private void initClusterDisplayProvider(
+ Context context, CarOccupantZoneManager occupantZoneManager) {
+ Preconditions.checkArgument(
+ occupantZoneManager != null,"Can't get CarOccupantZoneManager");
+ mOccupantZoneManager = occupantZoneManager;
+ checkClusterDisplayAdded();
+ mOccupantZoneManager.registerOccupantZoneConfigChangeListener(
+ new ClusterDisplayChangeListener());
+ }
+
+ private void checkClusterDisplayAdded() {
+ Display clusterDisplay = getClusterDisplay();
if (clusterDisplay != null) {
Log.i(TAG, String.format("Found display: %s (id: %d, owner: %s)",
clusterDisplay.getName(), clusterDisplay.getDisplayId(),
clusterDisplay.getOwnerPackageName()));
mClusterDisplayId = clusterDisplay.getDisplayId();
- clusterDisplayListener.onDisplayAdded(clusterDisplay.getDisplayId());
- trackClusterDisplay(null /* no need to track display by name */);
- } else {
- Log.i(TAG, "No physical cluster display found, starting network display");
- setupNetworkDisplay(context);
+ mListener.onDisplayAdded(clusterDisplay.getDisplayId());
}
}
- private void setupNetworkDisplay(Context context) {
- mNetworkedVirtualDisplay = new NetworkedVirtualDisplay(context,
- NETWORKED_DISPLAY_WIDTH, NETWORKED_DISPLAY_HEIGHT, NETWORKED_DISPLAY_DPI);
- String displayName = mNetworkedVirtualDisplay.start();
- trackClusterDisplay(displayName);
- }
-
- private void trackClusterDisplay(@Nullable String displayName) {
- mDisplayManager.registerDisplayListener(new DisplayListener() {
- @Override
- public void onDisplayAdded(int displayId) {
- boolean clusterDisplayAdded = false;
-
- if (displayName == null && mClusterDisplayId == -1) {
- mClusterDisplayId = displayId;
- clusterDisplayAdded = true;
- } else {
- Display display = mDisplayManager.getDisplay(displayId);
- if (display != null && TextUtils.equals(display.getName(), displayName)) {
- mClusterDisplayId = displayId;
- clusterDisplayAdded = true;
- }
- }
-
- if (clusterDisplayAdded) {
- mListener.onDisplayAdded(displayId);
- }
- }
-
- @Override
- public void onDisplayRemoved(int displayId) {
- if (displayId == mClusterDisplayId) {
- mClusterDisplayId = -1;
- mListener.onDisplayRemoved(displayId);
- }
- }
-
- @Override
- public void onDisplayChanged(int displayId) {
- if (displayId == mClusterDisplayId) {
- mListener.onDisplayChanged(displayId);
- }
- }
-
- }, null);
- }
-
- private static Display getInstrumentClusterDisplay(DisplayManager displayManager) {
- Display[] displays = displayManager.getDisplays();
- Log.d(TAG, "There are currently " + displays.length + " displays connected.");
-
- final int displayPortPrimary = 0; // primary port should not be instrument cluster.
- int displayPort = SystemProperties.getInt(PERSIST_CLUSTER_DISPLAY_PORT,
- displayPortPrimary);
- if (displayPort == displayPortPrimary) {
- displayPort = SystemProperties.getInt(RO_CLUSTER_DISPLAY_PORT,
- displayPortPrimary);
- if (displayPort == displayPortPrimary) {
- return null;
+ private Display getClusterDisplay() {
+ List<OccupantZoneInfo> zones = mOccupantZoneManager.getAllOccupantZones();
+ int zones_size = zones.size();
+ for (int i = 0; i < zones_size; ++i) {
+ OccupantZoneInfo zone = zones.get(i);
+ // Assumes that a Car has only one driver.
+ if (zone.occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) {
+ return mOccupantZoneManager.getDisplayForOccupant(
+ zone, CarOccupantZoneManager.DISPLAY_TYPE_INSTRUMENT_CLUSTER);
}
}
- // match port for system display ( = null getOwnerPackageName())
- // with separate check for main display as main display should be never picked up.
- for (Display display : displays) {
- if (display.getDisplayId() != Display.DEFAULT_DISPLAY
- && display.getOwnerPackageName() == null
- && display.getAddress() != null
- && display.getAddress() instanceof DisplayAddress.Physical) {
- final byte port = ((DisplayAddress.Physical) display.getAddress()).getPort();
- if (displayPort == port) {
- return display;
- }
- }
- }
+ Log.e(TAG, "Can't find the OccupantZoneInfo for driver");
return null;
}
+ private final class ClusterDisplayChangeListener implements
+ CarOccupantZoneManager.OccupantZoneConfigChangeListener {
+ @Override
+ public void onOccupantZoneConfigChanged(int changeFlags) {
+ if (DEBUG) Log.d(TAG, "onOccupantZoneConfigChanged changeFlags=" + changeFlags);
+ if ((changeFlags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY) == 0) {
+ return;
+ }
+ if (mClusterDisplayId == Display.INVALID_DISPLAY) {
+ checkClusterDisplayAdded();
+ } else {
+ Display clusterDisplay = getClusterDisplay();
+ if (clusterDisplay == null) {
+ mListener.onDisplayRemoved(mClusterDisplayId);
+ mClusterDisplayId = Display.INVALID_DISPLAY;
+ }
+ }
+ }
+ }
+
@Override
public String toString() {
return getClass().getSimpleName() + "{"
+ " clusterDisplayId = " + mClusterDisplayId
+ "}";
}
-}
+}
\ No newline at end of file
diff --git a/src/android/car/cluster/ClusterRenderingService.java b/src/android/car/cluster/ClusterRenderingService.java
index 8d0cafa..acaa4b2 100644
--- a/src/android/car/cluster/ClusterRenderingService.java
+++ b/src/android/car/cluster/ClusterRenderingService.java
@@ -15,7 +15,6 @@
*/
package android.car.cluster;
-import static android.content.Intent.ACTION_USER_SWITCHED;
import static android.content.Intent.ACTION_USER_UNLOCKED;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.view.Display.INVALID_DISPLAY;
@@ -24,7 +23,7 @@
import android.app.ActivityManager;
import android.app.ActivityOptions;
-import android.car.CarNotConnectedException;
+import android.car.Car;
import android.car.cluster.navigation.NavigationState.NavigationStateProto;
import android.car.cluster.renderer.InstrumentClusterRenderingService;
import android.car.cluster.renderer.NavigationRenderer;
@@ -50,8 +49,6 @@
import android.view.InputDevice;
import android.view.KeyEvent;
-import androidx.versionedparcelable.ParcelUtils;
-
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.FileDescriptor;
@@ -70,7 +67,6 @@
private static final String TAG = "Cluster.Service";
private static final int NAVIGATION_ACTIVITY_RETRY_INTERVAL_MS = 1000;
- static final int NAV_STATE_EVENT_ID = 1;
static final String LOCAL_BINDING_ACTION = "local";
static final String NAV_STATE_PROTO_BUNDLE_KEY = "navstate2";
@@ -124,23 +120,17 @@
};
public void setActivityLaunchOptions(int displayId, ClusterActivityState state) {
- try {
- ActivityOptions options = displayId != INVALID_DISPLAY
- ? ActivityOptions.makeBasic().setLaunchDisplayId(displayId)
- : null;
- setClusterActivityLaunchOptions(CarInstrumentClusterManager.CATEGORY_NAVIGATION,
- options);
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, String.format("activity options set: %s (displayeId: %d)",
- options, options.getLaunchDisplayId()));
- }
- setClusterActivityState(CarInstrumentClusterManager.CATEGORY_NAVIGATION,
- state.toBundle());
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, String.format("activity state set: %s", state));
- }
- } catch (CarNotConnectedException ex) {
- Log.e(TAG, "Unable to update service", ex);
+ ActivityOptions options = displayId != INVALID_DISPLAY
+ ? ActivityOptions.makeBasic().setLaunchDisplayId(displayId)
+ : null;
+ setClusterActivityLaunchOptions(options);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, String.format("activity options set: %s (displayeId: %d)",
+ options, options != null ? options.getLaunchDisplayId() : -1));
+ }
+ setClusterActivityState(state);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, String.format("activity state set: %s", state));
}
}
@@ -182,6 +172,7 @@
public void onDestroy() {
super.onDestroy();
mUserReceiver.unregister(this);
+ mDisplayProvider.release();
}
private void launchMainActivity() {
@@ -217,16 +208,18 @@
Log.e(TAG, "Failed to resolve the navigation activity");
return null;
}
- Rect displaySize = new Rect(0, 0, 320, 240); // Arbitrary size, better than nothing.
- DisplayManager dm = (DisplayManager) getSystemService(DisplayManager.class);
+ Rect displaySize = new Rect(0, 0, 240, 320); // Arbitrary size, better than nothing.
+ DisplayManager dm = getSystemService(DisplayManager.class);
Display display = dm.getDisplay(displayId);
if (display != null) {
display.getRectSize(displaySize);
}
+ setClusterActivityState(ClusterActivityState.create(/* visible= */ true,
+ /* unobscuredBounds= */ new Rect(0, 0, 240, 320)));
return new Intent(Intent.ACTION_MAIN)
.setComponent(component)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .putExtra(CarInstrumentClusterManager.KEY_EXTRA_ACTIVITY_STATE,
+ .putExtra(Car.CAR_EXTRA_CLUSTER_ACTIVITY_STATE,
ClusterActivityState.create(/* visible= */ true,
/* unobscuredBounds= */ displaySize).toBundle());
}
@@ -262,39 +255,27 @@
}
@Override
- public void onEvent(int eventType, Bundle bundle) {
+ public void onNavigationStateChanged(Bundle bundle) {
StringBuilder bundleSummary = new StringBuilder();
- if (eventType == NAV_STATE_EVENT_ID) {
- // Required to prevent backwards compatibility crash with old map providers
- // sending androidx.versionedparcelables
- bundle.setClassLoader(ParcelUtils.class.getClassLoader());
-
- // Attempt to read proto byte array
- byte[] protoBytes = bundle.getByteArray(NAV_STATE_PROTO_BUNDLE_KEY);
- if (protoBytes != null) {
- try {
- NavigationStateProto navState = NavigationStateProto.parseFrom(
- protoBytes);
- bundleSummary.append(navState.toString());
- // Update clients
- broadcastClientEvent(
- client -> client.onNavigationStateChange(navState));
- } catch (InvalidProtocolBufferException e) {
- Log.e(TAG, "Error parsing navigation state proto", e);
- }
- } else {
- Log.e(TAG, "Received nav state byte array is null");
+ // Attempt to read proto byte array
+ byte[] protoBytes = bundle.getByteArray(NAV_STATE_PROTO_BUNDLE_KEY);
+ if (protoBytes != null) {
+ try {
+ NavigationStateProto navState = NavigationStateProto.parseFrom(
+ protoBytes);
+ bundleSummary.append(navState.toString());
+
+ // Update clients
+ broadcastClientEvent(
+ client -> client.onNavigationStateChange(navState));
+ } catch (InvalidProtocolBufferException e) {
+ Log.e(TAG, "Error parsing navigation state proto", e);
}
} else {
- for (String key : bundle.keySet()) {
- bundleSummary.append(key);
- bundleSummary.append("=");
- bundleSummary.append(bundle.get(key));
- bundleSummary.append(" ");
- }
+ Log.e(TAG, "Received nav state byte array is null");
}
- Log.d(TAG, "onEvent(" + eventType + ", " + bundleSummary + ")");
+ Log.d(TAG, "onNavigationStateChanged(" + bundleSummary + ")");
}
};
@@ -377,14 +358,9 @@
case "setUnobscuredArea": {
if (args.length > 5) {
- Rect unobscuredArea = new Rect(parseInt(args[2]), parseInt(args[3]),
- parseInt(args[4]), parseInt(args[5]));
- try {
- setClusterActivityState(args[1],
- ClusterActivityState.create(true, unobscuredArea).toBundle());
- } catch (CarNotConnectedException e) {
- Log.i(TAG, "Failed to set activity state.", e);
- }
+ setClusterActivityState(ClusterActivityState.create(true,
+ new Rect(parseInt(args[2]), parseInt(args[3]),
+ parseInt(args[4]), parseInt(args[5]))));
} else {
Log.i(TAG, "wrong format, expected: category left top right bottom");
}
diff --git a/src/android/car/cluster/FakeFreeNavigationActivity.java b/src/android/car/cluster/FakeFreeNavigationActivity.java
index a836dc0..7b7a46b 100644
--- a/src/android/car/cluster/FakeFreeNavigationActivity.java
+++ b/src/android/car/cluster/FakeFreeNavigationActivity.java
@@ -17,6 +17,7 @@
package android.car.cluster;
import android.app.Activity;
+import android.car.Car;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Bundle;
@@ -54,10 +55,9 @@
Log.w(TAG, "Received a null intent");
return;
}
- Bundle bundle = intent.getBundleExtra(CarInstrumentClusterManager.KEY_EXTRA_ACTIVITY_STATE);
+ Bundle bundle = intent.getBundleExtra(Car.CAR_EXTRA_CLUSTER_ACTIVITY_STATE);
if (bundle == null) {
- Log.w(TAG, "Received an intent without " + CarInstrumentClusterManager
- .KEY_EXTRA_ACTIVITY_STATE);
+ Log.w(TAG, "Received an intent without " + Car.CAR_EXTRA_CLUSTER_ACTIVITY_STATE);
return;
}
ClusterActivityState state = ClusterActivityState.fromBundle(bundle);
diff --git a/src/android/car/cluster/ImageResolver.java b/src/android/car/cluster/ImageResolver.java
index 2e1ada9..cb5c19b 100644
--- a/src/android/car/cluster/ImageResolver.java
+++ b/src/android/car/cluster/ImageResolver.java
@@ -44,12 +44,12 @@
/**
* Returns a {@link Bitmap} given a request Uri and dimensions
*/
- Bitmap getBitmap(Uri uri, int width, int height);
+ Bitmap getBitmap(@NonNull Uri uri, int width, int height);
/**
* Returns a {@link Bitmap} given a request Uri, dimensions, and offLanesAlpha value
*/
- Bitmap getBitmap(Uri uri, int width, int height, float offLanesAlpha);
+ Bitmap getBitmap(@NonNull Uri uri, int width, int height, float offLanesAlpha);
}
/**
diff --git a/src/android/car/cluster/LaneView.java b/src/android/car/cluster/LaneView.java
index 2a5ca58..7d48e28 100644
--- a/src/android/car/cluster/LaneView.java
+++ b/src/android/car/cluster/LaneView.java
@@ -70,7 +70,7 @@
public void setLanes(ImageReference imageReference, ImageResolver imageResolver) {
imageResolver
- .getBitmap(imageReference, 0, getHeight())
+ .getBitmap(imageReference, 0, getHeight(), 0.5f)
.thenAccept(bitmap -> {
mHandler.post(() -> {
removeAllViews();
diff --git a/src/android/car/cluster/LoggingClusterRenderingService.java b/src/android/car/cluster/LoggingClusterRenderingService.java
index 89990f9..dfed06c 100644
--- a/src/android/car/cluster/LoggingClusterRenderingService.java
+++ b/src/android/car/cluster/LoggingClusterRenderingService.java
@@ -15,6 +15,7 @@
*/
package android.car.cluster;
+import android.car.cluster.navigation.NavigationState.NavigationStateProto;
import android.car.cluster.renderer.InstrumentClusterRenderingService;
import android.car.cluster.renderer.NavigationRenderer;
import android.car.navigation.CarNavigationInstrumentCluster;
@@ -32,7 +33,6 @@
public class LoggingClusterRenderingService extends InstrumentClusterRenderingService {
private static final String TAG = LoggingClusterRenderingService.class.getSimpleName();
private static final String NAV_STATE_PROTO_BUNDLE_KEY = "navstate2";
- private static final int NAV_STATE_EVENT_ID = 1;
@Override
public NavigationRenderer getNavigationRenderer() {
@@ -48,39 +48,29 @@
}
@Override
- public void onEvent(int eventType, Bundle bundle) {
+ public void onNavigationStateChanged(Bundle bundle) {
StringBuilder bundleSummary = new StringBuilder();
- if (eventType == NAV_STATE_EVENT_ID) {
- // Attempt to read proto byte array
- byte[] protoBytes = bundle.getByteArray(NAV_STATE_PROTO_BUNDLE_KEY);
- if (protoBytes != null) {
- try {
- android.car.cluster.navigation.NavigationState.NavigationStateProto
- navState =
- android.car.cluster.navigation.NavigationState.NavigationStateProto.parseFrom(
- protoBytes);
- bundleSummary.append(navState.toString());
- // Sending broadcast for testing.
- Intent intent = new Intent(
- "android.car.cluster.NAVIGATION_STATE_UPDATE");
- intent.putExtra(NAV_STATE_PROTO_BUNDLE_KEY, bundle);
- sendBroadcastAsUser(intent, UserHandle.ALL);
- } catch (InvalidProtocolBufferException e) {
- Log.e(TAG, "Error parsing navigation state proto", e);
- }
- } else {
- Log.e(TAG, "Received nav state byte array is null");
+ // Attempt to read proto byte array
+ byte[] protoBytes = bundle.getByteArray(NAV_STATE_PROTO_BUNDLE_KEY);
+ if (protoBytes != null) {
+ try {
+ NavigationStateProto navState = NavigationStateProto.parseFrom(protoBytes);
+ bundleSummary.append(navState.toString());
+
+ // Sending broadcast for testing.
+ Intent intent = new Intent(
+ "android.car.cluster.NAVIGATION_STATE_UPDATE");
+ intent.putExtra(NAV_STATE_PROTO_BUNDLE_KEY, bundle);
+ sendBroadcastAsUser(intent, UserHandle.ALL);
+ } catch (InvalidProtocolBufferException e) {
+ Log.e(TAG, "Error parsing navigation state proto", e);
}
} else {
- for (String key : bundle.keySet()) {
- bundleSummary.append(key);
- bundleSummary.append("=");
- bundleSummary.append(bundle.get(key));
- bundleSummary.append(" ");
- }
+ Log.e(TAG, "Received nav state byte array is null");
}
- Log.i(TAG, "onEvent(" + eventType + ", " + bundleSummary + ")");
+
+ Log.i(TAG, "onEvent(" + bundleSummary + ")");
}
};
diff --git a/src/android/car/cluster/MainClusterActivity.java b/src/android/car/cluster/MainClusterActivity.java
index 5e19a42..9c76d37 100644
--- a/src/android/car/cluster/MainClusterActivity.java
+++ b/src/android/car/cluster/MainClusterActivity.java
@@ -16,6 +16,8 @@
package android.car.cluster;
import static android.car.cluster.ClusterRenderingService.LOCAL_BINDING_ACTION;
+import static android.content.Intent.ACTION_SCREEN_OFF;
+import static android.content.Intent.ACTION_USER_PRESENT;
import static android.content.Intent.ACTION_USER_SWITCHED;
import static android.content.Intent.ACTION_USER_UNLOCKED;
import static android.content.PermissionChecker.PERMISSION_GRANTED;
@@ -54,6 +56,7 @@
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.lifecycle.LiveData;
+import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import androidx.viewpager.widget.ViewPager;
@@ -110,6 +113,7 @@
private static final int NAVIGATION_ACTIVITY_RETRY_INTERVAL_MS = 1000;
private static final int NAVIGATION_ACTIVITY_RELAUNCH_DELAY_MS = 5000;
+ private final UserReceiver mUserReceiver = new UserReceiver();
private ActivityMonitor mActivityMonitor = new ActivityMonitor();
private final Handler mHandler = new Handler();
private final Runnable mRetryLaunchNavigationActivity = this::tryLaunchNavigationActivity;
@@ -172,6 +176,28 @@
mClusterViewModel.setCurrentNavigationActivity(activity);
};
+ /**
+ * On user switch the navigation application must be re-launched on the new user. Otherwise
+ * the navigation fragment will keep showing the application on the previous user.
+ * {@link MainClusterActivity} is shared between all users (it is not restarted on user switch)
+ */
+ private class UserReceiver extends BroadcastReceiver {
+ void register(Context context) {
+ IntentFilter intentFilter = new IntentFilter(ACTION_USER_UNLOCKED);
+ context.registerReceiverForAllUsers(this, intentFilter, null, null);
+ }
+ void unregister(Context context) {
+ context.unregisterReceiver(this);
+ }
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Broadcast received: " + intent);
+ }
+ tryLaunchNavigationActivity();
+ }
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -202,7 +228,24 @@
mOrderToFacet.get(NAV_FACET_ID).mButton.requestFocus();
mNavStateController = new NavStateController(findViewById(R.id.navigation_state));
- mClusterViewModel = ViewModelProviders.of(this).get(ClusterViewModel.class);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_USER_PRESENT);
+ filter.addAction(ACTION_SCREEN_OFF);
+ registerReceiver(new BroadcastReceiver(){
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)){
+ Log.d(TAG, "ACTION_SCREEN_OFF");
+ mNavStateController.hideNavigationStateInfo();
+ }
+ else if (intent.getAction().equals(Intent.ACTION_USER_PRESENT)) {
+ Log.d(TAG, "ACTION_USER_PRESENT");
+ mNavStateController.showNavigationStateInfo();
+ }
+ }
+ }, filter);
+
+ mClusterViewModel = new ViewModelProvider(this).get(ClusterViewModel.class);
mClusterViewModel.getNavigationFocus().observe(this, focus -> {
// If focus is lost, we launch the default navigation activity again.
if (!focus) {
@@ -230,9 +273,11 @@
mActivityMonitor.start();
+ mUserReceiver.register(this);
+
InMemoryPhoneBook.init(this);
- PhoneFragmentViewModel phoneViewModel = ViewModelProviders.of(this).get(
+ PhoneFragmentViewModel phoneViewModel = new ViewModelProvider(this).get(
PhoneFragmentViewModel.class);
phoneViewModel.setPhoneStateCallback(new PhoneFragmentViewModel.PhoneStateCallback() {
@@ -263,6 +308,7 @@
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
+ mUserReceiver.unregister(this);
mActivityMonitor.stop();
if (mService != null) {
mService.unregisterClient(this);
@@ -394,7 +440,7 @@
Intent intent = new Intent(Intent.ACTION_MAIN)
.setComponent(navigationActivity)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .putExtra(CarInstrumentClusterManager.KEY_EXTRA_ACTIVITY_STATE,
+ .putExtra(Car.CAR_EXTRA_CLUSTER_ACTIVITY_STATE,
activityState.toBundle());
Log.d(TAG, "Launching: " + intent + " on display: " + mNavigationDisplay.mDisplayId);
@@ -420,7 +466,7 @@
* <ul>
* <li>Dynamically detect what's the default navigation activity the user has selected on the
* head unit, and obtain the activity marked with
- * {@link CarInstrumentClusterManager#CATEGORY_NAVIGATION} from the same package.
+ * {@link Car#CAR_CATEGORY_NAVIGATION} from the same package.
* <li>Let the user select one from settings.
* </ul>
*/
@@ -442,16 +488,6 @@
if (navigationApp == null) {
return null;
}
-
- // Check that it has the right permissions
- if (pm.checkPermission(Car.PERMISSION_CAR_DISPLAY_IN_CLUSTER, navigationApp.activityInfo
- .packageName) != PERMISSION_GRANTED) {
- Log.i(TAG, String.format("Package '%s' doesn't have permission %s",
- navigationApp.activityInfo.packageName,
- Car.PERMISSION_CAR_DISPLAY_IN_CLUSTER));
- return null;
- }
-
return new ComponentName(navigationApp.activityInfo.packageName,
navigationApp.activityInfo.name);
} catch (URISyntaxException ex) {
diff --git a/src/android/car/cluster/MusicFragment.java b/src/android/car/cluster/MusicFragment.java
index 34a7ded..540db4d 100644
--- a/src/android/car/cluster/MusicFragment.java
+++ b/src/android/car/cluster/MusicFragment.java
@@ -15,6 +15,8 @@
*/
package android.car.cluster;
+import static android.car.media.CarMediaManager.MEDIA_SOURCE_MODE_PLAYBACK;
+
import android.os.Bundle;
import android.util.Size;
import android.view.LayoutInflater;
@@ -51,9 +53,10 @@
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
FragmentActivity activity = requireActivity();
- PlaybackViewModel playbackViewModel = PlaybackViewModel.get(activity.getApplication());
+ PlaybackViewModel playbackViewModel =
+ PlaybackViewModel.get(activity.getApplication(), MEDIA_SOURCE_MODE_PLAYBACK);
MediaSourceViewModel mMediaSourceViewModel = MediaSourceViewModel.get(
- activity.getApplication());
+ activity.getApplication(), MEDIA_SOURCE_MODE_PLAYBACK);
MusicFragmentViewModel innerViewModel = ViewModelProviders.of(activity).get(
MusicFragmentViewModel.class);
diff --git a/src/android/car/cluster/MusicFragmentViewModel.java b/src/android/car/cluster/MusicFragmentViewModel.java
index 769cac8..9fb0fa4 100644
--- a/src/android/car/cluster/MusicFragmentViewModel.java
+++ b/src/android/car/cluster/MusicFragmentViewModel.java
@@ -48,7 +48,7 @@
mMediaSourceViewModel = mediaSourceViewModel;
mMediaSource = mMediaSourceViewModel.getPrimaryMediaSource();
mAppName = mapNonNull(mMediaSource, MediaSource::getDisplayName);
- mAppIcon = mapNonNull(mMediaSource, MediaSource::getRoundPackageIcon);
+ mAppIcon = mapNonNull(mMediaSource, MediaSource::getCroppedPackageIcon);
}
LiveData<CharSequence> getAppName() {
diff --git a/src/android/car/cluster/NavStateController.java b/src/android/car/cluster/NavStateController.java
index facab51..fe4d724 100644
--- a/src/android/car/cluster/NavStateController.java
+++ b/src/android/car/cluster/NavStateController.java
@@ -15,6 +15,10 @@
*/
package android.car.cluster;
+import static android.car.cluster.navigation.NavigationState.NavigationStateProto.ServiceStatus.NORMAL;
+import static android.car.cluster.navigation.NavigationState.NavigationStateProto.ServiceStatus.REROUTING;
+import static android.car.cluster.navigation.NavigationState.NavigationStateProto.ServiceStatus.SERVICE_STATUS_UNSPECIFIED;
+
import android.annotation.Nullable;
import android.car.cluster.navigation.NavigationState.Destination;
import android.car.cluster.navigation.NavigationState.Destination.Traffic;
@@ -44,6 +48,7 @@
private Handler mHandler = new Handler();
+ private View mNavigationState;
private LinearLayout mSectionManeuver;
private LinearLayout mSectionNavigation;
private LinearLayout mSectionServiceStatus;
@@ -66,6 +71,7 @@
* @param container {@link View} containing the navigation state views
*/
public NavStateController(View container) {
+ mNavigationState = container;
mSectionManeuver = container.findViewById(R.id.section_maneuver);
mSectionNavigation = container.findViewById(R.id.section_navigation);
mSectionServiceStatus = container.findViewById(R.id.section_service_status);
@@ -82,6 +88,14 @@
mContext = container.getContext();
}
+ public void hideNavigationStateInfo() {
+ mNavigationState.setVisibility(View.INVISIBLE);
+ }
+
+ public void showNavigationStateInfo() {
+ mNavigationState.setVisibility(View.VISIBLE);
+ }
+
public void setImageResolver(@Nullable ImageResolver imageResolver) {
mImageResolver = imageResolver;
}
@@ -98,7 +112,13 @@
return;
}
- if (state.getServiceStatus() == NavigationStateProto.ServiceStatus.REROUTING) {
+ NavigationStateProto.ServiceStatus serviceStatus = state.getServiceStatus();
+ if (serviceStatus == SERVICE_STATUS_UNSPECIFIED) {
+ mSectionManeuver.setVisibility(View.INVISIBLE);
+ mSectionNavigation.setVisibility(View.INVISIBLE);
+ mSectionServiceStatus.setVisibility(View.INVISIBLE);
+ return;
+ } else if (serviceStatus == REROUTING) {
mSectionManeuver.setVisibility(View.INVISIBLE);
mSectionNavigation.setVisibility(View.INVISIBLE);
mSectionServiceStatus.setVisibility(View.VISIBLE);
@@ -115,8 +135,8 @@
Traffic traffic = destination != null ? destination.getTraffic() : null;
String eta = destination != null
? destination.getFormattedDurationUntilArrival().isEmpty()
- ? formatEta(destination.getEstimatedTimeAtArrival())
- : destination.getFormattedDurationUntilArrival()
+ ? formatEta(destination.getEstimatedTimeAtArrival())
+ : destination.getFormattedDurationUntilArrival()
: null;
mEta.setText(eta);
mEta.setTextColor(getTrafficColor(traffic));
diff --git a/src/android/car/cluster/NetworkedVirtualDisplay.java b/src/android/car/cluster/NetworkedVirtualDisplay.java
deleted file mode 100644
index 56121d0..0000000
--- a/src/android/car/cluster/NetworkedVirtualDisplay.java
+++ /dev/null
@@ -1,452 +0,0 @@
-/*
- * Copyright (C) 2018 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;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManager.DisplayListener;
-import android.hardware.display.VirtualDisplay;
-import android.media.MediaCodec;
-import android.media.MediaCodec.BufferInfo;
-import android.media.MediaCodec.CodecException;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecInfo.CodecProfileLevel;
-import android.media.MediaFormat;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-import android.view.Display;
-import android.view.Surface;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.RandomAccessFile;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.nio.ByteBuffer;
-import java.util.UUID;
-
-/**
- * This class encapsulates all work related to managing networked virtual display.
- * <p>
- * It opens a socket and listens on port {@code PORT} for connections, or the emulator pipe. Once
- * connection is established it creates virtual display and media encoder and starts streaming video
- * to that socket. If the receiving part is disconnected, it will keep port open and virtual
- * display won't be destroyed.
- */
-public class NetworkedVirtualDisplay {
- private static final String TAG = "Cluster." + NetworkedVirtualDisplay.class.getSimpleName();
-
- private final String mUniqueId = UUID.randomUUID().toString();
-
- private final DisplayManager mDisplayManager;
- private final int mWidth;
- private final int mHeight;
- private final int mDpi;
-
- private static final int FPS = 25;
- private static final int BITRATE = 6144000;
- private static final String MEDIA_FORMAT_MIMETYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
-
- public static final int MSG_START = 0;
- public static final int MSG_STOP = 1;
- public static final int MSG_SEND_FRAME = 2;
-
- private static final String PIPE_NAME = "pipe:qemud:carCluster";
- private static final String PIPE_DEVICE = "/dev/qemu_pipe";
-
- // Constants shared with emulator in car-cluster-widget.cpp
- public static final int PIPE_START = 1;
- public static final int PIPE_STOP = 2;
-
- private static final int PORT = 5151;
-
- private SenderThread mActiveThread;
- private HandlerThread mBroadcastThread = new HandlerThread("BroadcastThread");
-
- private VirtualDisplay mVirtualDisplay;
- private MediaCodec mVideoEncoder;
- private Handler mHandler;
- private byte[] mBuffer = null;
- private int mLastFrameLength = 0;
-
- private final DebugCounter mCounter = new DebugCounter();
-
- NetworkedVirtualDisplay(Context context, int width, int height, int dpi) {
- mDisplayManager = context.getSystemService(DisplayManager.class);
- mWidth = width;
- mHeight = height;
- mDpi = dpi;
-
- DisplayListener displayListener = new DisplayListener() {
- @Override
- public void onDisplayAdded(int i) {
- final Display display = mDisplayManager.getDisplay(i);
- if (display != null && getDisplayName().equals(display.getName())) {
- onVirtualDisplayReady(display);
- }
- }
-
- @Override
- public void onDisplayRemoved(int i) {}
-
- @Override
- public void onDisplayChanged(int i) {}
- };
-
- mDisplayManager.registerDisplayListener(displayListener, new Handler());
- }
-
- /**
- * Opens socket and creates virtual display asynchronously once connection established. Clients
- * of this class may subscribe to
- * {@link android.hardware.display.DisplayManager#registerDisplayListener(
- * DisplayListener, Handler)} to be notified when virtual display is created.
- * Note, that this method should be called only once.
- *
- * @return Unique display name associated with the instance of this class.
- *
- * @see {@link Display#getName()}
- *
- * @throws IllegalStateException thrown if networked display already started
- */
- public String start() {
- if (mBroadcastThread.isAlive()) {
- throw new IllegalStateException("Already started");
- }
-
- mBroadcastThread.start();
- mHandler = new BroadcastThreadHandler(mBroadcastThread.getLooper());
- mHandler.sendMessage(Message.obtain(mHandler, MSG_START));
- return getDisplayName();
- }
-
- public void release() {
- mHandler.sendMessage(Message.obtain(mHandler, MSG_STOP));
- mBroadcastThread.quitSafely();
-
- if (mVirtualDisplay != null) {
- mVirtualDisplay.setSurface(null);
- mVirtualDisplay.release();
- mVirtualDisplay = null;
- }
- }
-
- private String getDisplayName() {
- return "Cluster-" + mUniqueId;
- }
-
- private VirtualDisplay createVirtualDisplay() {
- Log.i(TAG, "createVirtualDisplay " + mWidth + "x" + mHeight +"@" + mDpi);
- return mDisplayManager.createVirtualDisplay(getDisplayName(), mWidth, mHeight, mDpi,
- null, 0 /* flags */, null, null );
- }
-
- private void onVirtualDisplayReady(Display display) {
- Log.i(TAG, "onVirtualDisplayReady, display: " + display);
- }
-
- private void startCasting(Handler handler) {
- Log.i(TAG, "Start casting...");
- if (mVideoEncoder != null) {
- Log.i(TAG, "Already started casting");
- return;
- }
- mVideoEncoder = createVideoStream(handler);
-
- if (mVirtualDisplay == null) {
- mVirtualDisplay = createVirtualDisplay();
- }
-
- mVirtualDisplay.setSurface(mVideoEncoder.createInputSurface());
- mVideoEncoder.start();
- Log.i(TAG, "Video encoder started");
- }
-
- private MediaCodec createVideoStream(Handler handler) {
- MediaCodec encoder;
- try {
- encoder = MediaCodec.createEncoderByType(MEDIA_FORMAT_MIMETYPE);
- } catch (IOException e) {
- Log.e(TAG, "Failed to create video encoder for " + MEDIA_FORMAT_MIMETYPE, e);
- return null;
- }
-
- encoder.setCallback(new MediaCodec.Callback() {
- @Override
- public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
- // Nothing to do
- }
-
- @Override
- public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index,
- @NonNull BufferInfo info) {
- mCounter.outputBuffers++;
- doOutputBufferAvailable(index, info);
- }
-
- @Override
- public void onError(@NonNull MediaCodec codec, @NonNull CodecException e) {
- Log.e(TAG, "onError, codec: " + codec, e);
- mCounter.bufferErrors++;
- stopCasting();
- startCasting(handler);
- }
-
- @Override
- public void onOutputFormatChanged(@NonNull MediaCodec codec,
- @NonNull MediaFormat format) {
- Log.i(TAG, "onOutputFormatChanged, codec: " + codec + ", format: " + format);
-
- }
- }, handler);
-
- configureVideoEncoder(encoder, mWidth, mHeight);
- return encoder;
- }
-
- private void doOutputBufferAvailable(int index, @NonNull BufferInfo info) {
- mHandler.removeMessages(MSG_SEND_FRAME);
-
- ByteBuffer encodedData = mVideoEncoder.getOutputBuffer(index);
- if (encodedData == null) {
- throw new RuntimeException("couldn't fetch buffer at index " + index);
- }
-
- if (info.size != 0) {
- encodedData.position(info.offset);
- encodedData.limit(info.offset + info.size);
- mLastFrameLength = encodedData.remaining();
- if (mBuffer == null || mBuffer.length < mLastFrameLength) {
- Log.i(TAG, "Allocating new buffer: " + mLastFrameLength);
- mBuffer = new byte[mLastFrameLength];
- }
- encodedData.get(mBuffer, 0, mLastFrameLength);
- mVideoEncoder.releaseOutputBuffer(index, false);
-
- // Send this frame asynchronously (avoiding blocking on the socket). We might miss
- // frames if the consumer is not fast enough, but this is acceptable.
- sendFrameAsync(0);
- } else {
- Log.e(TAG, "Skipping empty buffer");
- mVideoEncoder.releaseOutputBuffer(index, false);
- }
- }
-
- private void sendFrameAsync(long delayMs) {
- Message msg = mHandler.obtainMessage(MSG_SEND_FRAME);
- mHandler.sendMessageDelayed(msg, delayMs);
- }
-
- private void sendFrame(byte[] buf, int len) {
- if (mActiveThread != null) {
- mActiveThread.send(buf, len);
- }
- }
-
- private void stopCasting() {
- Log.i(TAG, "Stopping casting...");
-
- if (mVirtualDisplay != null) {
- Surface surface = mVirtualDisplay.getSurface();
- if (surface != null) surface.release();
- }
-
- if (mVideoEncoder != null) {
- // Releasing encoder as stop/start didn't work well (couldn't create or reuse input
- // surface).
- try {
- mVideoEncoder.stop();
- mVideoEncoder.release();
- } catch (IllegalStateException e) {
- // do nothing, already released
- }
- mVideoEncoder = null;
- }
- Log.i(TAG, "Casting stopped");
- }
-
- private class BroadcastThreadHandler extends Handler {
- private static final int MAX_FAIL_COUNT = 10;
- private int mFailConnectCounter;
-
- BroadcastThreadHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_START:
- Log.i(TAG, "Received start message");
-
- // Make sure mActiveThread cannot start multiple times
- if (mActiveThread != null) {
- Log.w(TAG, "Trying to start a running thread. Race condition may exist");
- break;
- }
-
- // Failure to connect to either pipe or network returns null
- if (mActiveThread == null) {
- mActiveThread = tryPipeConnect();
- }
- if (mActiveThread == null) {
- mActiveThread = tryNetworkConnect();
- }
- if (mActiveThread == null) {
- // When failed attempt limit is reached, clean up and quit this thread.
- mFailConnectCounter++;
- if (mFailConnectCounter >= MAX_FAIL_COUNT) {
- Log.e(TAG, "Too many failed connection attempts; aborting");
- release();
- throw new RuntimeException("Abort after failed connection attempts");
- }
- mHandler.sendMessage(Message.obtain(mHandler, MSG_START));
- break;
- }
-
- try {
- mFailConnectCounter = 0;
- mCounter.clientsConnected++;
- mActiveThread.start();
- startCasting(this);
- } catch (Exception e) {
- Log.e(TAG, "Failed to start thread", e);
- Log.e(TAG, "DebugCounter: " + mCounter);
- }
- break;
-
- case MSG_STOP:
- Log.i(TAG, "Received stop message");
- stopCasting();
- mCounter.clientsDisconnected++;
- if (mActiveThread != null) {
- mActiveThread.close();
- try {
- mActiveThread.join();
- } catch (InterruptedException e) {
- Log.e(TAG, "Waiting for active thread to close failed", e);
- }
- mActiveThread = null;
- }
- break;
-
- case MSG_SEND_FRAME:
- if (mActiveThread == null) {
- // Stop the chaining signal if there's no client to send to
- break;
- }
- sendFrame(mBuffer, mLastFrameLength);
- // We will keep sending last frame every second as a heartbeat.
- sendFrameAsync(1000L);
- break;
- }
- }
-
- // Returns null if can't establish pipe connection
- // Otherwise returns the corresponding client thread
- private PipeThread tryPipeConnect() {
- try {
- RandomAccessFile pipe = new RandomAccessFile(PIPE_DEVICE, "rw");
- byte[] temp = new byte[PIPE_NAME.length() + 1];
- temp[PIPE_NAME.length()] = 0;
- System.arraycopy(PIPE_NAME.getBytes(), 0, temp, 0, PIPE_NAME.length());
- pipe.write(temp);
-
- // At this point, the pipe exists, so we will just wait for a start signal
- // This is in case pipe still sends leftover stops from last instantiation
- int signal = pipe.read();
- while (signal != PIPE_START) {
- Log.i(TAG, "Received non-start signal: " + signal);
- signal = pipe.read();
- }
- return new PipeThread(mHandler, pipe);
- } catch (IOException e) {
- Log.e(TAG, "Failed to establish pipe connection", e);
- return null;
- }
- }
-
- // Returns null if can't establish network connection
- // Otherwise returns the corresponding client thread
- private SocketThread tryNetworkConnect() {
- try {
- ServerSocket serverSocket = new ServerSocket(PORT);
- Log.i(TAG, "Server socket opened");
- Socket socket = serverSocket.accept();
- socket.setTcpNoDelay(true);
- socket.setKeepAlive(true);
- socket.setSoLinger(true, 0);
-
- InputStream inputStream = socket.getInputStream();
- OutputStream outputStream = socket.getOutputStream();
-
- return new SocketThread(mHandler, serverSocket, inputStream, outputStream);
- } catch (IOException e) {
- Log.e(TAG, "Failed to establish network connection", e);
- return null;
- }
- }
- }
-
- private static void configureVideoEncoder(MediaCodec codec, int width, int height) {
- MediaFormat format = MediaFormat.createVideoFormat(MEDIA_FORMAT_MIMETYPE, width, height);
-
- format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
- MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
- format.setInteger(MediaFormat.KEY_BIT_RATE, BITRATE);
- format.setInteger(MediaFormat.KEY_FRAME_RATE, FPS);
- format.setInteger(MediaFormat.KEY_CAPTURE_RATE, FPS);
- format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
- format.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 1); // 1 second between I-frames
- format.setInteger(MediaFormat.KEY_LEVEL, CodecProfileLevel.AVCLevel31);
- format.setInteger(MediaFormat.KEY_PROFILE,
- MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline);
-
- codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
- }
-
- @Override
- public String toString() {
- return getClass() + "{"
- + ", receiver connected: " + (mActiveThread != null)
- + ", encoder: " + mVideoEncoder
- + ", virtualDisplay" + mVirtualDisplay
- + "}";
- }
-
- private static class DebugCounter {
- long outputBuffers;
- long bufferErrors;
- long clientsConnected;
- long clientsDisconnected;
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + "{"
- + "outputBuffers=" + outputBuffers
- + ", bufferErrors=" + bufferErrors
- + ", clientsConnected=" + clientsConnected
- + ", clientsDisconnected= " + clientsDisconnected
- + "}";
- }
- }
-}
diff --git a/src/android/car/cluster/PipeThread.java b/src/android/car/cluster/PipeThread.java
deleted file mode 100644
index a8f6308..0000000
--- a/src/android/car/cluster/PipeThread.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2018 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;
-import android.os.Handler;
-import android.util.Log;
-
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-/**
- * Thread that can send data to the emulator using a qemud service.
- */
-public class PipeThread extends SenderThread {
- private static final String TAG = "Cluster." + PipeThread.class.getSimpleName();
-
- private RandomAccessFile mPipe;
-
- /**
- * Creates instance of pipe thread that can write to given pipe file.
- *
- * @param handler {@link Handler} used to message broadcaster.
- * @param pipe {@link RandomAccessFile} file already connected to pipe.
- */
- PipeThread(Handler handler, RandomAccessFile pipe) {
- super(handler);
- mPipe = pipe;
- }
-
- public void run() {
- try {
- int signal = mPipe.read();
- while (signal != NetworkedVirtualDisplay.PIPE_STOP) {
- Log.i(TAG, "Received non-stop signal: " + signal);
- signal = mPipe.read();
- }
- restart();
- } catch (IOException e) {
- Log.e(TAG, "Failed to read from pipe");
- restart();
- }
- }
-
- @Override
- public void send(byte[] buf, int len) {
- try {
- // First sends the size prior to sending the data, since receiving side only sees
- // the size of the buffer, which could be significant larger than the actual data.
- mPipe.write(ByteBuffer.allocate(4)
- .order(ByteOrder.LITTLE_ENDIAN).putInt(len).array());
- mPipe.write(buf);
- } catch (IOException e) {
- Log.e(TAG, "Write to pipe failed");
- restart();
- }
- }
-
- @Override
- public void close() {
- try {
- mPipe.close();
- } catch (IOException e) {
- Log.e(TAG, "Failed to close pipe", e);
- }
- }
-}
-
diff --git a/src/android/car/cluster/SenderThread.java b/src/android/car/cluster/SenderThread.java
deleted file mode 100644
index de461e2..0000000
--- a/src/android/car/cluster/SenderThread.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2018 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;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-
-/**
- * This class serves as a template for sending to specific clients of the broadcaster.
- */
-public abstract class SenderThread extends Thread {
- private static final String TAG = "Cluster.SenderThread";
-
- private Handler mHandler;
-
- SenderThread(Handler handler) {
- mHandler = handler;
- }
-
- abstract void send(byte[] buf, int len);
- abstract void close();
-
- /**
- * Tells the broadcasting thread to stop and close everything in progress, and start over again.
- * It will kill the current instance of this thread, and produce a new one.
- */
- synchronized void restart() {
- if (mHandler.hasMessages(NetworkedVirtualDisplay.MSG_START)) return;
- Log.i(TAG, "Sending STOP and START msgs to NetworkedVirtualDisplay");
-
- mHandler.sendMessage(Message.obtain(mHandler, NetworkedVirtualDisplay.MSG_STOP));
- mHandler.sendMessage(Message.obtain(mHandler, NetworkedVirtualDisplay.MSG_START));
- }
-}
diff --git a/src/android/car/cluster/SocketThread.java b/src/android/car/cluster/SocketThread.java
deleted file mode 100644
index 7f1c61a..0000000
--- a/src/android/car/cluster/SocketThread.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2018 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;
-import android.os.Handler;
-import android.util.Log;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.ServerSocket;
-
-/**
- * The thread that will send data on an opened socket.
- */
-public class SocketThread extends SenderThread {
- private static final String TAG = "Cluster." + SocketThread.class.getSimpleName();
- private ServerSocket mServerSocket;
- private OutputStream mOutputStream;
- private InputStream mInputStream;
-
- /**
- * Create instance of thread that can write to given open socket.
- *
- * @param handler {@link Handler} used to message the broadcaster.
- * @param serverSocket {@link ServerSocket} should be already opened.
- * @param inputStream {@link InputStream} corresponding to opened socket.
- * @param outputStream {@link OutputStream} corresponding to opened socket.
- */
- SocketThread(Handler handler, ServerSocket serverSocket, InputStream inputStream,
- OutputStream outputStream) {
- super(handler);
- mServerSocket = serverSocket;
- mInputStream = inputStream;
- mOutputStream = outputStream;
- }
-
- public void run() {
- try {
- // This read should block until something disconnects (or something
- // similar) which should cause an exception, in which case we should
- // try to setup again and reconnect
- mInputStream.read();
- } catch (IOException e) {
- Log.e(TAG, "Socket thread disconnected.");
- }
- restart();
- }
-
- @Override
- public void send(byte[] buf, int len) {
- try {
- mOutputStream.write(buf, 0, len);
- } catch (IOException e) {
- Log.e(TAG, "Failed to write data to socket, retrying connection");
- restart();
- }
- }
-
- @Override
- public void close() {
- if (mServerSocket != null) {
- try {
- mServerSocket.close();
- } catch (IOException e) {
- Log.w(TAG, "Failed to close server socket, ignoring");
- }
- mServerSocket = null;
- }
- mInputStream = null;
- mOutputStream = null;
- }
-}
-
diff --git a/tests/robotests/Android.bp b/tests/robotests/Android.bp
new file mode 100644
index 0000000..b5bc314
--- /dev/null
+++ b/tests/robotests/Android.bp
@@ -0,0 +1,18 @@
+//############################################
+// Messenger Robolectric test target. #
+//############################################
+
+android_robolectric_test {
+ name: "DirectRenderingClusterTests",
+
+ srcs: ["src/**/*.java"],
+
+ java_resource_dirs: ["config"],
+
+ // Include the testing libraries
+ libs: [
+ "android.car",
+ ],
+
+ instrumentation_for: "DirectRenderingCluster",
+}
diff --git a/tests/robotests/Android.mk b/tests/robotests/Android.mk
deleted file mode 100644
index 631a403..0000000
--- a/tests/robotests/Android.mk
+++ /dev/null
@@ -1,46 +0,0 @@
-#############################################
-# Messenger Robolectric test target. #
-#############################################
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := DirectRenderingClusterTests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-# Include the testing libraries
-LOCAL_JAVA_LIBRARIES := \
- robolectric_android-all-stub \
- Robolectric_all-target \
- mockito-robolectric-prebuilt \
- truth-prebuilt \
- android.car
-
-LOCAL_INSTRUMENTATION_FOR := DirectRenderingCluster
-
-LOCAL_MODULE_TAGS := optional
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-#############################################################
-# Messenger runner target to run the previous target. #
-#############################################################
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := RunDirectRenderingClusterTests
-
-LOCAL_SDK_VERSION := current
-
-LOCAL_JAVA_LIBRARIES := \
- DirectRenderingClusterTests \
- robolectric_android-all-stub \
- Robolectric_all-target \
- mockito-robolectric-prebuilt \
- truth-prebuilt \
- android.car
-
-LOCAL_TEST_PACKAGE := DirectRenderingCluster
-
-LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src
-
-include external/robolectric-shadows/run_robotests.mk
diff --git a/tests/Android.mk b/tests/robotests/config/robolectric.properties
similarity index 72%
rename from tests/Android.mk
rename to tests/robotests/config/robolectric.properties
index 9f0a4e8..cb4cb3e 100644
--- a/tests/Android.mk
+++ b/tests/robotests/config/robolectric.properties
@@ -1,4 +1,4 @@
-# Copyright (C) 2019 The Android Open Source Project
+# Copyright (C) 2020 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.
@@ -11,9 +11,5 @@
# 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.
+sdk=NEWEST_SDK
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-# Include all makefiles in subdirectories
-include $(call all-makefiles-under,$(LOCAL_PATH))