• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.settings.deeplink;
18 
19 import static android.provider.Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY;
20 import static android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY;
21 import static android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI;
22 
23 import static com.android.car.settings.CarSettingsApplication.CAR_SETTINGS_PACKAGE_NAME;
24 
25 import android.annotation.NonNull;
26 import android.content.ComponentName;
27 import android.content.Intent;
28 import android.content.pm.ActivityInfo;
29 import android.net.Uri;
30 import android.util.Log;
31 
32 import androidx.window.embedding.SplitRule;
33 
34 import com.android.car.settings.activityembedding.ActivityEmbeddingRulesController;
35 import com.android.car.settings.activityembedding.ActivityEmbeddingUtils;
36 import com.android.car.settings.common.CarSettingActivities;
37 import com.android.car.settings.common.Logger;
38 
39 import java.net.URISyntaxException;
40 
41 /**
42  * {@link CarSettingActivities.HomepageActivity} for when dual-pane is enable via ActivityEmbedding.
43  * This class should only be enabled when dual-pane is active, and should only host activities
44  * that have the permission {@link android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK}.
45  * Intents that target an Activity defined within this package, such as subclasses of
46  * {@link com.android.car.settings.common.BaseCarSettingsActivity}, will directly be started in dual
47  * pane unless it is targeting a single pane Activity. There is no need to convert these Intents
48  * using {@code convertToDeepLinkHomepageIntent}.
49  * Other Intents that target external setting defined outside of this package, such as those added
50  * by {@link com.android.car.settings.common.ExtraSettingsLoader}, should be started using a similar
51  * scheme as defined in the method {@code convertToDeepLinkHomepageIntent}.
52  */
53 public class DeepLinkHomepageActivity extends CarSettingActivities.HomepageActivity {
54     public static final String EMBEDDED_DEEPLINK_INTENT_DATA =
55             "com.android.car.settings.EMBEDDED_DEEPLINK_INTENT_DATA";
56     public static final String EXTRA_TARGET_SECONDARY_CONTAINER =
57             "com.android.car.settings.EXTRA_TARGET_SECONDARY_CONTAINER";
58     private static final String TAG = "DeepLinkHomepageActivity";
59 
60     private static final Logger LOG = new Logger(DeepLinkHomepageActivity.class);
61 
62     @Override
handleNewIntent(Intent intent)63     protected void handleNewIntent(Intent intent) {
64         super.handleNewIntent(intent);
65         maybeLaunchDeepLinkActivity(intent);
66     }
67 
68     /**
69      * Launches deep link activity to be directly two pane if it is supported.
70      */
maybeLaunchDeepLinkActivity(Intent intent)71     private void maybeLaunchDeepLinkActivity(Intent intent) {
72         if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)) {
73             Log.e(TAG, "Embedding is not enabled. Finishing DeepLinkHomepageActivity.");
74             finish();
75         }
76         Intent targetIntent;
77         try {
78             String intentUriString = intent.getStringExtra(
79                     EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI);
80             if (intentUriString == null) {
81                 LOG.e("Unable to parse trampoline intent. Intent URI is null");
82                 return;
83             }
84             targetIntent = Intent.parseUri(intentUriString, Intent.URI_INTENT_SCHEME);
85             targetIntent.setData(
86                     intent.getParcelableExtra(EMBEDDED_DEEPLINK_INTENT_DATA, Uri.class));
87         } catch (URISyntaxException e) {
88             LOG.e("Error parsing trampoline intent: " + e);
89             return;
90         }
91         ComponentName targetComponentName = targetIntent.resolveActivity(getPackageManager());
92         targetIntent.setComponent(targetComponentName);
93         targetIntent.removeFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
94         targetIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
95         targetIntent.replaceExtras(intent);
96         ActivityEmbeddingRulesController.registerDualPaneSplitRule(this,
97                 /* primaryComponent= */ new ComponentName(getApplicationContext(), getClass()),
98                 /* secondaryComponent= */ targetComponentName,
99                 /* secondaryIntentAction= */ targetIntent.getAction(),
100                 /* finishPrimaryWithSecondary= */ SplitRule.FinishBehavior.ALWAYS,
101                 /* finishSecondaryWithPrimary= */ SplitRule.FinishBehavior.ALWAYS,
102                 /* clearTop= */ true);
103         setTopLevelHeaderKey(getTopLevelHeaderKey(targetIntent));
104         targetIntent.putExtra(EXTRA_TARGET_SECONDARY_CONTAINER, true);
105         startActivity(targetIntent);
106     }
107 
108     /**
109      * Retrieves the top level header key from the deeplink Intent. If it is available via resolved
110      * {@link ActivityInfo}, then we prioritize displaying that over the intent extra.
111      */
getTopLevelHeaderKey(Intent targetIntent)112     private String getTopLevelHeaderKey(Intent targetIntent) {
113         String metaDataHeaderKey = null;
114         ActivityInfo ai = getActivityInfo(getPackageManager(), targetIntent.getComponent());
115         if (ai != null && ai.metaData != null) {
116             metaDataHeaderKey = ai.metaData.getString(META_DATA_KEY_HEADER_KEY);
117         }
118         String deepLinkExtraHeaderKey = targetIntent.getStringExtra(
119                 EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY);
120         return metaDataHeaderKey != null ? metaDataHeaderKey : deepLinkExtraHeaderKey;
121     }
122 
123     /**
124      * Converts an  {@link Intent} to a deeplink intent that, upon being started, will be
125      * trampolined and then handled by this class {@link DeepLinkHomepageActivity}.
126      */
127     @NonNull
convertToDeepLinkHomepageIntent(@onNull Intent targetIntent)128     public static Intent convertToDeepLinkHomepageIntent(@NonNull Intent targetIntent) {
129         targetIntent = new Intent(targetIntent);
130         targetIntent.setSelector(null);
131 
132         Intent trampolineIntent = new Intent(ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY);
133         trampolineIntent.setPackage(CAR_SETTINGS_PACKAGE_NAME);
134         trampolineIntent.replaceExtras(targetIntent);
135         trampolineIntent.putExtra(EMBEDDED_DEEPLINK_INTENT_DATA, targetIntent.getData());
136         // If Intent#getData() is not null, Intent#toUri will return an Uri which has the scheme
137         // of Intent#getData(), and it may not be the scheme of the original Intent (i.e http:
138         // instead of ACTION_VIEW.
139         targetIntent.setData(null);
140         trampolineIntent.putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI,
141                 targetIntent.toUri(Intent.URI_INTENT_SCHEME));
142 
143         return trampolineIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
144                 | Intent.FLAG_ACTIVITY_NEW_TASK);
145     }
146 }
147