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 android.telecom.CallerInfo;
37 
38 import java.util.List;
39 
40 /**
41  * Uses the incoming {@link Call}'s ringtone URI (obtained by the Contact Lookup) to obtain a
42  * {@link Ringtone} from the {@link RingtoneManager} that can be played by the system during an
43  * incoming call. If the ringtone URI is null, use the default Ringtone for the active user.
44  */
45 @VisibleForTesting
46 public class RingtoneFactory {
47 
48     private final Context mContext;
49     private final CallsManager mCallsManager;
50 
51     public RingtoneFactory(CallsManager callsManager, Context context) {
52         mContext = context;
53         mCallsManager = callsManager;
54     }
55 
56     /**
57      * Determines if a ringtone has haptic channels.
58      * @param ringtone The ringtone URI.
59      * @return {@code true} if there is a haptic channel, {@code false} otherwise.
60      */
61     public boolean hasHapticChannels(Ringtone ringtone) {
62         boolean hasHapticChannels = RingtoneManager.hasHapticChannels(ringtone.getUri());
63         Log.i(this, "hasHapticChannels %s -> %b", ringtone.getUri(), hasHapticChannels);
64         return hasHapticChannels;
65     }
66 
67     public Ringtone getRingtone(Call incomingCall,
68             @Nullable VolumeShaper.Configuration volumeShaperConfig) {
69         // Use the default ringtone of the work profile if the contact is a work profile contact.
70         Context userContext = isWorkContact(incomingCall) ?
71                 getWorkProfileContextForUser(mCallsManager.getCurrentUserHandle()) :
72                 getContextForUserHandle(mCallsManager.getCurrentUserHandle());
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(userContext, ringtoneUri,
80                         volumeShaperConfig);
81             } catch (NullPointerException npe) {
82                 Log.e(this, npe, "getRingtone: NPE 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             Uri defaultRingtoneUri;
90             if (UserManager.get(contextToUse).isUserUnlocked(contextToUse.getUserId())) {
91                 defaultRingtoneUri = RingtoneManager.getActualDefaultRingtoneUri(contextToUse,
92                         RingtoneManager.TYPE_RINGTONE);
93             } else {
94                 defaultRingtoneUri = Settings.System.DEFAULT_RINGTONE_URI;
95             }
96             if (defaultRingtoneUri == null) {
97                 return null;
98             }
99             try {
100                 ringtone = RingtoneManager.getRingtone(
101                         contextToUse, defaultRingtoneUri, volumeShaperConfig);
102             } catch (NullPointerException npe) {
103                 Log.e(this, npe, "getRingtone: NPE while getting ringtone.");
104             }
105         }
106         return setRingtoneAudioAttributes(ringtone);
107     }
108 
109     public Ringtone getRingtone(Call incomingCall) {
110         return getRingtone(incomingCall, null);
111     }
112 
113     /** Returns a ringtone to be used when ringer is not audible for the incoming call. */
114     @Nullable
115     public Ringtone getHapticOnlyRingtone() {
116         Uri ringtoneUri = Uri.parse("file://" + mContext.getString(
117                 com.android.internal.R.string.config_defaultRingtoneVibrationSound));
118         Ringtone ringtone = RingtoneManager.getRingtone(mContext, ringtoneUri, null);
119         if (ringtone != null) {
120             // Make sure the sound is muted.
121             ringtone.setVolume(0);
122         }
123         return setRingtoneAudioAttributes(ringtone);
124     }
125 
126     private Ringtone setRingtoneAudioAttributes(Ringtone ringtone) {
127         if (ringtone != null) {
128             ringtone.setAudioAttributes(new AudioAttributes.Builder()
129                     .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
130                     .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
131                     .build());
132         }
133         return ringtone;
134     }
135 
136     private Context getWorkProfileContextForUser(UserHandle userHandle) {
137         // UserManager.getEnabledProfiles returns the enabled profiles along with the user's handle
138         // itself (so we must filter out the user).
139         List<UserInfo> profiles = UserManager.get(mContext).getEnabledProfiles(
140                 userHandle.getIdentifier());
141         UserInfo workprofile = null;
142         int managedProfileCount = 0;
143         for (UserInfo profile : profiles) {
144             UserHandle profileUserHandle = profile.getUserHandle();
145             if (profileUserHandle != userHandle && profile.isManagedProfile()) {
146                 managedProfileCount++;
147                 workprofile = profile;
148             }
149         }
150         // There may be many different types of profiles, so only count Managed (Work) Profiles.
151         if(managedProfileCount == 1) {
152             return getContextForUserHandle(workprofile.getUserHandle());
153         }
154         // There are multiple managed profiles for the associated user and we do not have enough
155         // info to determine which profile is the work profile. Just use the default.
156         return null;
157     }
158 
159     private Context getContextForUserHandle(UserHandle userHandle) {
160         if(userHandle == null) {
161             return null;
162         }
163         try {
164             return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, userHandle);
165         } catch (PackageManager.NameNotFoundException e) {
166             Log.w("RingtoneFactory", "Package name not found: " + e.getMessage());
167         }
168         return null;
169     }
170 
171     private boolean hasDefaultRingtoneForUser(Context userContext) {
172         if(userContext == null) {
173             return false;
174         }
175         return !TextUtils.isEmpty(Settings.System.getStringForUser(userContext.getContentResolver(),
176                 Settings.System.RINGTONE, userContext.getUserId()));
177     }
178 
179     private boolean isWorkContact(Call incomingCall) {
180         CallerInfo contactCallerInfo = incomingCall.getCallerInfo();
181         return (contactCallerInfo != null) &&
182                 (contactCallerInfo.userType == CallerInfo.USER_TYPE_WORK);
183     }
184 }
185