1 /*
2  * Copyright (C) 2020 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.systemui.media.controls.models.recommendation
18 
19 import android.app.smartspace.SmartspaceAction
20 import android.content.Context
21 import android.content.Intent
22 import android.content.pm.PackageManager
23 import android.text.TextUtils
24 import android.util.Log
25 import androidx.annotation.VisibleForTesting
26 import com.android.internal.logging.InstanceId
27 
28 @VisibleForTesting const val KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME"
29 
30 /** State of a Smartspace media recommendations view. */
31 data class SmartspaceMediaData(
32     /** Unique id of a Smartspace media target. */
33     val targetId: String,
34     /** Indicates if the status is active. */
35     val isActive: Boolean,
36     /** Package name of the media recommendations' provider-app. */
37     val packageName: String,
38     /** Action to perform when the card is tapped. Also contains the target's extra info. */
39     val cardAction: SmartspaceAction?,
40     /** List of media recommendations. */
41     val recommendations: List<SmartspaceAction>,
42     /** Intent for the user's initiated dismissal. */
43     val dismissIntent: Intent?,
44     /** The timestamp in milliseconds that the card was generated */
45     val headphoneConnectionTimeMillis: Long,
46     /** Instance ID for [MediaUiEventLogger] */
47     val instanceId: InstanceId,
48     /** The timestamp in milliseconds indicating when the card should be removed */
49     val expiryTimeMs: Long,
50 ) {
51     /**
52      * Indicates if all the data is valid.
53      *
54      * TODO(b/230333302): Make MediaControlPanel more flexible so that we can display fewer than
55      *
56      * ```
57      *     [NUM_REQUIRED_RECOMMENDATIONS].
58      * ```
59      */
60     fun isValid() = getValidRecommendations().size >= NUM_REQUIRED_RECOMMENDATIONS
61 
62     /** Returns the list of [recommendations] that have valid data. */
63     fun getValidRecommendations() = recommendations.filter { it.icon != null }
64 
65     /** Returns the upstream app name if available. */
66     fun getAppName(context: Context): CharSequence? {
67         val nameFromAction = cardAction?.intent?.extras?.getString(KEY_SMARTSPACE_APP_NAME)
68         if (!TextUtils.isEmpty(nameFromAction)) {
69             return nameFromAction
70         }
71 
72         val packageManager = context.packageManager
73         packageManager.getLaunchIntentForPackage(packageName)?.let {
74             val launchActivity = it.resolveActivityInfo(packageManager, 0)
75             return launchActivity.loadLabel(packageManager)
76         }
77 
78         Log.w(
79             TAG,
80             "Package $packageName does not have a main launcher activity. " +
81                 "Fallback to full app name"
82         )
83         return try {
84             val applicationInfo = packageManager.getApplicationInfo(packageName, /* flags= */ 0)
85             packageManager.getApplicationLabel(applicationInfo)
86         } catch (e: PackageManager.NameNotFoundException) {
87             null
88         }
89     }
90 }
91 
92 /** Key to indicate whether this card should be used to re-show recent media */
93 const val EXTRA_KEY_TRIGGER_RESUME = "SHOULD_TRIGGER_RESUME"
94 /** Key for extras [SmartspaceMediaData.cardAction] indicating why the card was sent */
95 const val EXTRA_KEY_TRIGGER_SOURCE = "MEDIA_RECOMMENDATION_TRIGGER_SOURCE"
96 /** Value for [EXTRA_KEY_TRIGGER_SOURCE] when the card is sent on headphone connection */
97 const val EXTRA_VALUE_TRIGGER_HEADPHONE = "HEADPHONE_CONNECTION"
98 /** Value for key [EXTRA_KEY_TRIGGER_SOURCE] when the card is sent as a regular update */
99 const val EXTRA_VALUE_TRIGGER_PERIODIC = "PERIODIC_TRIGGER"
100 
101 const val NUM_REQUIRED_RECOMMENDATIONS = 3
102 private val TAG = SmartspaceMediaData::class.simpleName!!
103