summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoberto Perez <robertoalexis@google.com>2019-06-03 16:54:07 -0700
committerRoberto Perez <robertoalexis@google.com>2019-06-04 17:20:53 -0700
commit1b0f3a1874a41ddc1056be766c75de154a683e3a (patch)
tree0302826b7980aa2bd289f6be32bedad01633c8ff
parent5970b3de51391c5786a8e26757f1843bbe265f4a (diff)
downloadCluster-1b0f3a1874a41ddc1056be766c75de154a683e3a.tar.gz
Simplify default navigation activity logic
Default activity is now read from a configuration file that can be overlayed. This makes the mechanism more reliable. Bug: 129360858 Test: Manual on device Change-Id: Id7162311a65a5ac240b52802ad507b0ea443a0ee
-rw-r--r--AndroidManifest.xml10
-rw-r--r--res/layout/activity_fake_free_navigation.xml49
-rw-r--r--res/values/config.xml22
-rw-r--r--res/values/dimens.xml6
-rw-r--r--src/android/car/cluster/FakeFreeNavigationActivity.java72
-rw-r--r--src/android/car/cluster/MainClusterActivity.java108
6 files changed, 217 insertions, 50 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 98e235b..fc9246d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -56,6 +56,7 @@
android:exported="false"
android:singleUser="true"
android:permission="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"/>
+
<service android:name=".LoggingClusterRenderingService"
android:exported="false"
android:permission="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"/>
@@ -69,5 +70,14 @@
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
+
+ <activity android:name=".FakeFreeNavigationActivity"
+ android:exported="false"
+ 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">
+ </activity>
</application>
</manifest>
diff --git a/res/layout/activity_fake_free_navigation.xml b/res/layout/activity_fake_free_navigation.xml
new file mode 100644
index 0000000..ef56b1a
--- /dev/null
+++ b/res/layout/activity_fake_free_navigation.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ImageView
+ android:id="@+id/unobscuredArea"
+ android:alpha="0.25"
+ android:background="@android:color/white"
+ android:layout_height="0dp"
+ android:layout_width="0dp"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:gravity="center_horizontal"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Free Navigation PlaceHolder"
+ android:textSize="@dimen/title_text_size" />
+
+ <ProgressBar
+ android:id="@+id/indeterminateBar"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+</RelativeLayout> \ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644
index 0000000..89998f3
--- /dev/null
+++ b/res/values/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 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 to configure based on each OEM's preference. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Activity to present for free navigation -->
+ <string name="freeNavigationIntent" translatable="false">intent:#Intent;component=android.car.cluster/.FakeFreeNavigationActivity;launchFlags=0x24000000;end</string>
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 4fa9e22..5afdb28 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -21,8 +21,9 @@
<dimen name="info_value_text_size">20sp</dimen>
<!-- -->
- <!-- Navigation state componenets -->
+ <!-- Navigation state components -->
<!-- -->
+ <dimen name="title_text_size">40sp</dimen>
<dimen name="nav_state_width">170dp</dimen>
<!-- Maneuver -->
<dimen name="maneuver_width">60dp</dimen>
@@ -56,6 +57,9 @@
<!-- Distance: kilometers to meters -->
<item name="distance_factor" format="float" type="dimen">1000</item>
+ <!-- -->
+ <!-- Media Facet -->
+ <!-- -->
<!-- fragment_metadata.xml -->
<dimen name="fragment_metadata_queue_divider_margin">@*android:dimen/car_keyline_1</dimen>
<dimen name="fragment_metadata_queue_margin_top">@*android:dimen/car_padding_4</dimen>
diff --git a/src/android/car/cluster/FakeFreeNavigationActivity.java b/src/android/car/cluster/FakeFreeNavigationActivity.java
new file mode 100644
index 0000000..a836dc0
--- /dev/null
+++ b/src/android/car/cluster/FakeFreeNavigationActivity.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 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.app.Activity;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+/**
+ * Fake navigation activity used while no other application is providing turn-by-turn driving
+ * directions.
+ */
+public class FakeFreeNavigationActivity extends Activity {
+ private final static String TAG = FakeFreeNavigationActivity.class.getSimpleName();
+
+ private ImageView mUnobscuredArea;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Log.i(TAG, "onCreate");
+ setContentView(R.layout.activity_fake_free_navigation);
+ mUnobscuredArea = findViewById(R.id.unobscuredArea);
+
+ handleIntent(getIntent());
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ handleIntent(intent);
+ }
+
+ private void handleIntent(Intent intent) {
+ if (intent == null) {
+ Log.w(TAG, "Received a null intent");
+ return;
+ }
+ Bundle bundle = intent.getBundleExtra(CarInstrumentClusterManager.KEY_EXTRA_ACTIVITY_STATE);
+ if (bundle == null) {
+ Log.w(TAG, "Received an intent without " + CarInstrumentClusterManager
+ .KEY_EXTRA_ACTIVITY_STATE);
+ return;
+ }
+ ClusterActivityState state = ClusterActivityState.fromBundle(bundle);
+ Log.i(TAG, "handling intent with state: " + state);
+
+ Rect unobscured = state.getUnobscuredBounds();
+ RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
+ unobscured.width(), unobscured.height());
+ lp.setMargins(unobscured.left, unobscured.top, 0, 0);
+ mUnobscuredArea.setLayoutParams(lp);
+ }
+} \ No newline at end of file
diff --git a/src/android/car/cluster/MainClusterActivity.java b/src/android/car/cluster/MainClusterActivity.java
index 11b0c26..b6f6d36 100644
--- a/src/android/car/cluster/MainClusterActivity.java
+++ b/src/android/car/cluster/MainClusterActivity.java
@@ -63,6 +63,7 @@ import com.android.car.telephony.common.InMemoryPhoneBook;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
+import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -104,12 +105,13 @@ public class MainClusterActivity extends FragmentActivity implements
private VirtualDisplay mPendingVirtualDisplay = null;
private static final int NAVIGATION_ACTIVITY_RETRY_INTERVAL_MS = 1000;
+ private static final int NAVIGATION_ACTIVITY_RELAUNCH_DELAY_MS = 5000;
private UserReceiver mUserReceiver;
private ActivityMonitor mActivityMonitor = new ActivityMonitor();
private final Handler mHandler = new Handler();
private final Runnable mRetryLaunchNavigationActivity = this::tryLaunchNavigationActivity;
- private int mNavigationDisplayId = NO_DISPLAY;
+ private VirtualDisplay mNavigationDisplay = new VirtualDisplay(NO_DISPLAY, null);
private int mPreviousFacet;
@@ -162,7 +164,7 @@ public class MainClusterActivity extends FragmentActivity implements
};
private ActivityMonitor.ActivityListener mNavigationActivityMonitor = (displayId, activity) -> {
- if (displayId != mNavigationDisplayId) {
+ if (displayId != mNavigationDisplay.mDisplayId) {
return;
}
mClusterViewModel.setCurrentNavigationActivity(activity);
@@ -229,6 +231,16 @@ public class MainClusterActivity extends FragmentActivity implements
tryLaunchNavigationActivity();
}
});
+ mClusterViewModel.getNavigationActivityState().observe(this, state -> {
+ if (state == ClusterViewModel.NavigationActivityState.LOADING) {
+ if (!mHandler.hasCallbacks(mRetryLaunchNavigationActivity)) {
+ mHandler.postDelayed(mRetryLaunchNavigationActivity,
+ NAVIGATION_ACTIVITY_RELAUNCH_DELAY_MS);
+ }
+ } else {
+ mHandler.removeCallbacks(mRetryLaunchNavigationActivity);
+ }
+ });
mClusterViewModel.getSensor(Sensors.SENSOR_GEAR).observe(this, this::updateSelectedGear);
@@ -302,7 +314,7 @@ public class MainClusterActivity extends FragmentActivity implements
public void updateNavDisplay(VirtualDisplay virtualDisplay) {
// Starting the default navigation activity. This activity will be shown when navigation
// focus is not taken.
- startNavigationActivity(virtualDisplay.mDisplayId);
+ startNavigationActivity(virtualDisplay);
// Notify the service (so it updates display properties on car service)
if (mService == null) {
// Service is not bound yet. Hold the information and notify when the service is bound.
@@ -368,10 +380,10 @@ public class MainClusterActivity extends FragmentActivity implements
}
}
- private void startNavigationActivity(int displayId) {
- mActivityMonitor.removeListener(mNavigationDisplayId, mNavigationActivityMonitor);
- mActivityMonitor.addListener(displayId, mNavigationActivityMonitor);
- mNavigationDisplayId = displayId;
+ private void startNavigationActivity(VirtualDisplay virtualDisplay) {
+ mActivityMonitor.removeListener(mNavigationDisplay.mDisplayId, mNavigationActivityMonitor);
+ mActivityMonitor.addListener(virtualDisplay.mDisplayId, mNavigationActivityMonitor);
+ mNavigationDisplay = virtualDisplay;
tryLaunchNavigationActivity();
}
@@ -382,7 +394,7 @@ public class MainClusterActivity extends FragmentActivity implements
* have a default navigation activity selected yet.
*/
private void tryLaunchNavigationActivity() {
- if (mNavigationDisplayId == NO_DISPLAY) {
+ if (mNavigationDisplay.mDisplayId == NO_DISPLAY) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, String.format("Launch activity ignored (no display yet)"));
}
@@ -398,14 +410,17 @@ public class MainClusterActivity extends FragmentActivity implements
if (navigationActivity == null) {
throw new ActivityNotFoundException();
}
- Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(CarInstrumentClusterManager
- .CATEGORY_NAVIGATION)
- .setPackage(navigationActivity.getPackageName())
+ ClusterActivityState activityState = ClusterActivityState
+ .create(true, mNavigationDisplay.mUnobscuredBounds);
+ Intent intent = new Intent(Intent.ACTION_MAIN)
.setComponent(navigationActivity)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- Log.d(TAG, "Launching: " + intent + " on display: " + mNavigationDisplayId);
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra(CarInstrumentClusterManager.KEY_EXTRA_ACTIVITY_STATE,
+ activityState.toBundle());
+
+ Log.d(TAG, "Launching: " + intent + " on display: " + mNavigationDisplay.mDisplayId);
Bundle activityOptions = ActivityOptions.makeBasic()
- .setLaunchDisplayId(mNavigationDisplayId)
+ .setLaunchDisplayId(mNavigationDisplay.mDisplayId)
.toBundle();
startActivityAsUser(intent, activityOptions, UserHandle.CURRENT);
@@ -420,55 +435,50 @@ public class MainClusterActivity extends FragmentActivity implements
/**
* Returns a default navigation activity to show in the cluster.
- * In the current implementation we search for an activity with the
- * {@link Car#CAR_CATEGORY_NAVIGATION} category from the same navigation app
- * selected from CarLauncher (see CarLauncher#getMapsIntent()).
+ * In the current implementation we obtain this activity from an intent defined in a resources
+ * file (which OEMs can overlay).
* Alternatively, other implementations could:
* <ul>
- * <li>Read this package from a resource (having a OEM default activity to show)
+ * <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.
* <li>Let the user select one from settings.
* </ul>
*/
private ComponentName getNavigationActivity() {
PackageManager pm = getPackageManager();
int userId = ActivityManager.getCurrentUser();
+ String intentString = getString(R.string.freeNavigationIntent);
- // Get currently selected navigation app.
- Intent intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN,
- Intent.CATEGORY_APP_MAPS);
- ResolveInfo navigationApp = pm.resolveActivityAsUser(intent,
- PackageManager.MATCH_DEFAULT_ONLY, userId);
-
- // 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));
+ if (intentString == null) {
+ Log.w(TAG, "No free navigation activity defined");
return null;
}
+ Log.i(TAG, "Free navigation intent: " + intentString);
- // Get all possible cluster activities
- intent = new Intent(Intent.ACTION_MAIN).addCategory(CarInstrumentClusterManager
- .CATEGORY_NAVIGATION);
- List<ResolveInfo> candidates = pm.queryIntentActivitiesAsUser(intent, 0, userId);
-
- // If there is a select navigation app, try finding a matching auxiliary navigation activity
- if (navigationApp != null) {
- for (ResolveInfo candidate : candidates) {
- if (candidate.activityInfo.packageName.equals(navigationApp.activityInfo
- .packageName)) {
- Log.d(TAG, "Found activity: " + candidate);
- return new ComponentName(candidate.activityInfo.packageName,
- candidate.activityInfo.name);
- }
+ try {
+ Intent intent = Intent.parseUri(intentString, Intent.URI_INTENT_SCHEME);
+ ResolveInfo navigationApp = pm.resolveActivityAsUser(intent,
+ PackageManager.MATCH_DEFAULT_ONLY, userId);
+ 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;
}
- }
- // During initialization implicit intents might not provided a result. We will just
- // retry until we find one, or we exhaust the retries.
- Log.d(TAG, "No default activity found (it might not be available yet).");
- return null;
+ return new ComponentName(navigationApp.activityInfo.packageName,
+ navigationApp.activityInfo.name);
+ } catch (URISyntaxException ex) {
+ Log.e(TAG, "Unable to parse free navigation activity intent: '" + intentString + "'");
+ return null;
+ }
}
private void registerGear(View view, Sensors.Gear gear) {