1 /*
2  * Copyright (C) 2015 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.server.telecom;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.content.pm.PackageManager;
22 import android.content.pm.UserInfo;
23 import android.media.AudioAttributes;
24 import android.media.RingtoneManager;
25 import android.media.Ringtone;
26 import android.media.VolumeShaper;
27 import android.net.Uri;
28 import android.os.UserHandle;
29 import android.os.UserManager;
30 import android.provider.Settings;
31 
32 import android.telecom.Log;
33 import android.text.TextUtils;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 import com.android.server.telecom.flags.FeatureFlags;
37 
38 import android.telecom.CallerInfo;
39 import android.util.Pair;
40 
41 import java.util.List;
42 
43 /**
44  * Uses the incoming {@link Call}'s ringtone URI (obtained by the Contact Lookup) to obtain a
45  * {@link Ringtone} from the {@link RingtoneManager} that can be played by the system during an
46  * incoming call. If the ringtone URI is null, use the default Ringtone for the active user.
47  */
48 @VisibleForTesting
49 public class RingtoneFactory {
50 
51     private final Context mContext;
52     private final CallsManager mCallsManager;
53     private FeatureFlags mFeatureFlags;
54 
RingtoneFactory(CallsManager callsManager, Context context, FeatureFlags featureFlags)55     public RingtoneFactory(CallsManager callsManager, Context context, FeatureFlags featureFlags) {
56         mContext = context;
57         mCallsManager = callsManager;
58         mFeatureFlags = featureFlags;
59     }
60 
getRingtone(Call incomingCall, @Nullable VolumeShaper.Configuration volumeShaperConfig, boolean hapticChannelsMuted)61     public Pair<Uri, Ringtone> getRingtone(Call incomingCall,
62             @Nullable VolumeShaper.Configuration volumeShaperConfig, boolean hapticChannelsMuted) {
63         // Initializing ringtones on the main thread can deadlock
64         ThreadUtil.checkNotOnMainThread();
65 
66         AudioAttributes audioAttrs = getDefaultRingtoneAudioAttributes(hapticChannelsMuted);
67 
68         // Use the default ringtone of the work profile if the contact is a work profile contact.
69         // or the default ringtone of the receiving user.
70         Context userContext = isWorkContact(incomingCall) ?
71                 getWorkProfileContextForUser(mCallsManager.getCurrentUserHandle()) :
72                 getContextForUserHandle(incomingCall.getAssociatedUser());
73         Uri ringtoneUri = incomingCall.getRingtone();
74         Ringtone ringtone = null;
75 
76         if (ringtoneUri != null && userContext != null) {
77             // Ringtone URI is explicitly specified. First, try to create a Ringtone with that.
78             try {
79                 ringtone = RingtoneManager.getRingtone(
80                         userContext, ringtoneUri, volumeShaperConfig, audioAttrs);
81             } catch (Exception e) {
82                 Log.e(this, e, "getRingtone: exception while getting ringtone.");
83             }
84         }
85         if (ringtone == null) {
86             // Contact didn't specify ringtone or custom Ringtone creation failed. Get default
87             // ringtone for user or profile.
88             Context contextToUse = hasDefaultRingtoneForUser(userContext) ? userContext : mContext;
89             UserManager um = contextToUse.getSystemService(UserManager.class);
90             boolean isUserUnlocked = mFeatureFlags.telecomResolveHiddenDependencies()
91                     ? um.isUserUnlocked(contextToUse.getUser())
92                     : um.isUserUnlocked(contextToUse.getUserId());
93             Uri defaultRingtoneUri;
94             if (isUserUnlocked) {
95                 defaultRingtoneUri = RingtoneManager.getActualDefaultRingtoneUri(contextToUse,
96                         RingtoneManager.TYPE_RINGTONE);
97                 if (defaultRingtoneUri == null) {
98                     Log.i(this, "getRingtone: defaultRingtoneUri for user is null.");
99                 }
100             } else {
101                 defaultRingtoneUri = Settings.System.DEFAULT_RINGTONE_URI;
102                 if (defaultRingtoneUri == null) {
103                     Log.i(this, "getRingtone: Settings.System.DEFAULT_RINGTONE_URI is null.");
104                 }
105             }
106 
107             ringtoneUri = defaultRingtoneUri;
108             if (ringtoneUri == null) {
109                 return null;
110             }
111 
112             try {
113                 ringtone = RingtoneManager.getRingtone(
114                         contextToUse, ringtoneUri, volumeShaperConfig, audioAttrs);
115             } catch (Exception e) {
116                 Log.e(this, e, "getRingtone: exception while getting ringtone.");
117             }
118         }
119         return new Pair(ringtoneUri, ringtone);
120     }
121 
getDefaultRingtoneAudioAttributes(boolean hapticChannelsMuted)122     private AudioAttributes getDefaultRingtoneAudioAttributes(boolean hapticChannelsMuted) {
123         return new AudioAttributes.Builder()
124             .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
125             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
126             .setHapticChannelsMuted(hapticChannelsMuted)
127             .build();
128     }
129 
130     /** Returns a ringtone to be used when ringer is not audible for the incoming call. */
131     @Nullable
getHapticOnlyRingtone()132     public Pair<Uri, Ringtone> getHapticOnlyRingtone() {
133         // Initializing ringtones on the main thread can deadlock
134         ThreadUtil.checkNotOnMainThread();
135         Uri ringtoneUri = Uri.parse("file://" + mContext.getString(
136                 com.android.internal.R.string.config_defaultRingtoneVibrationSound));
137         AudioAttributes audioAttrs = getDefaultRingtoneAudioAttributes(
138             /* hapticChannelsMuted */ false);
139         Ringtone ringtone = RingtoneManager.getRingtone(
140                 mContext, ringtoneUri, /* volumeShaperConfig */ null, audioAttrs);
141         if (ringtone != null) {
142             // Make sure the sound is muted.
143             ringtone.setVolume(0);
144         }
145         return new Pair(ringtoneUri, ringtone);
146     }
147 
getWorkProfileContextForUser(UserHandle userHandle)148     private Context getWorkProfileContextForUser(UserHandle userHandle) {
149         // UserManager.getUserProfiles returns the enabled profiles along with the context user's
150         // handle itself (so we must filter out the user).
151         Context userContext = mContext.createContextAsUser(userHandle, 0);
152         UserManager um = mFeatureFlags.telecomResolveHiddenDependencies()
153                 ? userContext.getSystemService(UserManager.class)
154                 : mContext.getSystemService(UserManager.class);
155         List<UserHandle> profiles = um.getUserProfiles();
156         List<UserInfo> userInfoProfiles = um.getEnabledProfiles(userHandle.getIdentifier());
157         UserHandle workProfileUser = null;
158         int managedProfileCount = 0;
159 
160         if (mFeatureFlags.telecomResolveHiddenDependencies()) {
161             for (UserHandle profileUser : profiles) {
162                 UserManager userManager = mContext.createContextAsUser(profileUser, 0)
163                         .getSystemService(UserManager.class);
164                 if (!userHandle.equals(profileUser) && userManager.isManagedProfile()) {
165                     managedProfileCount++;
166                     workProfileUser = profileUser;
167                 }
168             }
169         } else {
170             for(UserInfo profile: userInfoProfiles) {
171                 UserHandle profileUserHandle = profile.getUserHandle();
172                 if (!profileUserHandle.equals(userHandle) && profile.isManagedProfile()) {
173                     managedProfileCount++;
174                     workProfileUser = profileUserHandle;
175                 }
176             }
177         }
178         // There may be many different types of profiles, so only count Managed (Work) Profiles.
179         if(managedProfileCount == 1) {
180             return getContextForUserHandle(workProfileUser);
181         }
182         // There are multiple managed profiles for the associated user and we do not have enough
183         // info to determine which profile is the work profile. Just use the default.
184         return null;
185     }
186 
getContextForUserHandle(UserHandle userHandle)187     private Context getContextForUserHandle(UserHandle userHandle) {
188         if(userHandle == null) {
189             return null;
190         }
191         try {
192             return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, userHandle);
193         } catch (PackageManager.NameNotFoundException e) {
194             Log.w("RingtoneFactory", "Package name not found: " + e.getMessage());
195         }
196         return null;
197     }
198 
hasDefaultRingtoneForUser(Context userContext)199     private boolean hasDefaultRingtoneForUser(Context userContext) {
200         if(userContext == null) {
201             return false;
202         }
203         return !TextUtils.isEmpty(Settings.System.getStringForUser(userContext.getContentResolver(),
204                 Settings.System.RINGTONE, userContext.getUserId()));
205     }
206 
isWorkContact(Call incomingCall)207     private boolean isWorkContact(Call incomingCall) {
208         CallerInfo contactCallerInfo = incomingCall.getCallerInfo();
209         return (contactCallerInfo != null) &&
210                 (contactCallerInfo.userType == CallerInfo.USER_TYPE_WORK);
211     }
212 }
213