diff options
author | Jean-Baptiste Queru <jbq@google.com> | 2009-07-29 14:57:05 -0700 |
---|---|---|
committer | Jean-Baptiste Queru <jbq@google.com> | 2009-07-29 14:57:05 -0700 |
commit | 61e4248f8f6ae8a8f40550cc0800e5190cd1dc09 (patch) | |
tree | 07d964042985825b97f51b3744977c25b685b2f1 /packages | |
parent | 2af1b3db3d4f687d008db74b150f149e956b4bc6 (diff) | |
parent | a8675f67e33bc7337d148358783b0fd138b501ff (diff) | |
download | frameworks_base-61e4248f8f6ae8a8f40550cc0800e5190cd1dc09.zip frameworks_base-61e4248f8f6ae8a8f40550cc0800e5190cd1dc09.tar.gz frameworks_base-61e4248f8f6ae8a8f40550cc0800e5190cd1dc09.tar.bz2 |
merge from donut
Diffstat (limited to 'packages')
46 files changed, 1387 insertions, 382 deletions
diff --git a/packages/SettingsProvider/etc/bookmarks.xml b/packages/SettingsProvider/etc/bookmarks.xml index 5fb6608..5af416a 100644 --- a/packages/SettingsProvider/etc/bookmarks.xml +++ b/packages/SettingsProvider/etc/bookmarks.xml @@ -53,6 +53,6 @@ shortcut="s" /> <bookmark package="com.google.android.youtube" - class="com.google.android.youtube.HomePage" + class="com.google.android.youtube.HomeActivity" shortcut="y" /> -</bookmarks>
\ No newline at end of file +</bookmarks> diff --git a/packages/SettingsProvider/res/values-cs/defaults.xml b/packages/SettingsProvider/res/values-cs/defaults.xml new file mode 100644 index 0000000..a7c01b3 --- /dev/null +++ b/packages/SettingsProvider/res/values-cs/defaults.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="def_airplane_mode_radios">"mobil,bluetooth,wifi"</string> + <string name="def_location_providers_allowed">"gps"</string> + <!-- no translation found for def_backup_transport (6764822064303377157) --> + <skip /> +</resources> diff --git a/packages/SettingsProvider/res/values-cs/strings.xml b/packages/SettingsProvider/res/values-cs/strings.xml new file mode 100644 index 0000000..dc75a92 --- /dev/null +++ b/packages/SettingsProvider/res/values-cs/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Paměť pro nastavení"</string> +</resources> diff --git a/packages/SettingsProvider/res/values-de/defaults.xml b/packages/SettingsProvider/res/values-de/defaults.xml new file mode 100644 index 0000000..f85d3f0 --- /dev/null +++ b/packages/SettingsProvider/res/values-de/defaults.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="def_airplane_mode_radios">"Mobilfunk, Bluetooth, WLAN"</string> + <string name="def_location_providers_allowed">"GPS"</string> + <!-- no translation found for def_backup_transport (6764822064303377157) --> + <skip /> +</resources> diff --git a/packages/SettingsProvider/res/values-de/strings.xml b/packages/SettingsProvider/res/values-de/strings.xml new file mode 100644 index 0000000..50c8a14 --- /dev/null +++ b/packages/SettingsProvider/res/values-de/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Einstellungsspeicher"</string> +</resources> diff --git a/packages/SettingsProvider/res/values-es/defaults.xml b/packages/SettingsProvider/res/values-es/defaults.xml new file mode 100644 index 0000000..a64805a --- /dev/null +++ b/packages/SettingsProvider/res/values-es/defaults.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="def_airplane_mode_radios">"móvil,bluetooth,wifi"</string> + <string name="def_location_providers_allowed">"gps"</string> + <!-- no translation found for def_backup_transport (6764822064303377157) --> + <skip /> +</resources> diff --git a/packages/SettingsProvider/res/values-es/strings.xml b/packages/SettingsProvider/res/values-es/strings.xml new file mode 100644 index 0000000..d30d195 --- /dev/null +++ b/packages/SettingsProvider/res/values-es/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Almacenamiento de configuración"</string> +</resources> diff --git a/packages/SettingsProvider/res/values-fr/defaults.xml b/packages/SettingsProvider/res/values-fr/defaults.xml new file mode 100644 index 0000000..56334cc --- /dev/null +++ b/packages/SettingsProvider/res/values-fr/defaults.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="def_airplane_mode_radios">"cellulaire, Bluetooth, Wi-Fi"</string> + <string name="def_location_providers_allowed">"gps"</string> + <!-- no translation found for def_backup_transport (6764822064303377157) --> + <skip /> +</resources> diff --git a/packages/SettingsProvider/res/values-fr/strings.xml b/packages/SettingsProvider/res/values-fr/strings.xml new file mode 100644 index 0000000..686ec8b --- /dev/null +++ b/packages/SettingsProvider/res/values-fr/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Stockage des paramètres"</string> +</resources> diff --git a/packages/SettingsProvider/res/values-it/defaults.xml b/packages/SettingsProvider/res/values-it/defaults.xml new file mode 100644 index 0000000..19c0896 --- /dev/null +++ b/packages/SettingsProvider/res/values-it/defaults.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="def_airplane_mode_radios">"cellulare,bluetooth,wifi"</string> + <string name="def_location_providers_allowed">"gps"</string> + <!-- no translation found for def_backup_transport (6764822064303377157) --> + <skip /> +</resources> diff --git a/packages/SettingsProvider/res/values-it/strings.xml b/packages/SettingsProvider/res/values-it/strings.xml new file mode 100644 index 0000000..29e462f --- /dev/null +++ b/packages/SettingsProvider/res/values-it/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Archiviazione impostazioni"</string> +</resources> diff --git a/packages/SettingsProvider/res/values-nl/defaults.xml b/packages/SettingsProvider/res/values-nl/defaults.xml new file mode 100644 index 0000000..625235a --- /dev/null +++ b/packages/SettingsProvider/res/values-nl/defaults.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="def_airplane_mode_radios">"mobiel,bluetooth,wifi"</string> + <string name="def_location_providers_allowed">"gps"</string> + <!-- no translation found for def_backup_transport (6764822064303377157) --> + <skip /> +</resources> diff --git a/packages/SettingsProvider/res/values-nl/strings.xml b/packages/SettingsProvider/res/values-nl/strings.xml new file mode 100644 index 0000000..b37b535 --- /dev/null +++ b/packages/SettingsProvider/res/values-nl/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Opslagruimte voor instellingen"</string> +</resources> diff --git a/packages/SettingsProvider/res/values-pl/defaults.xml b/packages/SettingsProvider/res/values-pl/defaults.xml new file mode 100644 index 0000000..b60832e --- /dev/null +++ b/packages/SettingsProvider/res/values-pl/defaults.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="def_airplane_mode_radios">"komórka,bluetooth,wifi"</string> + <string name="def_location_providers_allowed">"gps"</string> + <!-- no translation found for def_backup_transport (6764822064303377157) --> + <skip /> +</resources> diff --git a/packages/SettingsProvider/res/values-pl/strings.xml b/packages/SettingsProvider/res/values-pl/strings.xml new file mode 100644 index 0000000..4ab1e91 --- /dev/null +++ b/packages/SettingsProvider/res/values-pl/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Pamięć ustawień"</string> +</resources> diff --git a/packages/SettingsProvider/res/values-zh-rTW/defaults.xml b/packages/SettingsProvider/res/values-zh-rTW/defaults.xml new file mode 100644 index 0000000..fdbba88 --- /dev/null +++ b/packages/SettingsProvider/res/values-zh-rTW/defaults.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="def_airplane_mode_radios">"手機,藍牙,wifi"</string> + <string name="def_location_providers_allowed">"gps"</string> + <!-- no translation found for def_backup_transport (6764822064303377157) --> + <skip /> +</resources> diff --git a/packages/SettingsProvider/res/values-zh-rTW/strings.xml b/packages/SettingsProvider/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000..b24144a --- /dev/null +++ b/packages/SettingsProvider/res/values-zh-rTW/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"設定儲存空間"</string> +</resources> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index b6bc8a5..2b36904 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -16,12 +16,16 @@ package com.android.providers.settings; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.EOFException; import java.util.Arrays; import java.util.HashMap; +import java.util.zip.CRC32; import android.backup.BackupDataInput; import android.backup.BackupDataOutput; @@ -34,7 +38,9 @@ import android.database.Cursor; import android.media.AudioManager; import android.net.Uri; import android.net.wifi.WifiManager; +import android.os.FileUtils; import android.os.ParcelFileDescriptor; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; @@ -52,6 +58,13 @@ public class SettingsBackupAgent extends BackupHelperAgent { private static final String KEY_SYNC = "sync_providers"; private static final String KEY_LOCALE = "locale"; + private static final int STATE_SYSTEM = 0; + private static final int STATE_SECURE = 1; + private static final int STATE_SYNC = 2; + private static final int STATE_LOCALE = 3; + private static final int STATE_WIFI = 4; + private static final int STATE_SIZE = 5; // The number of state items + private static String[] sortedSystemKeys = null; private static String[] sortedSecureKeys = null; @@ -87,20 +100,22 @@ public class SettingsBackupAgent extends BackupHelperAgent { byte[] secureSettingsData = getSecureSettings(); byte[] syncProviders = mSettingsHelper.getSyncProviders(); byte[] locale = mSettingsHelper.getLocaleData(); - - data.writeEntityHeader(KEY_SYSTEM, systemSettingsData.length); - data.writeEntityData(systemSettingsData, systemSettingsData.length); - - data.writeEntityHeader(KEY_SECURE, secureSettingsData.length); - data.writeEntityData(secureSettingsData, secureSettingsData.length); - - data.writeEntityHeader(KEY_SYNC, syncProviders.length); - data.writeEntityData(syncProviders, syncProviders.length); - - data.writeEntityHeader(KEY_LOCALE, locale.length); - data.writeEntityData(locale, locale.length); - - backupFile(FILE_WIFI_SUPPLICANT, data); + byte[] wifiData = getFileData(FILE_WIFI_SUPPLICANT); + + long[] stateChecksums = readOldChecksums(oldState); + + stateChecksums[STATE_SYSTEM] = + writeIfChanged(stateChecksums[STATE_SYSTEM], KEY_SYSTEM, systemSettingsData, data); + stateChecksums[STATE_SECURE] = + writeIfChanged(stateChecksums[STATE_SECURE], KEY_SECURE, secureSettingsData, data); + stateChecksums[STATE_SYNC] = + writeIfChanged(stateChecksums[STATE_SYNC], KEY_SYNC, syncProviders, data); + stateChecksums[STATE_LOCALE] = + writeIfChanged(stateChecksums[STATE_LOCALE], KEY_LOCALE, locale, data); + stateChecksums[STATE_WIFI] = + writeIfChanged(stateChecksums[STATE_WIFI], FILE_WIFI_SUPPLICANT, wifiData, data); + + writeNewChecksums(stateChecksums, newState); } @Override @@ -115,11 +130,15 @@ public class SettingsBackupAgent extends BackupHelperAgent { final int size = data.getDataSize(); if (KEY_SYSTEM.equals(key)) { restoreSettings(data, Settings.System.CONTENT_URI); + mSettingsHelper.applyAudioSettings(); } else if (KEY_SECURE.equals(key)) { restoreSettings(data, Settings.Secure.CONTENT_URI); -// TODO: Re-enable WIFI restore when we figure out a solution for the permissions -// } else if (FILE_WIFI_SUPPLICANT.equals(key)) { -// restoreFile(FILE_WIFI_SUPPLICANT, data); + } else if (FILE_WIFI_SUPPLICANT.equals(key)) { + restoreFile(FILE_WIFI_SUPPLICANT, data); + FileUtils.setPermissions(FILE_WIFI_SUPPLICANT, + FileUtils.S_IRUSR | FileUtils.S_IWUSR | + FileUtils.S_IRGRP | FileUtils.S_IWGRP, + Process.myUid(), Process.WIFI_UID); } else if (KEY_SYNC.equals(key)) { mSettingsHelper.setSyncProviders(data); } else if (KEY_LOCALE.equals(key)) { @@ -132,6 +151,49 @@ public class SettingsBackupAgent extends BackupHelperAgent { } } + private long[] readOldChecksums(ParcelFileDescriptor oldState) throws IOException { + long[] stateChecksums = new long[STATE_SIZE]; + + DataInputStream dataInput = new DataInputStream( + new FileInputStream(oldState.getFileDescriptor())); + for (int i = 0; i < STATE_SIZE; i++) { + try { + stateChecksums[i] = dataInput.readLong(); + } catch (EOFException eof) { + break; + } + } + dataInput.close(); + return stateChecksums; + } + + private void writeNewChecksums(long[] checksums, ParcelFileDescriptor newState) + throws IOException { + DataOutputStream dataOutput = new DataOutputStream( + new FileOutputStream(newState.getFileDescriptor())); + for (int i = 0; i < STATE_SIZE; i++) { + dataOutput.writeLong(checksums[i]); + } + dataOutput.close(); + } + + private long writeIfChanged(long oldChecksum, String key, byte[] data, + BackupDataOutput output) { + CRC32 checkSummer = new CRC32(); + checkSummer.update(data); + long newChecksum = checkSummer.getValue(); + if (oldChecksum == newChecksum) { + return oldChecksum; + } + try { + output.writeEntityHeader(key, data.length); + output.writeEntityData(data, data.length); + } catch (IOException ioe) { + // Bail + } + return newChecksum; + } + private byte[] getSystemSettings() { Cursor sortedCursor = getContentResolver().query(Settings.System.CONTENT_URI, PROJECTION, null, null, Settings.NameValueTable.NAME); @@ -248,7 +310,7 @@ public class SettingsBackupAgent extends BackupHelperAgent { return result; } - private void backupFile(String filename, BackupDataOutput data) { + private byte[] getFileData(String filename) { try { File file = new File(filename); if (file.exists()) { @@ -260,14 +322,13 @@ public class SettingsBackupAgent extends BackupHelperAgent { got = fis.read(bytes, offset, bytes.length - offset); if (got > 0) offset += got; } while (offset < bytes.length && got > 0); - data.writeEntityHeader(filename, bytes.length); - data.writeEntityData(bytes, bytes.length); + return bytes; } else { - data.writeEntityHeader(filename, 0); - data.writeEntityData(EMPTY_DATA, 0); + return EMPTY_DATA; } } catch (IOException ioe) { Log.w(TAG, "Couldn't backup " + filename); + return EMPTY_DATA; } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index 2c5775a..ca739e6 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -167,6 +167,9 @@ public class SettingsHelper { // Check if locale was set by the user: Configuration conf = mContext.getResources().getConfiguration(); Locale loc = conf.locale; + // TODO: The following is not working as intended because the network is forcing a locale + // change after registering. Need to find some other way to detect if the user manually + // changed the locale if (conf.userSetLocale) return; // Don't change if user set it in the SetupWizard final String[] availableLocales = mContext.getAssets().getLocales(); @@ -193,6 +196,14 @@ public class SettingsHelper { } catch (RemoteException e) { // Intentionally left blank } + } + /** + * Informs the audio service of changes to the settings so that + * they can be re-read and applied. + */ + void applyAudioSettings() { + AudioManager am = new AudioManager(mContext); + am.reloadAudioSettings(); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 2abf8b3..c0de9a5 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -126,11 +126,14 @@ public class SettingsProvider extends ContentProvider { // a notification and then using the contract class to get their data, // the system property will be updated and they'll get the new data. + boolean backedUpDataChanged = false; String property = null, table = uri.getPathSegments().get(0); if (table.equals("system")) { property = Settings.System.SYS_PROP_SETTING_VERSION; + backedUpDataChanged = true; } else if (table.equals("secure")) { property = Settings.Secure.SYS_PROP_SETTING_VERSION; + backedUpDataChanged = true; } else if (table.equals("gservices")) { property = Settings.Gservices.SYS_PROP_SETTING_VERSION; } @@ -142,7 +145,9 @@ public class SettingsProvider extends ContentProvider { } // Inform the backup manager about a data change - mBackupManager.dataChanged(); + if (backedUpDataChanged) { + mBackupManager.dataChanged(); + } // Now send the notification through the content framework. String notify = uri.getQueryParameter("notify"); diff --git a/packages/SubscribedFeedsProvider/res/values-cs/strings.xml b/packages/SubscribedFeedsProvider/res/values-cs/strings.xml new file mode 100644 index 0000000..9b782b0 --- /dev/null +++ b/packages/SubscribedFeedsProvider/res/values-cs/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Synchronizace zdrojů"</string> +</resources> diff --git a/packages/SubscribedFeedsProvider/res/values-de/strings.xml b/packages/SubscribedFeedsProvider/res/values-de/strings.xml new file mode 100644 index 0000000..1ade594 --- /dev/null +++ b/packages/SubscribedFeedsProvider/res/values-de/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Feedsynchronisierung"</string> +</resources> diff --git a/packages/SubscribedFeedsProvider/res/values-es/strings.xml b/packages/SubscribedFeedsProvider/res/values-es/strings.xml new file mode 100644 index 0000000..86c6946 --- /dev/null +++ b/packages/SubscribedFeedsProvider/res/values-es/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Sincronización de feeds"</string> +</resources> diff --git a/packages/SubscribedFeedsProvider/res/values-fr/strings.xml b/packages/SubscribedFeedsProvider/res/values-fr/strings.xml new file mode 100644 index 0000000..924b960 --- /dev/null +++ b/packages/SubscribedFeedsProvider/res/values-fr/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Synchronisation des flux"</string> +</resources> diff --git a/packages/SubscribedFeedsProvider/res/values-it/strings.xml b/packages/SubscribedFeedsProvider/res/values-it/strings.xml new file mode 100644 index 0000000..eabb17e --- /dev/null +++ b/packages/SubscribedFeedsProvider/res/values-it/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Sincronizzazione feed"</string> +</resources> diff --git a/packages/SubscribedFeedsProvider/res/values-nl/strings.xml b/packages/SubscribedFeedsProvider/res/values-nl/strings.xml new file mode 100644 index 0000000..b9e82d1 --- /dev/null +++ b/packages/SubscribedFeedsProvider/res/values-nl/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Feeds synchroniseren"</string> +</resources> diff --git a/packages/SubscribedFeedsProvider/res/values-pl/strings.xml b/packages/SubscribedFeedsProvider/res/values-pl/strings.xml new file mode 100644 index 0000000..02da9f3 --- /dev/null +++ b/packages/SubscribedFeedsProvider/res/values-pl/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Synchronizowanie kanałów"</string> +</resources> diff --git a/packages/SubscribedFeedsProvider/res/values-zh-rTW/strings.xml b/packages/SubscribedFeedsProvider/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000..e6643cd --- /dev/null +++ b/packages/SubscribedFeedsProvider/res/values-zh-rTW/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"同步資訊提供"</string> +</resources> diff --git a/packages/TtsService/jni/android_tts_SynthProxy.cpp b/packages/TtsService/jni/android_tts_SynthProxy.cpp index 1958ba9..99d7723 100644 --- a/packages/TtsService/jni/android_tts_SynthProxy.cpp +++ b/packages/TtsService/jni/android_tts_SynthProxy.cpp @@ -33,6 +33,8 @@ #define DEFAULT_TTS_FORMAT AudioSystem::PCM_16_BIT #define DEFAULT_TTS_NB_CHANNELS 1 #define DEFAULT_TTS_BUFFERSIZE 1024 +// TODO use the TTS stream type when available +#define DEFAULT_TTS_STREAM_TYPE AudioSystem::MUSIC #define USAGEMODE_PLAY_IMMEDIATELY 0 #define USAGEMODE_WRITE_TO_FILE 1 @@ -46,22 +48,28 @@ struct fields_t { jmethodID synthProxyMethodPost; }; +// structure to hold the data that is used each time the TTS engine has synthesized more data struct afterSynthData_t { jint jniStorage; int usageMode; FILE* outputFile; + AudioSystem::stream_type streamType; }; // ---------------------------------------------------------------------------- static fields_t javaTTSFields; +// TODO move to synth member once we have multiple simultaneous engines running +static Mutex engineMutex; + // ---------------------------------------------------------------------------- class SynthProxyJniStorage { public : - //jclass tts_class; jobject tts_ref; TtsEngine* mNativeSynthInterface; + void* mEngineLibHandle; AudioTrack* mAudioOut; + AudioSystem::stream_type mStreamType; uint32_t mSampleRate; AudioSystem::audio_format mAudFormat; int mNbChannels; @@ -69,23 +77,31 @@ class SynthProxyJniStorage { size_t mBufferSize; SynthProxyJniStorage() { - //tts_class = NULL; tts_ref = NULL; mNativeSynthInterface = NULL; + mEngineLibHandle = NULL; mAudioOut = NULL; + mStreamType = DEFAULT_TTS_STREAM_TYPE; mSampleRate = DEFAULT_TTS_RATE; mAudFormat = DEFAULT_TTS_FORMAT; mNbChannels = DEFAULT_TTS_NB_CHANNELS; mBufferSize = DEFAULT_TTS_BUFFERSIZE; mBuffer = new int8_t[mBufferSize]; + memset(mBuffer, 0, mBufferSize); } ~SynthProxyJniStorage() { + //LOGV("entering ~SynthProxyJniStorage()"); killAudio(); if (mNativeSynthInterface) { mNativeSynthInterface->shutdown(); mNativeSynthInterface = NULL; } + if (mEngineLibHandle) { + //LOGE("~SynthProxyJniStorage(): before close library"); + int res = dlclose(mEngineLibHandle); + LOGE_IF( res != 0, "~SynthProxyJniStorage(): dlclose returned %d", res); + } delete mBuffer; } @@ -97,66 +113,66 @@ class SynthProxyJniStorage { } } - void createAudioOut(uint32_t rate, AudioSystem::audio_format format, - int channel) { + void createAudioOut(AudioSystem::stream_type streamType, uint32_t rate, + AudioSystem::audio_format format, int channel) { mSampleRate = rate; mAudFormat = format; mNbChannels = channel; - // TODO use the TTS stream type - int streamType = AudioSystem::MUSIC; + mStreamType = streamType; // retrieve system properties to ensure successful creation of the // AudioTrack object for playback int afSampleRate; - if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) { + if (AudioSystem::getOutputSamplingRate(&afSampleRate, mStreamType) != NO_ERROR) { afSampleRate = 44100; } int afFrameCount; - if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) { + if (AudioSystem::getOutputFrameCount(&afFrameCount, mStreamType) != NO_ERROR) { afFrameCount = 2048; } uint32_t afLatency; - if (AudioSystem::getOutputLatency(&afLatency, streamType) != NO_ERROR) { + if (AudioSystem::getOutputLatency(&afLatency, mStreamType) != NO_ERROR) { afLatency = 500; } uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSampleRate); if (minBufCount < 2) minBufCount = 2; int minFrameCount = (afFrameCount * rate * minBufCount)/afSampleRate; - mAudioOut = new AudioTrack(streamType, rate, format, channel, + mAudioOut = new AudioTrack(mStreamType, rate, format, channel, minFrameCount > 4096 ? minFrameCount : 4096, 0, 0, 0, 0); // not using an AudioTrack callback if (mAudioOut->initCheck() != NO_ERROR) { - LOGI("AudioTrack error"); + LOGE("createAudioOut(): AudioTrack error"); delete mAudioOut; mAudioOut = NULL; } else { //LOGI("AudioTrack OK"); + mAudioOut->setVolume(2.0f, 2.0f); mAudioOut->start(); - LOGI("AudioTrack started"); + LOGV("AudioTrack started"); } } }; // ---------------------------------------------------------------------------- -void prepAudioTrack(SynthProxyJniStorage* pJniData, - uint32_t rate, AudioSystem::audio_format format, int channel) -{ +void prepAudioTrack(SynthProxyJniStorage* pJniData, AudioSystem::stream_type streamType, + uint32_t rate, AudioSystem::audio_format format, int channel) { // Don't bother creating a new audiotrack object if the current - // object is already set. + // object is already initialized with the same audio parameters. if ( pJniData->mAudioOut && (rate == pJniData->mSampleRate) && (format == pJniData->mAudFormat) && - (channel == pJniData->mNbChannels) ){ + (channel == pJniData->mNbChannels) && + (streamType == pJniData->mStreamType) ){ return; } if (pJniData->mAudioOut){ pJniData->killAudio(); } - pJniData->createAudioOut(rate, format, channel); + pJniData->createAudioOut(streamType, rate, format, channel); } @@ -186,9 +202,10 @@ static tts_callback_status ttsSynthDoneCB(void *& userdata, uint32_t rate, } if (bufferSize > 0) { - prepAudioTrack(pJniData, rate, format, channel); + prepAudioTrack(pJniData, pForAfter->streamType, rate, format, channel); if (pJniData->mAudioOut) { pJniData->mAudioOut->write(wav, bufferSize); + memset(wav, 0, bufferSize); //LOGV("AudioTrack wrote: %d bytes", bufferSize); } else { LOGE("Can't play, null audiotrack"); @@ -203,6 +220,7 @@ static tts_callback_status ttsSynthDoneCB(void *& userdata, uint32_t rate, } if (bufferSize > 0){ fwrite(wav, 1, bufferSize, pForAfter->outputFile); + memset(wav, 0, bufferSize); } } // Future update: @@ -241,23 +259,25 @@ android_tts_SynthProxy_native_setup(JNIEnv *env, jobject thiz, SynthProxyJniStorage* pJniStorage = new SynthProxyJniStorage(); prepAudioTrack(pJniStorage, - DEFAULT_TTS_RATE, DEFAULT_TTS_FORMAT, DEFAULT_TTS_NB_CHANNELS); + DEFAULT_TTS_STREAM_TYPE, DEFAULT_TTS_RATE, DEFAULT_TTS_FORMAT, DEFAULT_TTS_NB_CHANNELS); const char *nativeSoLibNativeString = env->GetStringUTFChars(nativeSoLib, 0); void *engine_lib_handle = dlopen(nativeSoLibNativeString, RTLD_NOW | RTLD_LOCAL); - if (engine_lib_handle==NULL) { - LOGI("engine_lib_handle==NULL"); + if (engine_lib_handle == NULL) { + LOGE("android_tts_SynthProxy_native_setup(): engine_lib_handle == NULL"); // TODO report error so the TTS can't be used } else { TtsEngine *(*get_TtsEngine)() = reinterpret_cast<TtsEngine* (*)()>(dlsym(engine_lib_handle, "getTtsEngine")); pJniStorage->mNativeSynthInterface = (*get_TtsEngine)(); + pJniStorage->mEngineLibHandle = engine_lib_handle; if (pJniStorage->mNativeSynthInterface) { + Mutex::Autolock l(engineMutex); pJniStorage->mNativeSynthInterface->init(ttsSynthDoneCB); } } @@ -276,10 +296,29 @@ android_tts_SynthProxy_native_setup(JNIEnv *env, jobject thiz, static void android_tts_SynthProxy_native_finalize(JNIEnv *env, jobject thiz, jint jniData) { - if (jniData) { - SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; - delete pSynthData; + //LOGV("entering android_tts_SynthProxy_finalize()"); + if (jniData == 0) { + //LOGE("android_tts_SynthProxy_native_finalize(): invalid JNI data"); + return; } + + Mutex::Autolock l(engineMutex); + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + env->DeleteGlobalRef(pSynthData->tts_ref); + delete pSynthData; + + env->SetIntField(thiz, javaTTSFields.synthProxyFieldJniData, 0); +} + + +static void +android_tts_SynthProxy_shutdown(JNIEnv *env, jobject thiz, jint jniData) +{ + //LOGV("entering android_tts_SynthProxy_shutdown()"); + + // do everything a call to finalize would + android_tts_SynthProxy_native_finalize(env, thiz, jniData); } @@ -321,6 +360,8 @@ android_tts_SynthProxy_setLanguage(JNIEnv *env, jobject thiz, jint jniData, return result; } + Mutex::Autolock l(engineMutex); + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; const char *langNativeString = env->GetStringUTFChars(language, 0); const char *countryNativeString = env->GetStringUTFChars(country, 0); @@ -380,6 +421,8 @@ android_tts_SynthProxy_setSpeechRate(JNIEnv *env, jobject thiz, jint jniData, char buffer [bufSize]; sprintf(buffer, "%d", speechRate); + Mutex::Autolock l(engineMutex); + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; LOGI("setting speech rate to %d", speechRate); @@ -402,6 +445,8 @@ android_tts_SynthProxy_setPitch(JNIEnv *env, jobject thiz, jint jniData, return result; } + Mutex::Autolock l(engineMutex); + int bufSize = 10; char buffer [bufSize]; sprintf(buffer, "%d", pitch); @@ -434,6 +479,8 @@ android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData, return result; } + Mutex::Autolock l(engineMutex); + // Retrieve audio parameters before writing the file header AudioSystem::audio_format encoding = DEFAULT_TTS_FORMAT; uint32_t rate = DEFAULT_TTS_RATE; @@ -468,6 +515,7 @@ android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData, unsigned int unique_identifier; + memset(pSynthData->mBuffer, 0, pSynthData->mBufferSize); result = pSynthData->mNativeSynthInterface->synthesizeText(textNativeString, pSynthData->mBuffer, pSynthData->mBufferSize, (void *)pForAfter); @@ -526,7 +574,7 @@ android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData, static int android_tts_SynthProxy_speak(JNIEnv *env, jobject thiz, jint jniData, - jstring textJavaString) + jstring textJavaString, jint javaStreamType) { int result = TTS_FAILURE; @@ -535,19 +583,22 @@ android_tts_SynthProxy_speak(JNIEnv *env, jobject thiz, jint jniData, return result; } + Mutex::Autolock l(engineMutex); + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; if (pSynthData->mAudioOut) { - pSynthData->mAudioOut->stop(); pSynthData->mAudioOut->start(); } afterSynthData_t* pForAfter = new (afterSynthData_t); pForAfter->jniStorage = jniData; pForAfter->usageMode = USAGEMODE_PLAY_IMMEDIATELY; + pForAfter->streamType = (AudioSystem::stream_type) javaStreamType; if (pSynthData->mNativeSynthInterface) { const char *textNativeString = env->GetStringUTFChars(textJavaString, 0); + memset(pSynthData->mBuffer, 0, pSynthData->mBufferSize); result = pSynthData->mNativeSynthInterface->synthesizeText(textNativeString, pSynthData->mBuffer, pSynthData->mBufferSize, (void *)pForAfter); env->ReleaseStringUTFChars(textJavaString, textNativeString); @@ -569,48 +620,14 @@ android_tts_SynthProxy_stop(JNIEnv *env, jobject thiz, jint jniData) SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; - if (pSynthData->mNativeSynthInterface) { - result = pSynthData->mNativeSynthInterface->stop(); - } if (pSynthData->mAudioOut) { pSynthData->mAudioOut->stop(); } - - return result; -} - - -static void -android_tts_SynthProxy_shutdown(JNIEnv *env, jobject thiz, jint jniData) -{ - if (jniData == 0) { - LOGE("android_tts_SynthProxy_shutdown(): invalid JNI data"); - return; - } - - SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; if (pSynthData->mNativeSynthInterface) { - pSynthData->mNativeSynthInterface->shutdown(); - pSynthData->mNativeSynthInterface = NULL; - } -} - - -// TODO add buffer format -static void -android_tts_SynthProxy_playAudioBuffer(JNIEnv *env, jobject thiz, jint jniData, - int bufferPointer, int bufferSize) -{ -LOGI("android_tts_SynthProxy_playAudioBuffer"); - if (jniData == 0) { - LOGE("android_tts_SynthProxy_playAudioBuffer(): invalid JNI data"); - return; + result = pSynthData->mNativeSynthInterface->stop(); } - SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; - short* wav = (short*) bufferPointer; - pSynthData->mAudioOut->write(wav, bufferSize); - //LOGI("AudioTrack wrote: %d bytes", bufferSize); + return result; } @@ -672,7 +689,7 @@ static JNINativeMethod gMethods[] = { (void*)android_tts_SynthProxy_stop }, { "native_speak", - "(ILjava/lang/String;)I", + "(ILjava/lang/String;I)I", (void*)android_tts_SynthProxy_speak }, { "native_synthesizeToFile", @@ -699,10 +716,6 @@ static JNINativeMethod gMethods[] = { "(II)I", (void*)android_tts_SynthProxy_setPitch }, - { "native_playAudioBuffer", - "(III)V", - (void*)android_tts_SynthProxy_playAudioBuffer - }, { "native_getLanguage", "(I)[Ljava/lang/String;", (void*)android_tts_SynthProxy_getLanguage diff --git a/packages/TtsService/src/android/tts/SynthProxy.java b/packages/TtsService/src/android/tts/SynthProxy.java index bb16b14..a0814aa 100755 --- a/packages/TtsService/src/android/tts/SynthProxy.java +++ b/packages/TtsService/src/android/tts/SynthProxy.java @@ -15,6 +15,8 @@ */ package android.tts; +import android.media.AudioManager; +import android.media.AudioSystem; import android.util.Log; import java.lang.ref.WeakReference; @@ -52,8 +54,13 @@ public class SynthProxy { /** * Synthesize speech and speak it directly using AudioTrack. */ - public int speak(String text) { - return native_speak(mJniData, text); + public int speak(String text, int streamType) { + if ((streamType > -1) && (streamType < AudioSystem.getNumStreamTypes())) { + return native_speak(mJniData, text, streamType); + } else { + Log.e("SynthProxy", "Trying to speak with invalid stream type " + streamType); + return native_speak(mJniData, text, AudioManager.STREAM_MUSIC); + } } /** @@ -102,13 +109,6 @@ public class SynthProxy { } /** - * Plays the given audio buffer. - */ - public void playAudioBuffer(int bufferPointer, int bufferSize) { - native_playAudioBuffer(mJniData, bufferPointer, bufferSize); - } - - /** * Returns the currently set language, country and variant information. */ public String[] getLanguage() { @@ -156,7 +156,7 @@ public class SynthProxy { private native final int native_stop(int jniData); - private native final int native_speak(int jniData, String text); + private native final int native_speak(int jniData, String text, int streamType); private native final int native_synthesizeToFile(int jniData, String text, String filename); @@ -173,9 +173,6 @@ public class SynthProxy { private native final int native_setPitch(int jniData, int speechRate); - // TODO add buffer format - private native final void native_playAudioBuffer(int jniData, int bufferPointer, int bufferSize); - private native final String[] native_getLanguage(int jniData); private native final int native_getRate(int jniData); diff --git a/packages/TtsService/src/android/tts/TtsService.java b/packages/TtsService/src/android/tts/TtsService.java index a713edf..e52ba80 100755 --- a/packages/TtsService/src/android/tts/TtsService.java +++ b/packages/TtsService/src/android/tts/TtsService.java @@ -22,6 +22,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.net.Uri; @@ -38,6 +39,8 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Locale; import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.TimeUnit; + /** * @hide Synthesizes speech from text. This is implemented as a service so that @@ -57,23 +60,29 @@ public class TtsService extends Service implements OnCompletionListener { public int mType = TEXT; public long mDuration = 0; public String mFilename = null; + public String mCallingApp = ""; - public SpeechItem(String text, ArrayList<String> params, int itemType) { + public SpeechItem(String source, String text, ArrayList<String> params, int itemType) { mText = text; mParams = params; mType = itemType; + mCallingApp = source; } - public SpeechItem(long silenceTime) { + public SpeechItem(String source, long silenceTime, ArrayList<String> params) { mDuration = silenceTime; + mParams = params; mType = SILENCE; + mCallingApp = source; } - public SpeechItem(String text, ArrayList<String> params, int itemType, String filename) { + public SpeechItem(String source, String text, ArrayList<String> params, + int itemType, String filename) { mText = text; mParams = params; mType = itemType; mFilename = filename; + mCallingApp = source; } } @@ -103,18 +112,26 @@ public class TtsService extends Service implements OnCompletionListener { private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000; private static final int MAX_FILENAME_LENGTH = 250; + // TODO use the TTS stream type when available + private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_MUSIC; private static final String ACTION = "android.intent.action.START_TTS_SERVICE"; private static final String CATEGORY = "android.intent.category.TTS"; private static final String PKGNAME = "android.tts"; - final RemoteCallbackList<android.speech.tts.ITtsCallback> mCallbacks = new RemoteCallbackList<ITtsCallback>(); + private final RemoteCallbackList<ITtsCallback> mCallbacks + = new RemoteCallbackList<ITtsCallback>(); + + private HashMap<String, ITtsCallback> mCallbacksMap; private Boolean mIsSpeaking; private ArrayList<SpeechItem> mSpeechQueue; private HashMap<String, SoundResource> mEarcons; private HashMap<String, SoundResource> mUtterances; private MediaPlayer mPlayer; + private SpeechItem mCurrentSpeechItem; + private HashMap<SpeechItem, Boolean> mKillList; // Used to ensure that in-flight synth calls + // are killed when stop is used. private TtsService mSelf; private ContentResolver mResolver; @@ -122,25 +139,30 @@ public class TtsService extends Service implements OnCompletionListener { private final ReentrantLock speechQueueLock = new ReentrantLock(); private final ReentrantLock synthesizerLock = new ReentrantLock(); - private SynthProxy nativeSynth; + private static SynthProxy sNativeSynth = null; @Override public void onCreate() { super.onCreate(); - //Log.i("TTS", "TTS starting"); + Log.i("TtsService", "TtsService.onCreate()"); mResolver = getContentResolver(); String soLibPath = "/system/lib/libttspico.so"; - nativeSynth = new SynthProxy(soLibPath); + if (sNativeSynth == null) { + sNativeSynth = new SynthProxy(soLibPath); + } mSelf = this; mIsSpeaking = false; mEarcons = new HashMap<String, SoundResource>(); mUtterances = new HashMap<String, SoundResource>(); + mCallbacksMap = new HashMap<String, android.speech.tts.ITtsCallback>(); mSpeechQueue = new ArrayList<SpeechItem>(); mPlayer = null; + mCurrentSpeechItem = null; + mKillList = new HashMap<SpeechItem, Boolean>(); setDefaultSettings(); } @@ -151,7 +173,8 @@ public class TtsService extends Service implements OnCompletionListener { // Don't hog the media player cleanUpPlayer(); - nativeSynth.shutdown(); + sNativeSynth.shutdown(); + sNativeSynth = null; // Unregister all callbacks. mCallbacks.kill(); @@ -159,10 +182,10 @@ public class TtsService extends Service implements OnCompletionListener { private void setDefaultSettings() { - setLanguage(this.getDefaultLanguage(), getDefaultCountry(), getDefaultLocVariant()); + setLanguage("", this.getDefaultLanguage(), getDefaultCountry(), getDefaultLocVariant()); // speech rate - setSpeechRate(getDefaultRate()); + setSpeechRate("", getDefaultRate()); } @@ -217,38 +240,38 @@ public class TtsService extends Service implements OnCompletionListener { } - private int setSpeechRate(int rate) { + private int setSpeechRate(String callingApp, int rate) { if (isDefaultEnforced()) { - return nativeSynth.setSpeechRate(getDefaultRate()); + return sNativeSynth.setSpeechRate(getDefaultRate()); } else { - return nativeSynth.setSpeechRate(rate); + return sNativeSynth.setSpeechRate(rate); } } - private int setPitch(int pitch) { - return nativeSynth.setPitch(pitch); + private int setPitch(String callingApp, int pitch) { + return sNativeSynth.setPitch(pitch); } private int isLanguageAvailable(String lang, String country, String variant) { - //Log.v("TTS", "TtsService.isLanguageAvailable(" + lang + ", " + country + ", " +variant+")"); - return nativeSynth.isLanguageAvailable(lang, country, variant); + //Log.v("TtsService", "TtsService.isLanguageAvailable(" + lang + ", " + country + ", " +variant+")"); + return sNativeSynth.isLanguageAvailable(lang, country, variant); } private String[] getLanguage() { - return nativeSynth.getLanguage(); + return sNativeSynth.getLanguage(); } - private int setLanguage(String lang, String country, String variant) { - //Log.v("TTS", "TtsService.setLanguage(" + lang + ", " + country + ", " + variant + ")"); + private int setLanguage(String callingApp, String lang, String country, String variant) { + Log.v("TtsService", "TtsService.setLanguage(" + lang + ", " + country + ", " + variant + ")"); if (isDefaultEnforced()) { - return nativeSynth.setLanguage(getDefaultLanguage(), getDefaultCountry(), + return sNativeSynth.setLanguage(getDefaultLanguage(), getDefaultCountry(), getDefaultLocVariant()); } else { - return nativeSynth.setLanguage(lang, country, variant); + return sNativeSynth.setLanguage(lang, country, variant); } } @@ -263,7 +286,7 @@ public class TtsService extends Service implements OnCompletionListener { * @param resId * The resource ID of the sound within its package */ - private void addSpeech(String text, String packageName, int resId) { + private void addSpeech(String callingApp, String text, String packageName, int resId) { mUtterances.put(text, new SoundResource(packageName, resId)); } @@ -276,7 +299,7 @@ public class TtsService extends Service implements OnCompletionListener { * The filename of the sound resource. This must be a complete * path like: (/sdcard/mysounds/mysoundbite.mp3). */ - private void addSpeech(String text, String filename) { + private void addSpeech(String callingApp, String text, String filename) { mUtterances.put(text, new SoundResource(filename)); } @@ -290,7 +313,7 @@ public class TtsService extends Service implements OnCompletionListener { * @param resId * The resource ID of the sound within its package */ - private void addEarcon(String earcon, String packageName, int resId) { + private void addEarcon(String callingApp, String earcon, String packageName, int resId) { mEarcons.put(earcon, new SoundResource(packageName, resId)); } @@ -303,7 +326,7 @@ public class TtsService extends Service implements OnCompletionListener { * The filename of the sound resource. This must be a complete * path like: (/sdcard/mysounds/mysoundbite.mp3). */ - private void addEarcon(String earcon, String filename) { + private void addEarcon(String callingApp, String earcon, String filename) { mEarcons.put(earcon, new SoundResource(filename)); } @@ -313,17 +336,20 @@ public class TtsService extends Service implements OnCompletionListener { * @param text * The text that should be spoken * @param queueMode - * 0 for no queue (interrupts all previous utterances), 1 for - * queued + * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances), + * TextToSpeech.TTS_QUEUE_ADD for queued * @param params * An ArrayList of parameters. This is not implemented for all * engines. */ - private int speak(String text, int queueMode, ArrayList<String> params) { - if (queueMode == 0) { - stop(); + private int speak(String callingApp, String text, int queueMode, ArrayList<String> params) { + Log.v("TtsService", "TTS service received " + text); + if (queueMode == TextToSpeech.TTS_QUEUE_FLUSH) { + stop(callingApp); + } else if (queueMode == 2) { + stopAll(callingApp); } - mSpeechQueue.add(new SpeechItem(text, params, SpeechItem.TEXT)); + mSpeechQueue.add(new SpeechItem(callingApp, text, params, SpeechItem.TEXT)); if (!mIsSpeaking) { processSpeechQueue(); } @@ -336,18 +362,20 @@ public class TtsService extends Service implements OnCompletionListener { * @param earcon * The earcon that should be played * @param queueMode - * 0 for no queue (interrupts all previous utterances), 1 for - * queued + * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances), + * TextToSpeech.TTS_QUEUE_ADD for queued * @param params * An ArrayList of parameters. This is not implemented for all * engines. */ - private int playEarcon(String earcon, int queueMode, + private int playEarcon(String callingApp, String earcon, int queueMode, ArrayList<String> params) { - if (queueMode == 0) { - stop(); + if (queueMode == TextToSpeech.TTS_QUEUE_FLUSH) { + stop(callingApp); + } else if (queueMode == 2) { + stopAll(callingApp); } - mSpeechQueue.add(new SpeechItem(earcon, params, SpeechItem.EARCON)); + mSpeechQueue.add(new SpeechItem(callingApp, earcon, params, SpeechItem.EARCON)); if (!mIsSpeaking) { processSpeechQueue(); } @@ -355,49 +383,153 @@ public class TtsService extends Service implements OnCompletionListener { } /** - * Stops all speech output and removes any utterances still in the queue. + * Stops all speech output and removes any utterances still in the queue for the calling app. */ - private int stop() { - Log.i("TTS", "Stopping"); - mSpeechQueue.clear(); + private int stop(String callingApp) { + int result = TextToSpeech.TTS_ERROR; + boolean speechQueueAvailable = false; + try{ + // If the queue is locked for more than 1 second, + // something has gone very wrong with processSpeechQueue. + speechQueueAvailable = speechQueueLock.tryLock(1000, TimeUnit.MILLISECONDS); + if (speechQueueAvailable) { + Log.i("TtsService", "Stopping"); + for (int i = mSpeechQueue.size() - 1; i > -1; i--){ + if (mSpeechQueue.get(i).mCallingApp.equals(callingApp)){ + mSpeechQueue.remove(i); + } + } + if ((mCurrentSpeechItem != null) && + mCurrentSpeechItem.mCallingApp.equals(callingApp)) { + result = sNativeSynth.stop(); + mKillList.put(mCurrentSpeechItem, true); + if (mPlayer != null) { + try { + mPlayer.stop(); + } catch (IllegalStateException e) { + // Do nothing, the player is already stopped. + } + } + mIsSpeaking = false; + mCurrentSpeechItem = null; + } else { + result = TextToSpeech.TTS_SUCCESS; + } + Log.i("TtsService", "Stopped"); + } + } catch (InterruptedException e) { + Log.e("TtsService", "TTS stop: tryLock interrupted"); + e.printStackTrace(); + } finally { + // This check is needed because finally will always run; even if the + // method returns somewhere in the try block. + if (speechQueueAvailable) { + speechQueueLock.unlock(); + } + return result; + } + } - int result = nativeSynth.stop(); - mIsSpeaking = false; - if (mPlayer != null) { - try { - mPlayer.stop(); - } catch (IllegalStateException e) { - // Do nothing, the player is already stopped. + + + /** + * Stops all speech output and removes any utterances still in the queue globally. + */ + private int stopAll(String callingApp) { + int result = TextToSpeech.TTS_ERROR; + boolean speechQueueAvailable = false; + try{ + // If the queue is locked for more than 1 second, + // something has gone very wrong with processSpeechQueue. + speechQueueAvailable = speechQueueLock.tryLock(1000, TimeUnit.MILLISECONDS); + if (speechQueueAvailable) { + for (int i = mSpeechQueue.size() - 1; i > -1; i--){ + if (mSpeechQueue.get(i).mType != SpeechItem.TEXT_TO_FILE){ + mSpeechQueue.remove(i); + } + } + if ((mCurrentSpeechItem != null) && + ((mCurrentSpeechItem.mType != SpeechItem.TEXT_TO_FILE) || + mCurrentSpeechItem.mCallingApp.equals(callingApp))) { + result = sNativeSynth.stop(); + mKillList.put(mCurrentSpeechItem, true); + if (mPlayer != null) { + try { + mPlayer.stop(); + } catch (IllegalStateException e) { + // Do nothing, the player is already stopped. + } + } + mIsSpeaking = false; + mCurrentSpeechItem = null; + } else { + result = TextToSpeech.TTS_SUCCESS; + } + Log.i("TtsService", "Stopped all"); } + } catch (InterruptedException e) { + Log.e("TtsService", "TTS stopAll: tryLock interrupted"); + e.printStackTrace(); + } finally { + // This check is needed because finally will always run; even if the + // method returns somewhere in the try block. + if (speechQueueAvailable) { + speechQueueLock.unlock(); + } + return result; } - Log.i("TTS", "Stopped"); - return result; } public void onCompletion(MediaPlayer arg0) { + String callingApp = mCurrentSpeechItem.mCallingApp; + ArrayList<String> params = mCurrentSpeechItem.mParams; + String utteranceId = ""; + if (params != null){ + for (int i = 0; i < params.size() - 1; i = i + 2){ + String param = params.get(i); + if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_UTTERANCE_ID)){ + utteranceId = params.get(i+1); + } + } + } + if (utteranceId.length() > 0){ + dispatchUtteranceCompletedCallback(utteranceId, callingApp); + } processSpeechQueue(); } - private int playSilence(long duration, int queueMode, + private int playSilence(String callingApp, long duration, int queueMode, ArrayList<String> params) { - if (queueMode == 0) { - stop(); + if (queueMode == TextToSpeech.TTS_QUEUE_FLUSH) { + stop(callingApp); } - mSpeechQueue.add(new SpeechItem(duration)); + mSpeechQueue.add(new SpeechItem(callingApp, duration, params)); if (!mIsSpeaking) { processSpeechQueue(); } return TextToSpeech.TTS_SUCCESS; } - private void silence(final long duration) { + private void silence(final SpeechItem speechItem) { class SilenceThread implements Runnable { public void run() { + String utteranceId = ""; + if (speechItem.mParams != null){ + for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){ + String param = speechItem.mParams.get(i); + if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_UTTERANCE_ID)){ + utteranceId = speechItem.mParams.get(i+1); + } + } + } try { - Thread.sleep(duration); + Thread.sleep(speechItem.mDuration); } catch (InterruptedException e) { e.printStackTrace(); } finally { + if (utteranceId.length() > 0){ + dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp); + } processSpeechQueue(); } } @@ -407,47 +539,70 @@ public class TtsService extends Service implements OnCompletionListener { slnc.start(); } - private void speakInternalOnly(final String text, - final ArrayList<String> params) { + private void speakInternalOnly(final SpeechItem speechItem) { class SynthThread implements Runnable { public void run() { boolean synthAvailable = false; + String utteranceId = ""; try { synthAvailable = synthesizerLock.tryLock(); if (!synthAvailable) { Thread.sleep(100); Thread synth = (new Thread(new SynthThread())); - synth.setPriority(Thread.MIN_PRIORITY); + //synth.setPriority(Thread.MIN_PRIORITY); synth.start(); return; } - if (params != null){ - String language = ""; - String country = ""; - String variant = ""; - for (int i = 0; i < params.size() - 1; i = i + 2){ - String param = params.get(i); - if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_RATE)){ - setSpeechRate(Integer.parseInt(params.get(i+1))); - } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_LANGUAGE)){ - language = params.get(i+1); - } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_COUNTRY)){ - country = params.get(i+1); - } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_VARIANT)){ - variant = params.get(i+1); + int streamType = DEFAULT_STREAM_TYPE; + String language = ""; + String country = ""; + String variant = ""; + String speechRate = ""; + if (speechItem.mParams != null){ + for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){ + String param = speechItem.mParams.get(i); + if (param != null) { + if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_RATE)) { + speechRate = speechItem.mParams.get(i+1); + } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_LANGUAGE)){ + language = speechItem.mParams.get(i+1); + } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_COUNTRY)){ + country = speechItem.mParams.get(i+1); + } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_VARIANT)){ + variant = speechItem.mParams.get(i+1); + } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_UTTERANCE_ID)){ + utteranceId = speechItem.mParams.get(i+1); + } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_STREAM)) { + try { + streamType + = Integer.parseInt(speechItem.mParams.get(i + 1)); + } catch (NumberFormatException e) { + streamType = DEFAULT_STREAM_TYPE; + } + } } } + } + // Only do the synthesis if it has not been killed by a subsequent utterance. + if (mKillList.get(speechItem) == null) { if (language.length() > 0){ - setLanguage(language, country, variant); + setLanguage("", language, country, variant); + } + if (speechRate.length() > 0){ + setSpeechRate("", Integer.parseInt(speechRate)); } + sNativeSynth.speak(speechItem.mText, streamType); } - nativeSynth.speak(text); } catch (InterruptedException e) { + Log.e("TtsService", "TTS speakInternalOnly(): tryLock interrupted"); e.printStackTrace(); } finally { // This check is needed because finally will always run; // even if the // method returns somewhere in the try block. + if (utteranceId.length() > 0){ + dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp); + } if (synthAvailable) { synthesizerLock.unlock(); } @@ -456,52 +611,67 @@ public class TtsService extends Service implements OnCompletionListener { } } Thread synth = (new Thread(new SynthThread())); - synth.setPriority(Thread.MIN_PRIORITY); + //synth.setPriority(Thread.MIN_PRIORITY); synth.start(); } - private void synthToFileInternalOnly(final String text, - final ArrayList<String> params, final String filename) { + private void synthToFileInternalOnly(final SpeechItem speechItem) { class SynthThread implements Runnable { public void run() { - Log.i("TTS", "Synthesizing to " + filename); boolean synthAvailable = false; + String utteranceId = ""; + Log.i("TtsService", "Synthesizing to " + speechItem.mFilename); try { synthAvailable = synthesizerLock.tryLock(); if (!synthAvailable) { Thread.sleep(100); Thread synth = (new Thread(new SynthThread())); - synth.setPriority(Thread.MIN_PRIORITY); + //synth.setPriority(Thread.MIN_PRIORITY); synth.start(); return; } - if (params != null){ - String language = ""; - String country = ""; - String variant = ""; - for (int i = 0; i < params.size() - 1; i = i + 2){ - String param = params.get(i); - if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_RATE)){ - setSpeechRate(Integer.parseInt(params.get(i+1))); - } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_LANGUAGE)){ - language = params.get(i+1); - } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_COUNTRY)){ - country = params.get(i+1); - } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_VARIANT)){ - variant = params.get(i+1); + String language = ""; + String country = ""; + String variant = ""; + String speechRate = ""; + if (speechItem.mParams != null){ + for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){ + String param = speechItem.mParams.get(i); + if (param != null) { + if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_RATE)) { + speechRate = speechItem.mParams.get(i+1); + } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_LANGUAGE)){ + language = speechItem.mParams.get(i+1); + } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_COUNTRY)){ + country = speechItem.mParams.get(i+1); + } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_VARIANT)){ + variant = speechItem.mParams.get(i+1); + } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_UTTERANCE_ID)){ + utteranceId = speechItem.mParams.get(i+1); + } } } + } + // Only do the synthesis if it has not been killed by a subsequent utterance. + if (mKillList.get(speechItem) == null){ if (language.length() > 0){ - setLanguage(language, country, variant); + setLanguage("", language, country, variant); + } + if (speechRate.length() > 0){ + setSpeechRate("", Integer.parseInt(speechRate)); } + sNativeSynth.synthesizeToFile(speechItem.mText, speechItem.mFilename); } - nativeSynth.synthesizeToFile(text, filename); } catch (InterruptedException e) { + Log.e("TtsService", "TTS synthToFileInternalOnly(): tryLock interrupted"); e.printStackTrace(); } finally { // This check is needed because finally will always run; // even if the // method returns somewhere in the try block. + if (utteranceId.length() > 0){ + dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp); + } if (synthAvailable) { synthesizerLock.unlock(); } @@ -510,7 +680,7 @@ public class TtsService extends Service implements OnCompletionListener { } } Thread synth = (new Thread(new SynthThread())); - synth.setPriority(Thread.MIN_PRIORITY); + //synth.setPriority(Thread.MIN_PRIORITY); synth.start(); } @@ -532,26 +702,30 @@ public class TtsService extends Service implements OnCompletionListener { sendBroadcast(i); } - private void dispatchSpeechCompletedCallbacks(String mark) { - Log.i("TTS callback", "dispatch started"); + + private void dispatchUtteranceCompletedCallback(String utteranceId, String packageName) { + ITtsCallback cb = mCallbacksMap.get(packageName); + if (cb == null){ + return; + } + Log.i("TtsService", "TTS callback: dispatch started"); // Broadcast to all clients the new value. final int N = mCallbacks.beginBroadcast(); - for (int i = 0; i < N; i++) { - try { - mCallbacks.getBroadcastItem(i).markReached(mark); - } catch (RemoteException e) { - // The RemoteCallbackList will take care of removing - // the dead object for us. - } + try { + cb.utteranceCompleted(utteranceId); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing + // the dead object for us. } mCallbacks.finishBroadcast(); - Log.i("TTS callback", "dispatch completed to " + N); + Log.i("TtsService", "TTS callback: dispatch completed to " + N); } private SpeechItem splitCurrentTextIfNeeded(SpeechItem currentSpeechItem){ if (currentSpeechItem.mText.length() < MAX_SPEECH_ITEM_CHAR_LENGTH){ return currentSpeechItem; } else { + String callingApp = currentSpeechItem.mCallingApp; ArrayList<SpeechItem> splitItems = new ArrayList<SpeechItem>(); int start = 0; int end = start + MAX_SPEECH_ITEM_CHAR_LENGTH - 1; @@ -559,13 +733,13 @@ public class TtsService extends Service implements OnCompletionListener { SpeechItem splitItem; while (end < currentSpeechItem.mText.length()){ splitText = currentSpeechItem.mText.substring(start, end); - splitItem = new SpeechItem(splitText, null, SpeechItem.TEXT); + splitItem = new SpeechItem(callingApp, splitText, null, SpeechItem.TEXT); splitItems.add(splitItem); start = end; end = start + MAX_SPEECH_ITEM_CHAR_LENGTH - 1; } splitText = currentSpeechItem.mText.substring(start); - splitItem = new SpeechItem(splitText, null, SpeechItem.TEXT); + splitItem = new SpeechItem(callingApp, splitText, null, SpeechItem.TEXT); splitItems.add(splitItem); mSpeechQueue.remove(0); for (int i = splitItems.size() - 1; i >= 0; i--){ @@ -588,23 +762,21 @@ public class TtsService extends Service implements OnCompletionListener { return; } - SpeechItem currentSpeechItem = mSpeechQueue.get(0); + mCurrentSpeechItem = mSpeechQueue.get(0); mIsSpeaking = true; - SoundResource sr = getSoundResource(currentSpeechItem); + SoundResource sr = getSoundResource(mCurrentSpeechItem); // Synth speech as needed - synthesizer should call // processSpeechQueue to continue running the queue - Log.i("TTS processing: ", currentSpeechItem.mText); + Log.i("TtsService", "TTS processing: " + mCurrentSpeechItem.mText); if (sr == null) { - if (currentSpeechItem.mType == SpeechItem.TEXT) { - currentSpeechItem = splitCurrentTextIfNeeded(currentSpeechItem); - speakInternalOnly(currentSpeechItem.mText, - currentSpeechItem.mParams); - } else if (currentSpeechItem.mType == SpeechItem.TEXT_TO_FILE) { - synthToFileInternalOnly(currentSpeechItem.mText, - currentSpeechItem.mParams, currentSpeechItem.mFilename); + if (mCurrentSpeechItem.mType == SpeechItem.TEXT) { + mCurrentSpeechItem = splitCurrentTextIfNeeded(mCurrentSpeechItem); + speakInternalOnly(mCurrentSpeechItem); + } else if (mCurrentSpeechItem.mType == SpeechItem.TEXT_TO_FILE) { + synthToFileInternalOnly(mCurrentSpeechItem); } else { // This is either silence or an earcon that was missing - silence(currentSpeechItem.mDuration); + silence(mCurrentSpeechItem); } } else { cleanUpPlayer(); @@ -615,8 +787,7 @@ public class TtsService extends Service implements OnCompletionListener { // Utterance is part of the app calling the library Context ctx; try { - ctx = this.createPackageContext(sr.mSourcePackageName, - 0); + ctx = this.createPackageContext(sr.mSourcePackageName, 0); } catch (NameNotFoundException e) { e.printStackTrace(); mSpeechQueue.remove(0); // Remove it from the queue and @@ -639,6 +810,7 @@ public class TtsService extends Service implements OnCompletionListener { } mPlayer.setOnCompletionListener(this); try { + mPlayer.setAudioStreamType(getStreamTypeFromParams(mCurrentSpeechItem.mParams)); mPlayer.start(); } catch (IllegalStateException e) { mSpeechQueue.clear(); @@ -659,6 +831,24 @@ public class TtsService extends Service implements OnCompletionListener { } } + private int getStreamTypeFromParams(ArrayList<String> paramList) { + int streamType = DEFAULT_STREAM_TYPE; + if (paramList == null) { + return streamType; + } + for (int i = 0; i < paramList.size() - 1; i = i + 2) { + String param = paramList.get(i); + if ((param != null) && (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_STREAM))) { + try { + streamType = Integer.parseInt(paramList.get(i + 1)); + } catch (NumberFormatException e) { + streamType = DEFAULT_STREAM_TYPE; + } + } + } + return streamType; + } + private void cleanUpPlayer() { if (mPlayer != null) { mPlayer.release(); @@ -679,7 +869,7 @@ public class TtsService extends Service implements OnCompletionListener { * something like "/sdcard/myappsounds/mysound.wav". * @return A boolean that indicates if the synthesis succeeded */ - private boolean synthesizeToFile(String text, ArrayList<String> params, + private boolean synthesizeToFile(String callingApp, String text, ArrayList<String> params, String filename) { // Don't allow a filename that is too long if (filename.length() > MAX_FILENAME_LENGTH) { @@ -690,7 +880,7 @@ public class TtsService extends Service implements OnCompletionListener { if (text.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH){ return false; } - mSpeechQueue.add(new SpeechItem(text, params, SpeechItem.TEXT_TO_FILE, filename)); + mSpeechQueue.add(new SpeechItem(callingApp, text, params, SpeechItem.TEXT_TO_FILE, filename)); if (!mIsSpeaking) { processSpeechQueue(); } @@ -711,14 +901,22 @@ public class TtsService extends Service implements OnCompletionListener { private final android.speech.tts.ITts.Stub mBinder = new Stub() { - public void registerCallback(ITtsCallback cb) { - if (cb != null) + public int registerCallback(String packageName, ITtsCallback cb) { + if (cb != null) { mCallbacks.register(cb); + mCallbacksMap.put(packageName, cb); + return TextToSpeech.TTS_SUCCESS; + } + return TextToSpeech.TTS_ERROR; } - public void unregisterCallback(ITtsCallback cb) { - if (cb != null) + public int unregisterCallback(String packageName, ITtsCallback cb) { + if (cb != null) { + mCallbacksMap.remove(packageName); mCallbacks.unregister(cb); + return TextToSpeech.TTS_SUCCESS; + } + return TextToSpeech.TTS_ERROR; } /** @@ -728,18 +926,18 @@ public class TtsService extends Service implements OnCompletionListener { * @param text * The text that should be spoken * @param queueMode - * 0 for no queue (interrupts all previous utterances), 1 for - * queued + * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances) + * TextToSpeech.TTS_QUEUE_ADD for queued * @param params * An ArrayList of parameters. The first element of this * array controls the type of voice to use. */ - public int speak(String text, int queueMode, String[] params) { + public int speak(String callingApp, String text, int queueMode, String[] params) { ArrayList<String> speakingParams = new ArrayList<String>(); if (params != null) { speakingParams = new ArrayList<String>(Arrays.asList(params)); } - return mSelf.speak(text, queueMode, speakingParams); + return mSelf.speak(callingApp, text, queueMode, speakingParams); } /** @@ -748,17 +946,17 @@ public class TtsService extends Service implements OnCompletionListener { * @param earcon * The earcon that should be played * @param queueMode - * 0 for no queue (interrupts all previous utterances), 1 for - * queued + * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances) + * TextToSpeech.TTS_QUEUE_ADD for queued * @param params * An ArrayList of parameters. */ - public int playEarcon(String earcon, int queueMode, String[] params) { + public int playEarcon(String callingApp, String earcon, int queueMode, String[] params) { ArrayList<String> speakingParams = new ArrayList<String>(); if (params != null) { speakingParams = new ArrayList<String>(Arrays.asList(params)); } - return mSelf.playEarcon(earcon, queueMode, speakingParams); + return mSelf.playEarcon(callingApp, earcon, queueMode, speakingParams); } /** @@ -767,25 +965,25 @@ public class TtsService extends Service implements OnCompletionListener { * @param duration * The duration of the silence that should be played * @param queueMode - * 0 for no queue (interrupts all previous utterances), 1 for - * queued + * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances) + * TextToSpeech.TTS_QUEUE_ADD for queued * @param params * An ArrayList of parameters. */ - public int playSilence(long duration, int queueMode, String[] params) { + public int playSilence(String callingApp, long duration, int queueMode, String[] params) { ArrayList<String> speakingParams = new ArrayList<String>(); if (params != null) { speakingParams = new ArrayList<String>(Arrays.asList(params)); } - return mSelf.playSilence(duration, queueMode, speakingParams); + return mSelf.playSilence(callingApp, duration, queueMode, speakingParams); } /** * Stops all speech output and removes any utterances still in the * queue. */ - public int stop() { - return mSelf.stop(); + public int stop(String callingApp) { + return mSelf.stop(callingApp); } /** @@ -807,8 +1005,8 @@ public class TtsService extends Service implements OnCompletionListener { * @param resId * The resource ID of the sound within its package */ - public void addSpeech(String text, String packageName, int resId) { - mSelf.addSpeech(text, packageName, resId); + public void addSpeech(String callingApp, String text, String packageName, int resId) { + mSelf.addSpeech(callingApp, text, packageName, resId); } /** @@ -820,8 +1018,8 @@ public class TtsService extends Service implements OnCompletionListener { * The filename of the sound resource. This must be a * complete path like: (/sdcard/mysounds/mysoundbite.mp3). */ - public void addSpeechFile(String text, String filename) { - mSelf.addSpeech(text, filename); + public void addSpeechFile(String callingApp, String text, String filename) { + mSelf.addSpeech(callingApp, text, filename); } /** @@ -834,8 +1032,8 @@ public class TtsService extends Service implements OnCompletionListener { * @param resId * The resource ID of the sound within its package */ - public void addEarcon(String earcon, String packageName, int resId) { - mSelf.addEarcon(earcon, packageName, resId); + public void addEarcon(String callingApp, String earcon, String packageName, int resId) { + mSelf.addEarcon(callingApp, earcon, packageName, resId); } /** @@ -847,8 +1045,8 @@ public class TtsService extends Service implements OnCompletionListener { * The filename of the sound resource. This must be a * complete path like: (/sdcard/mysounds/mysoundbite.mp3). */ - public void addEarconFile(String earcon, String filename) { - mSelf.addEarcon(earcon, filename); + public void addEarconFile(String callingApp, String earcon, String filename) { + mSelf.addEarcon(callingApp, earcon, filename); } /** @@ -858,8 +1056,8 @@ public class TtsService extends Service implements OnCompletionListener { * @param speechRate * The speech rate that should be used */ - public int setSpeechRate(int speechRate) { - return mSelf.setSpeechRate(speechRate); + public int setSpeechRate(String callingApp, int speechRate) { + return mSelf.setSpeechRate(callingApp, speechRate); } /** @@ -869,8 +1067,8 @@ public class TtsService extends Service implements OnCompletionListener { * @param pitch * The pitch that should be used for the synthesized voice */ - public int setPitch(int pitch) { - return mSelf.setPitch(pitch); + public int setPitch(String callingApp, int pitch) { + return mSelf.setPitch(callingApp, pitch); } /** @@ -904,8 +1102,8 @@ public class TtsService extends Service implements OnCompletionListener { * @param country the three letter ISO country code. * @param variant the variant code associated with the country and language pair. */ - public int setLanguage(String lang, String country, String variant) { - return mSelf.setLanguage(lang, country, variant); + public int setLanguage(String callingApp, String lang, String country, String variant) { + return mSelf.setLanguage(callingApp, lang, country, variant); } /** @@ -922,13 +1120,13 @@ public class TtsService extends Service implements OnCompletionListener { * be something like "/sdcard/myappsounds/mysound.wav". * @return A boolean that indicates if the synthesis succeeded */ - public boolean synthesizeToFile(String text, String[] params, + public boolean synthesizeToFile(String callingApp, String text, String[] params, String filename) { ArrayList<String> speakingParams = new ArrayList<String>(); if (params != null) { speakingParams = new ArrayList<String>(Arrays.asList(params)); } - return mSelf.synthesizeToFile(text, speakingParams, filename); + return mSelf.synthesizeToFile(callingApp, text, speakingParams, filename); } }; diff --git a/packages/VpnServices/res/values-cs/strings.xml b/packages/VpnServices/res/values-cs/strings.xml new file mode 100644 index 0000000..5f3522d --- /dev/null +++ b/packages/VpnServices/res/values-cs/strings.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Služby VPN"</string> + <!-- no translation found for vpn_notification_title_connected (2567196609405266775) --> + <skip /> + <!-- no translation found for vpn_notification_title_disconnected (3564361788336568244) --> + <skip /> + <!-- no translation found for vpn_notification_hint_disconnected (1952209867082269429) --> + <skip /> +</resources> diff --git a/packages/VpnServices/res/values-de/strings.xml b/packages/VpnServices/res/values-de/strings.xml new file mode 100644 index 0000000..93fa474 --- /dev/null +++ b/packages/VpnServices/res/values-de/strings.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"VPN-Dienste"</string> + <!-- no translation found for vpn_notification_title_connected (2567196609405266775) --> + <skip /> + <!-- no translation found for vpn_notification_title_disconnected (3564361788336568244) --> + <skip /> + <!-- no translation found for vpn_notification_hint_disconnected (1952209867082269429) --> + <skip /> +</resources> diff --git a/packages/VpnServices/res/values-es/strings.xml b/packages/VpnServices/res/values-es/strings.xml new file mode 100644 index 0000000..bb4f348 --- /dev/null +++ b/packages/VpnServices/res/values-es/strings.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Servicios VPN"</string> + <!-- no translation found for vpn_notification_title_connected (2567196609405266775) --> + <skip /> + <!-- no translation found for vpn_notification_title_disconnected (3564361788336568244) --> + <skip /> + <!-- no translation found for vpn_notification_hint_disconnected (1952209867082269429) --> + <skip /> +</resources> diff --git a/packages/VpnServices/res/values-fr/strings.xml b/packages/VpnServices/res/values-fr/strings.xml new file mode 100644 index 0000000..4395d03 --- /dev/null +++ b/packages/VpnServices/res/values-fr/strings.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Services VPN"</string> + <!-- no translation found for vpn_notification_title_connected (2567196609405266775) --> + <skip /> + <!-- no translation found for vpn_notification_title_disconnected (3564361788336568244) --> + <skip /> + <!-- no translation found for vpn_notification_hint_disconnected (1952209867082269429) --> + <skip /> +</resources> diff --git a/packages/VpnServices/res/values-it/strings.xml b/packages/VpnServices/res/values-it/strings.xml new file mode 100644 index 0000000..76e0214 --- /dev/null +++ b/packages/VpnServices/res/values-it/strings.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Servizi VPN"</string> + <!-- no translation found for vpn_notification_title_connected (2567196609405266775) --> + <skip /> + <!-- no translation found for vpn_notification_title_disconnected (3564361788336568244) --> + <skip /> + <!-- no translation found for vpn_notification_hint_disconnected (1952209867082269429) --> + <skip /> +</resources> diff --git a/packages/VpnServices/res/values-nl/strings.xml b/packages/VpnServices/res/values-nl/strings.xml new file mode 100644 index 0000000..9a50f3b --- /dev/null +++ b/packages/VpnServices/res/values-nl/strings.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"VPN-services"</string> + <!-- no translation found for vpn_notification_title_connected (2567196609405266775) --> + <skip /> + <!-- no translation found for vpn_notification_title_disconnected (3564361788336568244) --> + <skip /> + <!-- no translation found for vpn_notification_hint_disconnected (1952209867082269429) --> + <skip /> +</resources> diff --git a/packages/VpnServices/res/values-pl/strings.xml b/packages/VpnServices/res/values-pl/strings.xml new file mode 100644 index 0000000..d2e8c60 --- /dev/null +++ b/packages/VpnServices/res/values-pl/strings.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"Usługi VPN"</string> + <!-- no translation found for vpn_notification_title_connected (2567196609405266775) --> + <skip /> + <!-- no translation found for vpn_notification_title_disconnected (3564361788336568244) --> + <skip /> + <!-- no translation found for vpn_notification_hint_disconnected (1952209867082269429) --> + <skip /> +</resources> diff --git a/packages/VpnServices/res/values-zh-rTW/strings.xml b/packages/VpnServices/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000..021077b --- /dev/null +++ b/packages/VpnServices/res/values-zh-rTW/strings.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2009 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_label">"VPN 服務"</string> + <!-- no translation found for vpn_notification_title_connected (2567196609405266775) --> + <skip /> + <!-- no translation found for vpn_notification_title_disconnected (3564361788336568244) --> + <skip /> + <!-- no translation found for vpn_notification_hint_disconnected (1952209867082269429) --> + <skip /> +</resources> diff --git a/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java b/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java index 7dd9d9e..e4c070f 100644 --- a/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java +++ b/packages/VpnServices/src/com/android/server/vpn/AndroidServiceProxy.java @@ -18,6 +18,7 @@ package com.android.server.vpn; import android.net.LocalSocket; import android.net.LocalSocketAddress; +import android.net.vpn.VpnManager; import android.os.SystemProperties; import android.util.Log; @@ -48,6 +49,9 @@ public class AndroidServiceProxy extends ProcessProxy { private static final int END_OF_ARGUMENTS = 255; + private static final int STOP_SERVICE = -1; + private static final int AUTH_ERROR_CODE = 51; + private String mServiceName; private String mSocketName; private LocalSocket mKeepaliveSocket; @@ -72,14 +76,22 @@ public class AndroidServiceProxy extends ProcessProxy { @Override public synchronized void stop() { - if (isRunning()) setResultAndCloseControlSocket(-1); + if (isRunning()) { + try { + setResultAndCloseControlSocket(STOP_SERVICE); + } catch (IOException e) { + // should not occur + throw new RuntimeException(e); + } + } + Log.d(mTag, "----- Stop: " + mServiceName); SystemProperties.set(SVC_STOP_CMD, mServiceName); } /** * Sends a command with arguments to the service through the control socket. */ - public void sendCommand(String ...args) throws IOException { + public synchronized void sendCommand(String ...args) throws IOException { OutputStream out = getControlSocketOutput(); for (String arg : args) outputString(out, arg); out.write(END_OF_ARGUMENTS); @@ -94,7 +106,14 @@ public class AndroidServiceProxy extends ProcessProxy { @Override protected void performTask() throws IOException { String svc = mServiceName; - Log.d(mTag, "+++++ Execute: " + svc); + Log.d(mTag, "----- Stop the daemon just in case: " + mServiceName); + SystemProperties.set(SVC_STOP_CMD, mServiceName); + if (!blockUntil(SVC_STATE_STOPPED, 5)) { + throw new IOException("cannot start service anew: " + svc + + ", it is still running"); + } + + Log.d(mTag, "+++++ Start: " + svc); SystemProperties.set(SVC_START_CMD, svc); boolean success = blockUntil(SVC_STATE_RUNNING, WAITING_TIME); @@ -114,30 +133,22 @@ public class AndroidServiceProxy extends ProcessProxy { InputStream in = s.getInputStream(); int data = in.read(); if (data >= 0) { - Log.d(mTag, "got data from keepalive socket: " + data); - - if (data == 0) { - // re-establish the connection: - // synchronized here so that checkSocketResult() - // returns when new mKeepaliveSocket is available for - // next cmd - synchronized (this) { - setResultAndCloseControlSocket((byte) data); - s = mKeepaliveSocket = createServiceSocket(); - } - } else { - // keep the socket - setSocketResult(data); - } + Log.d(mTag, "got data from control socket: " + data); + + setSocketResult(data); } else { // service is gone if (mControlSocketInUse) setSocketResult(-1); break; } } - Log.d(mTag, "keepalive connection closed"); + Log.d(mTag, "control connection closed"); } catch (IOException e) { - Log.d(mTag, "keepalive socket broken: " + e.getMessage()); + if (e instanceof VpnConnectingError) { + throw e; + } else { + Log.d(mTag, "control socket broken: " + e.getMessage()); + } } // Wait 5 seconds for the service to exit @@ -179,7 +190,7 @@ public class AndroidServiceProxy extends ProcessProxy { } } - private synchronized void checkSocketResult() throws IOException { + private void checkSocketResult() throws IOException { try { // will be notified when the result comes back from service if (mSocketResult == null) wait(); @@ -194,14 +205,21 @@ public class AndroidServiceProxy extends ProcessProxy { } } - private synchronized void setSocketResult(int result) { + private synchronized void setSocketResult(int result) + throws VpnConnectingError { if (mControlSocketInUse) { mSocketResult = result; notifyAll(); + } else if (result > 0) { + // error from daemon + throw new VpnConnectingError((result == AUTH_ERROR_CODE) + ? VpnManager.VPN_ERROR_AUTH + : VpnManager.VPN_ERROR_CONNECTION_FAILED); } } - private void setResultAndCloseControlSocket(int result) { + private void setResultAndCloseControlSocket(int result) + throws VpnConnectingError { setSocketResult(result); try { mKeepaliveSocket.shutdownInput(); diff --git a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java index 6abf81c..7b3ddf8 100644 --- a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java +++ b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecPskService.java @@ -30,12 +30,11 @@ class L2tpIpsecPskService extends VpnService<L2tpIpsecPskProfile> { @Override protected void connect(String serverIp, String username, String password) throws IOException { - String hostIp = getHostIp(); L2tpIpsecPskProfile p = getProfile(); // IPSEC AndroidServiceProxy ipsecService = startService(IPSEC_DAEMON); - ipsecService.sendCommand(hostIp, serverIp, L2tpService.L2TP_PORT, + ipsecService.sendCommand(serverIp, L2tpService.L2TP_PORT, p.getPresharedKey()); sleep(2000); // 2 seconds diff --git a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java index 825953c..e2d4ff4 100644 --- a/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java +++ b/packages/VpnServices/src/com/android/server/vpn/L2tpIpsecService.java @@ -30,11 +30,9 @@ class L2tpIpsecService extends VpnService<L2tpIpsecProfile> { @Override protected void connect(String serverIp, String username, String password) throws IOException { - String hostIp = getHostIp(); - // IPSEC AndroidServiceProxy ipsecService = startService(IPSEC_DAEMON); - ipsecService.sendCommand(hostIp, serverIp, L2tpService.L2TP_PORT, + ipsecService.sendCommand(serverIp, L2tpService.L2TP_PORT, getUserkeyPath(), getUserCertPath(), getCaCertPath()); sleep(2000); // 2 seconds diff --git a/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java b/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java index 16d253a..5fac799 100644 --- a/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java +++ b/packages/VpnServices/src/com/android/server/vpn/MtpdHelper.java @@ -48,7 +48,6 @@ class MtpdHelper { "linkname", VPN_LINKNAME, "name", username, "password", password, - "ipparam", serverIp + "@" + vpnService.getGatewayIp(), "refuse-eap", "nodefaultroute", "usepeerdns", "idle", "1800", "mtu", "1400", diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnConnectingError.java b/packages/VpnServices/src/com/android/server/vpn/VpnConnectingError.java new file mode 100644 index 0000000..3c4ec7d --- /dev/null +++ b/packages/VpnServices/src/com/android/server/vpn/VpnConnectingError.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2009, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vpn; + +import java.io.IOException; + +/** + * Exception thrown when a connecting attempt fails. + */ +class VpnConnectingError extends IOException { + private int mErrorCode; + + VpnConnectingError(int errorCode) { + super("Connecting error: " + errorCode); + mErrorCode = errorCode; + } + + int getErrorCode() { + return mErrorCode; + } +} diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnService.java b/packages/VpnServices/src/com/android/server/vpn/VpnService.java index a60788a..87bd780 100644 --- a/packages/VpnServices/src/com/android/server/vpn/VpnService.java +++ b/packages/VpnServices/src/com/android/server/vpn/VpnService.java @@ -20,7 +20,6 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; -import android.net.NetworkUtils; import android.net.vpn.VpnManager; import android.net.vpn.VpnProfile; import android.net.vpn.VpnState; @@ -30,10 +29,8 @@ import android.util.Log; import java.io.IOException; import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.Socket; +import java.net.UnknownHostException; import java.util.ArrayList; -import java.util.Enumeration; import java.util.List; /** @@ -46,9 +43,9 @@ abstract class VpnService<E extends VpnProfile> { private static final String DNS2 = "net.dns2"; private static final String VPN_DNS1 = "vpn.dns1"; private static final String VPN_DNS2 = "vpn.dns2"; - private static final String VPN_UP = "vpn.up"; - private static final String VPN_IS_UP = "1"; - private static final String VPN_IS_DOWN = "0"; + private static final String VPN_STATUS = "vpn.status"; + private static final String VPN_IS_UP = "ok"; + private static final String VPN_IS_DOWN = "down"; private static final String REMOTE_IP = "net.ipremote"; private static final String DNS_DOMAIN_SUFFICES = "net.dns.search"; @@ -60,6 +57,7 @@ abstract class VpnService<E extends VpnProfile> { private VpnState mState = VpnState.IDLE; private boolean mInError; + private VpnConnectingError mError; // connection settings private String mOriginalDns1; @@ -67,7 +65,6 @@ abstract class VpnService<E extends VpnProfile> { private String mVpnDns1 = ""; private String mVpnDns2 = ""; private String mOriginalDomainSuffices; - private String mHostIp; private long mStartTime; // VPN connection start time @@ -106,14 +103,6 @@ abstract class VpnService<E extends VpnProfile> { } /** - * Returns the host IP for establishing the VPN connection. - */ - protected String getHostIp() throws IOException { - if (mHostIp == null) mHostIp = reallyGetHostIp(); - return mHostIp; - } - - /** * Returns the IP address of the specified host name. */ protected String getIp(String hostName) throws IOException { @@ -121,21 +110,6 @@ abstract class VpnService<E extends VpnProfile> { } /** - * Returns the IP address of the default gateway. - */ - protected String getGatewayIp() throws IOException { - Enumeration<NetworkInterface> ifces = - NetworkInterface.getNetworkInterfaces(); - for (; ifces.hasMoreElements(); ) { - NetworkInterface ni = ifces.nextElement(); - int gateway = NetworkUtils.getDefaultRoute(ni.getName()); - if (gateway == 0) continue; - return toInetAddress(gateway).getHostAddress(); - } - throw new IOException("Default gateway is not available"); - } - - /** * Sets the system property. The method is blocked until the value is * settled in. * @param name the name of the property @@ -166,20 +140,28 @@ abstract class VpnService<E extends VpnProfile> { return mState; } - synchronized void onConnect(String username, String password) - throws IOException { - mState = VpnState.CONNECTING; - broadcastConnectivity(VpnState.CONNECTING); + synchronized boolean onConnect(String username, String password) { + try { + mState = VpnState.CONNECTING; + broadcastConnectivity(VpnState.CONNECTING); - String serverIp = getIp(getProfile().getServerName()); + String serverIp = getIp(getProfile().getServerName()); - onBeforeConnect(); - connect(serverIp, username, password); - waitUntilConnectedOrTimedout(); + onBeforeConnect(); + connect(serverIp, username, password); + waitUntilConnectedOrTimedout(); + return true; + } catch (Throwable e) { + Log.e(TAG, "onConnect()", e); + mError = newConnectingError(e); + onError(); + return false; + } } synchronized void onDisconnect(boolean cleanUpServices) { try { + Log.d(TAG, " disconnecting VPN..."); mState = VpnState.DISCONNECTING; broadcastConnectivity(VpnState.DISCONNECTING); mNotification.showDisconnect(); @@ -189,7 +171,7 @@ abstract class VpnService<E extends VpnProfile> { mServiceHelper.stop(); } catch (Throwable e) { - Log.e(TAG, "onError()", e); + Log.e(TAG, "onDisconnect()", e); onFinalCleanUp(); } } @@ -214,26 +196,35 @@ abstract class VpnService<E extends VpnProfile> { SystemProperties.set(VPN_DNS1, "-"); SystemProperties.set(VPN_DNS2, "-"); - SystemProperties.set(VPN_UP, VPN_IS_DOWN); - Log.d(TAG, " VPN UP: " + SystemProperties.get(VPN_UP)); + SystemProperties.set(VPN_STATUS, VPN_IS_DOWN); + Log.d(TAG, " VPN UP: " + SystemProperties.get(VPN_STATUS)); } private void waitUntilConnectedOrTimedout() { - sleep(2000); // 2 seconds - for (int i = 0; i < 60; i++) { - if (VPN_IS_UP.equals(SystemProperties.get(VPN_UP))) { - onConnected(); - return; - } - sleep(500); // 0.5 second - } + // Run this in the background thread to not block UI + new Thread(new Runnable() { + public void run() { + sleep(2000); // 2 seconds + for (int i = 0; i < 60; i++) { + if (VPN_IS_UP.equals(SystemProperties.get(VPN_STATUS))) { + onConnected(); + return; + } else if (mState != VpnState.CONNECTING) { + break; + } + sleep(500); // 0.5 second + } - synchronized (this) { - if (mState == VpnState.CONNECTING) { - Log.d(TAG, " connecting timed out !!"); - onError(); + synchronized (VpnService.this) { + if (mState == VpnState.CONNECTING) { + Log.d(TAG, " connecting timed out !!"); + mError = newConnectingError( + new IOException("Connecting timed out")); + onError(); + } + } } - } + }).start(); } private synchronized void onConnected() { @@ -264,6 +255,13 @@ abstract class VpnService<E extends VpnProfile> { mContext.stopSelf(); } + private VpnConnectingError newConnectingError(Throwable e) { + return new VpnConnectingError( + (e instanceof UnknownHostException) + ? VpnManager.VPN_ERROR_UNKNOWN_SERVER + : VpnManager.VPN_ERROR_CONNECTION_FAILED); + } + private synchronized void onOneServiceGone() { switch (mState) { case IDLE: @@ -304,12 +302,12 @@ abstract class VpnService<E extends VpnProfile> { private void saveVpnDnsProperties() { mOriginalDns1 = mOriginalDns2 = ""; - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 5; i++) { mVpnDns1 = SystemProperties.get(VPN_DNS1); mVpnDns2 = SystemProperties.get(VPN_DNS2); if (mOriginalDns1.equals(mVpnDns1)) { Log.d(TAG, "wait for vpn dns to settle in..." + i); - sleep(500); + sleep(200); } else { mOriginalDns1 = SystemProperties.get(DNS1); mOriginalDns2 = SystemProperties.get(DNS2); @@ -322,7 +320,9 @@ abstract class VpnService<E extends VpnProfile> { return; } } - Log.e(TAG, "saveVpnDnsProperties(): DNS not updated??"); + Log.d(TAG, "saveVpnDnsProperties(): DNS not updated??"); + mOriginalDns1 = mVpnDns1 = SystemProperties.get(DNS1); + mOriginalDns2 = mVpnDns2 = SystemProperties.get(DNS2); } private void saveAndSetDomainSuffices() { @@ -340,7 +340,13 @@ abstract class VpnService<E extends VpnProfile> { } private void broadcastConnectivity(VpnState s) { - new VpnManager(mContext).broadcastConnectivity(mProfile.getName(), s); + VpnManager m = new VpnManager(mContext); + if ((s == VpnState.IDLE) && (mError != null)) { + m.broadcastConnectivity(mProfile.getName(), s, + mError.getErrorCode()); + } else { + m.broadcastConnectivity(mProfile.getName(), s); + } } private void startConnectivityMonitor() { @@ -373,26 +379,11 @@ abstract class VpnService<E extends VpnProfile> { private void checkDnsProperties() { String dns1 = SystemProperties.get(DNS1); if (!mVpnDns1.equals(dns1)) { - Log.w(TAG, " @@ !!! dns being overridden"); + Log.w(TAG, " dns being overridden by: " + dns1); onError(); } } - private String reallyGetHostIp() throws IOException { - Enumeration<NetworkInterface> ifces = - NetworkInterface.getNetworkInterfaces(); - for (; ifces.hasMoreElements(); ) { - NetworkInterface ni = ifces.nextElement(); - int gateway = NetworkUtils.getDefaultRoute(ni.getName()); - if (gateway == 0) continue; - Enumeration<InetAddress> addrs = ni.getInetAddresses(); - for (; addrs.hasMoreElements(); ) { - return addrs.nextElement().getHostAddress(); - } - } - throw new IOException("Host IP is not available"); - } - protected void sleep(int ms) { try { Thread.currentThread().sleep(ms); @@ -440,6 +431,9 @@ abstract class VpnService<E extends VpnProfile> { //@Override public void error(ProcessProxy p, Throwable e) { Log.e(TAG, "service error: " + p.getName(), e); + if (e instanceof VpnConnectingError) { + mError = (VpnConnectingError) e; + } commonCallback((AndroidServiceProxy) p); } diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java b/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java index 617875e..32b8e51 100644 --- a/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java +++ b/packages/VpnServices/src/com/android/server/vpn/VpnServiceBinder.java @@ -27,7 +27,6 @@ import android.net.vpn.VpnManager; import android.net.vpn.VpnProfile; import android.net.vpn.VpnState; import android.os.IBinder; -import android.util.Log; import java.io.IOException; @@ -55,6 +54,12 @@ public class VpnServiceBinder extends Service { } }; + public void onStart (Intent intent, int startId) { + super.onStart(intent, startId); + setForeground(true); + android.util.Log.d("VpnServiceBinder", "becomes a foreground service"); + } + public IBinder onBind(Intent intent) { return mBinder; } @@ -62,21 +67,13 @@ public class VpnServiceBinder extends Service { private synchronized boolean connect( VpnProfile p, String username, String password) { if (mService != null) return false; - try { - mService = createService(p); - mService.onConnect(username, password); - return true; - } catch (Throwable e) { - Log.e(TAG, "connect()", e); - if (mService != null) mService.onError(); - return false; - } + mService = createService(p); + return mService.onConnect(username, password); } private synchronized void checkStatus(VpnProfile p) { - if (mService == null) broadcastConnectivity(p.getName(), VpnState.IDLE); - - if (!p.getName().equals(mService.mProfile.getName())) { + if ((mService == null) + || (!p.getName().equals(mService.mProfile.getName()))) { broadcastConnectivity(p.getName(), VpnState.IDLE); } else { broadcastConnectivity(p.getName(), mService.getState()); |