1/*
2 * Copyright (C) 2008 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
17package com.android.providers.settings;
18
19import android.app.ActivityManagerNative;
20import android.app.IActivityManager;
21import android.app.backup.IBackupManager;
22import android.content.Context;
23import android.content.res.Configuration;
24import android.location.LocationManager;
25import android.media.AudioManager;
26import android.media.RingtoneManager;
27import android.net.Uri;
28import android.os.IPowerManager;
29import android.os.RemoteException;
30import android.os.ServiceManager;
31import android.os.UserManager;
32import android.provider.Settings;
33import android.telephony.TelephonyManager;
34import android.text.TextUtils;
35
36import java.util.Locale;
37
38public class SettingsHelper {
39    private static final String SILENT_RINGTONE = "_silent";
40    private Context mContext;
41    private AudioManager mAudioManager;
42    private TelephonyManager mTelephonyManager;
43
44    public SettingsHelper(Context context) {
45        mContext = context;
46        mAudioManager = (AudioManager) context
47                .getSystemService(Context.AUDIO_SERVICE);
48        mTelephonyManager = (TelephonyManager) context
49                .getSystemService(Context.TELEPHONY_SERVICE);
50    }
51
52    /**
53     * Sets the property via a call to the appropriate API, if any, and returns
54     * whether or not the setting should be saved to the database as well.
55     * @param name the name of the setting
56     * @param value the string value of the setting
57     * @return whether to continue with writing the value to the database. In
58     * some cases the data will be written by the call to the appropriate API,
59     * and in some cases the property value needs to be modified before setting.
60     */
61    public boolean restoreValue(String name, String value) {
62        if (Settings.System.SCREEN_BRIGHTNESS.equals(name)) {
63            setBrightness(Integer.parseInt(value));
64        } else if (Settings.System.SOUND_EFFECTS_ENABLED.equals(name)) {
65            setSoundEffects(Integer.parseInt(value) == 1);
66        } else if (Settings.Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
67            setGpsLocation(value);
68            return false;
69        } else if (Settings.Secure.BACKUP_AUTO_RESTORE.equals(name)) {
70            setAutoRestore(Integer.parseInt(value) == 1);
71        } else if (isAlreadyConfiguredCriticalAccessibilitySetting(name)) {
72            return false;
73        } else if (Settings.System.RINGTONE.equals(name)
74                || Settings.System.NOTIFICATION_SOUND.equals(name)) {
75            setRingtone(name, value);
76            return false;
77        }
78        return true;
79    }
80
81    public String onBackupValue(String name, String value) {
82        // Special processing for backing up ringtones & notification sounds
83        if (Settings.System.RINGTONE.equals(name)
84                || Settings.System.NOTIFICATION_SOUND.equals(name)) {
85            if (value == null) {
86                if (Settings.System.RINGTONE.equals(name)) {
87                    // For ringtones, we need to distinguish between non-telephony vs telephony
88                    if (mTelephonyManager != null && mTelephonyManager.isVoiceCapable()) {
89                        // Backup a null ringtone as silent on voice-capable devices
90                        return SILENT_RINGTONE;
91                    } else {
92                        // Skip backup of ringtone on non-telephony devices.
93                        return null;
94                    }
95                } else {
96                    // Backup a null notification sound as silent
97                    return SILENT_RINGTONE;
98                }
99            } else {
100                return getCanonicalRingtoneValue(value);
101            }
102        }
103        // Return the original value
104        return value;
105    }
106
107    /**
108     * Sets the ringtone of type specified by the name.
109     *
110     * @param name should be Settings.System.RINGTONE or Settings.System.NOTIFICATION_SOUND.
111     * @param value can be a canonicalized uri or "_silent" to indicate a silent (null) ringtone.
112     */
113    private void setRingtone(String name, String value) {
114        // If it's null, don't change the default
115        if (value == null) return;
116        Uri ringtoneUri = null;
117        if (SILENT_RINGTONE.equals(value)) {
118            ringtoneUri = null;
119        } else {
120            Uri canonicalUri = Uri.parse(value);
121            ringtoneUri = mContext.getContentResolver().uncanonicalize(canonicalUri);
122            if (ringtoneUri == null) {
123                // Unrecognized or invalid Uri, don't restore
124                return;
125            }
126        }
127        final int ringtoneType = Settings.System.RINGTONE.equals(name)
128                ? RingtoneManager.TYPE_RINGTONE : RingtoneManager.TYPE_NOTIFICATION;
129        RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, ringtoneUri);
130    }
131
132    private String getCanonicalRingtoneValue(String value) {
133        final Uri ringtoneUri = Uri.parse(value);
134        final Uri canonicalUri = mContext.getContentResolver().canonicalize(ringtoneUri);
135        return canonicalUri == null ? null : canonicalUri.toString();
136    }
137
138    private boolean isAlreadyConfiguredCriticalAccessibilitySetting(String name) {
139        // These are the critical accessibility settings that are required for a
140        // blind user to be able to interact with the device. If these settings are
141        // already configured, we will not overwrite them. If they are already set,
142        // it means that the user has performed a global gesture to enable accessibility
143        // and definitely needs these features working after the restore.
144        if (Settings.Secure.ACCESSIBILITY_ENABLED.equals(name)
145                || Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION.equals(name)
146                || Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD.equals(name)
147                || Settings.Secure.TOUCH_EXPLORATION_ENABLED.equals(name)) {
148            return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) != 0;
149        } else if (Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES.equals(name)
150                || Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES.equals(name)) {
151            return !TextUtils.isEmpty(Settings.Secure.getString(
152                    mContext.getContentResolver(), name));
153        }
154        return false;
155    }
156
157    private void setAutoRestore(boolean enabled) {
158        try {
159            IBackupManager bm = IBackupManager.Stub.asInterface(
160                    ServiceManager.getService(Context.BACKUP_SERVICE));
161            if (bm != null) {
162                bm.setAutoRestore(enabled);
163            }
164        } catch (RemoteException e) {}
165    }
166
167    private void setGpsLocation(String value) {
168        UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
169        if (um.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION)) {
170            return;
171        }
172        final String GPS = LocationManager.GPS_PROVIDER;
173        boolean enabled =
174                GPS.equals(value) ||
175                value.startsWith(GPS + ",") ||
176                value.endsWith("," + GPS) ||
177                value.contains("," + GPS + ",");
178        Settings.Secure.setLocationProviderEnabled(
179                mContext.getContentResolver(), GPS, enabled);
180    }
181
182    private void setSoundEffects(boolean enable) {
183        if (enable) {
184            mAudioManager.loadSoundEffects();
185        } else {
186            mAudioManager.unloadSoundEffects();
187        }
188    }
189
190    private void setBrightness(int brightness) {
191        try {
192            IPowerManager power = IPowerManager.Stub.asInterface(
193                    ServiceManager.getService("power"));
194            if (power != null) {
195                power.setTemporaryScreenBrightnessSettingOverride(brightness);
196            }
197        } catch (RemoteException doe) {
198
199        }
200    }
201
202    byte[] getLocaleData() {
203        Configuration conf = mContext.getResources().getConfiguration();
204        final Locale loc = conf.locale;
205        String localeString = loc.getLanguage();
206        String country = loc.getCountry();
207        if (!TextUtils.isEmpty(country)) {
208            localeString += "-" + country;
209        }
210        return localeString.getBytes();
211    }
212
213    /**
214     * Sets the locale specified. Input data is the byte representation of a
215     * BCP-47 language tag. For backwards compatibility, strings of the form
216     * {@code ll_CC} are also accepted, where {@code ll} is a two letter language
217     * code and {@code CC} is a two letter country code.
218     *
219     * @param data the locale string in bytes.
220     */
221    void setLocaleData(byte[] data, int size) {
222        // Check if locale was set by the user:
223        Configuration conf = mContext.getResources().getConfiguration();
224        // TODO: The following is not working as intended because the network is forcing a locale
225        // change after registering. Need to find some other way to detect if the user manually
226        // changed the locale
227        if (conf.userSetLocale) return; // Don't change if user set it in the SetupWizard
228
229        final String[] availableLocales = mContext.getAssets().getLocales();
230        // Replace "_" with "-" to deal with older backups.
231        String localeCode = new String(data, 0, size).replace('_', '-');
232        Locale loc = null;
233        for (int i = 0; i < availableLocales.length; i++) {
234            if (availableLocales[i].equals(localeCode)) {
235                loc = Locale.forLanguageTag(localeCode);
236                break;
237            }
238        }
239        if (loc == null) return; // Couldn't find the saved locale in this version of the software
240
241        try {
242            IActivityManager am = ActivityManagerNative.getDefault();
243            Configuration config = am.getConfiguration();
244            config.locale = loc;
245            // indicate this isn't some passing default - the user wants this remembered
246            config.userSetLocale = true;
247
248            am.updateConfiguration(config);
249        } catch (RemoteException e) {
250            // Intentionally left blank
251        }
252    }
253
254    /**
255     * Informs the audio service of changes to the settings so that
256     * they can be re-read and applied.
257     */
258    void applyAudioSettings() {
259        AudioManager am = new AudioManager(mContext);
260        am.reloadAudioSettings();
261    }
262}
263