diff options
author | Roberto Perez <robertoalexis@google.com> | 2019-06-03 16:54:07 -0700 |
---|---|---|
committer | Roberto Perez <robertoalexis@google.com> | 2019-06-04 17:20:53 -0700 |
commit | 1b0f3a1874a41ddc1056be766c75de154a683e3a (patch) | |
tree | 0302826b7980aa2bd289f6be32bedad01633c8ff | |
parent | 5970b3de51391c5786a8e26757f1843bbe265f4a (diff) | |
download | Cluster-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.xml | 10 | ||||
-rw-r--r-- | res/layout/activity_fake_free_navigation.xml | 49 | ||||
-rw-r--r-- | res/values/config.xml | 22 | ||||
-rw-r--r-- | res/values/dimens.xml | 6 | ||||
-rw-r--r-- | src/android/car/cluster/FakeFreeNavigationActivity.java | 72 | ||||
-rw-r--r-- | src/android/car/cluster/MainClusterActivity.java | 108 |
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) { |