1 /*
2  * Copyright (C) 2018 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.textclassifier.common.intent;
18 
19 import android.content.Intent;
20 import android.net.Uri;
21 import android.os.Bundle;
22 import android.text.TextUtils;
23 import android.view.textclassifier.TextClassifier;
24 import com.android.textclassifier.common.base.TcLog;
25 import com.google.android.textclassifier.NamedVariant;
26 import com.google.android.textclassifier.RemoteActionTemplate;
27 import com.google.common.collect.ImmutableList;
28 import javax.annotation.Nullable;
29 
30 /** Creates intents based on {@link RemoteActionTemplate} objects. */
31 public final class TemplateIntentFactory {
32   private static final String TAG = "TemplateIntentFactory";
33 
34   /** Constructs and returns a list of {@link LabeledIntent} based on the given templates. */
create( @ullable RemoteActionTemplate[] remoteActionTemplates)35   public ImmutableList<LabeledIntent> create(
36       @Nullable RemoteActionTemplate[] remoteActionTemplates) {
37     if (remoteActionTemplates == null || remoteActionTemplates.length == 0) {
38       return ImmutableList.of();
39     }
40     final ImmutableList.Builder<LabeledIntent> labeledIntents = ImmutableList.builder();
41     for (RemoteActionTemplate remoteActionTemplate : remoteActionTemplates) {
42       if (!isValidTemplate(remoteActionTemplate)) {
43         TcLog.w(TAG, "Invalid RemoteActionTemplate skipped.");
44         continue;
45       }
46       labeledIntents.add(
47           new LabeledIntent(
48               remoteActionTemplate.titleWithoutEntity,
49               remoteActionTemplate.titleWithEntity,
50               remoteActionTemplate.description,
51               remoteActionTemplate.descriptionWithAppName,
52               createIntent(remoteActionTemplate),
53               remoteActionTemplate.requestCode == null
54                   ? LabeledIntent.DEFAULT_REQUEST_CODE
55                   : remoteActionTemplate.requestCode));
56     }
57     return labeledIntents.build();
58   }
59 
isValidTemplate(@ullable RemoteActionTemplate remoteActionTemplate)60   private static boolean isValidTemplate(@Nullable RemoteActionTemplate remoteActionTemplate) {
61     if (remoteActionTemplate == null) {
62       TcLog.w(TAG, "Invalid RemoteActionTemplate: is null");
63       return false;
64     }
65     if (TextUtils.isEmpty(remoteActionTemplate.titleWithEntity)
66         && TextUtils.isEmpty(remoteActionTemplate.titleWithoutEntity)) {
67       TcLog.w(TAG, "Invalid RemoteActionTemplate: title is null");
68       return false;
69     }
70     if (TextUtils.isEmpty(remoteActionTemplate.description)) {
71       TcLog.w(TAG, "Invalid RemoteActionTemplate: description is null");
72       return false;
73     }
74     if (!TextUtils.isEmpty(remoteActionTemplate.packageName)) {
75       TcLog.w(TAG, "Invalid RemoteActionTemplate: package name is set");
76       return false;
77     }
78     if (TextUtils.isEmpty(remoteActionTemplate.action)) {
79       TcLog.w(TAG, "Invalid RemoteActionTemplate: intent action not set");
80       return false;
81     }
82     return true;
83   }
84 
createIntent(RemoteActionTemplate remoteActionTemplate)85   private static Intent createIntent(RemoteActionTemplate remoteActionTemplate) {
86     final Intent intent = new Intent(remoteActionTemplate.action);
87     final Uri uri =
88         TextUtils.isEmpty(remoteActionTemplate.data)
89             ? null
90             : Uri.parse(remoteActionTemplate.data).normalizeScheme();
91     final String type =
92         TextUtils.isEmpty(remoteActionTemplate.type)
93             ? null
94             : Intent.normalizeMimeType(remoteActionTemplate.type);
95     intent.setDataAndType(uri, type);
96     intent.setFlags(remoteActionTemplate.flags == null ? 0 : remoteActionTemplate.flags);
97     if (!TextUtils.isEmpty(remoteActionTemplate.packageName)) {
98       intent.setPackage(remoteActionTemplate.packageName);
99     }
100     if (remoteActionTemplate.category != null) {
101       for (String category : remoteActionTemplate.category) {
102         if (category != null) {
103           intent.addCategory(category);
104         }
105       }
106     }
107     intent.putExtras(nameVariantsToBundle(remoteActionTemplate.extras));
108     // If the template does not have EXTRA_FROM_TEXT_CLASSIFIER, create one to indicate the result
109     // is from the text classifier, so that client can handle the intent differently.
110     if (!intent.hasExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER)) {
111       intent.putExtra(TextClassifier.EXTRA_FROM_TEXT_CLASSIFIER, Bundle.EMPTY);
112     }
113     return intent;
114   }
115 
116   /** Converts an array of {@link NamedVariant} to a Bundle and returns it. */
nameVariantsToBundle(@ullable NamedVariant[] namedVariants)117   public static Bundle nameVariantsToBundle(@Nullable NamedVariant[] namedVariants) {
118     if (namedVariants == null) {
119       return Bundle.EMPTY;
120     }
121     Bundle bundle = new Bundle();
122     for (NamedVariant namedVariant : namedVariants) {
123       if (namedVariant == null) {
124         continue;
125       }
126       switch (namedVariant.getType()) {
127         case NamedVariant.TYPE_INT:
128           bundle.putInt(namedVariant.getName(), namedVariant.getInt());
129           break;
130         case NamedVariant.TYPE_LONG:
131           bundle.putLong(namedVariant.getName(), namedVariant.getLong());
132           break;
133         case NamedVariant.TYPE_FLOAT:
134           bundle.putFloat(namedVariant.getName(), namedVariant.getFloat());
135           break;
136         case NamedVariant.TYPE_DOUBLE:
137           bundle.putDouble(namedVariant.getName(), namedVariant.getDouble());
138           break;
139         case NamedVariant.TYPE_BOOL:
140           bundle.putBoolean(namedVariant.getName(), namedVariant.getBool());
141           break;
142         case NamedVariant.TYPE_STRING:
143           bundle.putString(namedVariant.getName(), namedVariant.getString());
144           break;
145         case NamedVariant.TYPE_STRING_ARRAY:
146           bundle.putStringArray(namedVariant.getName(), namedVariant.getStringArray());
147           break;
148         case NamedVariant.TYPE_FLOAT_ARRAY:
149           bundle.putFloatArray(namedVariant.getName(), namedVariant.getFloatArray());
150           break;
151         case NamedVariant.TYPE_INT_ARRAY:
152           bundle.putIntArray(namedVariant.getName(), namedVariant.getIntArray());
153           break;
154         case NamedVariant.TYPE_NAMED_VARIANT_ARRAY:
155           bundle.putBundle(
156               namedVariant.getName(), nameVariantsToBundle(namedVariant.getNamedVariantArray()));
157           break;
158         default:
159           TcLog.w(
160               TAG, "Unsupported type found in nameVariantsToBundle : " + namedVariant.getType());
161       }
162     }
163     return bundle;
164   }
165 }
166