Merge "Add support for playing a sound before/after dumpstate."
diff --git a/Android.mk b/Android.mk
index 22216b0..7c29c73 100644
--- a/Android.mk
+++ b/Android.mk
@@ -186,6 +186,10 @@
wifi/java/android/net/wifi/IWifiManager.aidl \
telephony/java/com/android/internal/telephony/IExtendedNetworkService.aidl \
vpn/java/android/net/vpn/IVpnService.aidl \
+ voip/java/android/net/sip/ISipSession.aidl \
+ voip/java/android/net/sip/ISipSessionListener.aidl \
+ voip/java/android/net/sip/ISipService.aidl
+#
# FRAMEWORKS_BASE_JAVA_SRC_DIRS comes from build/core/pathmap.mk
@@ -575,6 +579,7 @@
# ============================================================
ext_dirs := \
+ ../../external/nist-sip/java \
../../external/apache-http/src \
../../external/tagsoup/src \
../../external/libphonenumber/java/src
diff --git a/api/current.xml b/api/current.xml
index 7a9d2518..cc2bb3c 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -180176,6 +180176,468 @@
</parameter>
</method>
</class>
+<class name="JsonReader"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="java.io.Closeable">
+</implements>
+<constructor name="JsonReader"
+ type="android.util.JsonReader"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="in" type="java.io.Reader">
+</parameter>
+</constructor>
+<method name="beginArray"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="beginObject"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="close"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="endArray"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="endObject"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="hasNext"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextBoolean"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextDouble"
+ return="double"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextInt"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextLong"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextName"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextNull"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nextString"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="peek"
+ return="android.util.JsonToken"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="skipValue"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="syntaxError"
+ return="java.io.IOException"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="message" type="java.lang.String">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+</class>
+<class name="JsonToken"
+ extends="java.lang.Enum"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="valueOf"
+ return="android.util.JsonToken"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+</method>
+<method name="values"
+ return="android.util.JsonToken[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</class>
+<class name="JsonWriter"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="JsonWriter"
+ type="android.util.JsonWriter"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="out" type="java.io.Writer">
+</parameter>
+</constructor>
+<method name="beginArray"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="beginObject"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="close"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="endArray"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="endObject"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="flush"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="name"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="name" type="java.lang.String">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="nullValue"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="setIndentSpaces"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="indent" type="int">
+</parameter>
+</method>
+<method name="value"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="java.lang.String">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="value"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="boolean">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="value"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="double">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+<method name="value"
+ return="android.util.JsonWriter"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="value" type="long">
+</parameter>
+<exception name="IOException" type="java.io.IOException">
+</exception>
+</method>
+</class>
<class name="Log"
extends="java.lang.Object"
abstract="false"
@@ -212151,6 +212613,22 @@
<parameter name="defStyle" type="int">
</parameter>
</constructor>
+<constructor name="WebView"
+ type="android.webkit.WebView"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="attrs" type="android.util.AttributeSet">
+</parameter>
+<parameter name="defStyle" type="int">
+</parameter>
+<parameter name="privateBrowsing" type="boolean">
+</parameter>
+</constructor>
<method name="addJavascriptInterface"
return="void"
abstract="false"
@@ -212639,6 +213117,17 @@
visibility="public"
>
</method>
+<method name="isPrivateBrowsingEnabled"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
<method name="loadData"
return="void"
abstract="false"
@@ -272163,7 +272652,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="disable" type="boolean">
+<parameter name="loop" type="boolean">
</parameter>
<exception name="SocketException" type="java.net.SocketException">
</exception>
@@ -273722,7 +274211,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="keepAlive" type="boolean">
+<parameter name="value" type="boolean">
</parameter>
<exception name="SocketException" type="java.net.SocketException">
</exception>
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ee91318..d49adc2 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -635,6 +635,7 @@
/*package*/ ActivityThread mMainThread;
Activity mParent;
boolean mCalled;
+ boolean mCheckedForLoaderManager;
boolean mStarted;
private boolean mResumed;
private boolean mStopped;
@@ -774,16 +775,17 @@
if (mLoaderManager != null) {
return mLoaderManager;
}
- mLoaderManager = getLoaderManager(-1, mStarted);
+ mCheckedForLoaderManager = true;
+ mLoaderManager = getLoaderManager(-1, mStarted, true);
return mLoaderManager;
}
- LoaderManagerImpl getLoaderManager(int index, boolean started) {
+ LoaderManagerImpl getLoaderManager(int index, boolean started, boolean create) {
if (mAllLoaderManagers == null) {
mAllLoaderManagers = new SparseArray<LoaderManagerImpl>();
}
LoaderManagerImpl lm = mAllLoaderManagers.get(index);
- if (lm == null) {
+ if (lm == null && create) {
lm = new LoaderManagerImpl(started);
mAllLoaderManagers.put(index, lm);
}
@@ -967,10 +969,6 @@
mTitleReady = true;
onTitleChanged(getTitle(), getTitleColor());
}
- if (mWindow != null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
- // Invalidate the action bar menu so that it can initialize properly.
- mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
- }
mCalled = true;
}
@@ -992,7 +990,10 @@
mStarted = true;
if (mLoaderManager != null) {
mLoaderManager.doStart();
+ } else if (!mCheckedForLoaderManager) {
+ mLoaderManager = getLoaderManager(-1, mStarted, false);
}
+ mCheckedForLoaderManager = true;
}
/**
@@ -1550,13 +1551,14 @@
ArrayList<Fragment> fragments = mFragments.retainNonConfig();
boolean retainLoaders = false;
if (mAllLoaderManagers != null) {
- // prune out any loader managers that were already stopped, so
+ // prune out any loader managers that were already stopped and so
// have nothing useful to retain.
for (int i=mAllLoaderManagers.size()-1; i>=0; i--) {
LoaderManagerImpl lm = mAllLoaderManagers.valueAt(i);
if (lm.mRetaining) {
retainLoaders = true;
} else {
+ lm.doDestroy();
mAllLoaderManagers.removeAt(i);
}
}
@@ -1586,7 +1588,12 @@
}
void invalidateFragmentIndex(int index) {
+ //Log.v(TAG, "invalidateFragmentIndex: index=" + index);
if (mAllLoaderManagers != null) {
+ LoaderManagerImpl lm = mAllLoaderManagers.get(index);
+ if (lm != null) {
+ lm.doDestroy();
+ }
mAllLoaderManagers.remove(index);
}
}
@@ -4289,6 +4296,9 @@
final void performDestroy() {
mFragments.dispatchDestroy();
onDestroy();
+ if (mLoaderManager != null) {
+ mLoaderManager.doDestroy();
+ }
}
final boolean isResumed() {
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 26cd9de..0bb200c 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -24,6 +24,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.SparseArray;
import android.view.ContextMenu;
import android.view.LayoutInflater;
@@ -216,6 +217,7 @@
LoaderManagerImpl mLoaderManager;
boolean mStarted;
+ boolean mCheckedForLoaderManager;
/**
* Default constructor. <strong>Every</string> fragment must have an
@@ -426,7 +428,8 @@
if (mLoaderManager != null) {
return mLoaderManager;
}
- mLoaderManager = mActivity.getLoaderManager(mIndex, mStarted);
+ mCheckedForLoaderManager = true;
+ mLoaderManager = mActivity.getLoaderManager(mIndex, mStarted, true);
return mLoaderManager;
}
@@ -567,6 +570,10 @@
public void onStart() {
mCalled = true;
mStarted = true;
+ if (!mCheckedForLoaderManager) {
+ mCheckedForLoaderManager = true;
+ mLoaderManager = mActivity.getLoaderManager(mIndex, mStarted, false);
+ }
if (mLoaderManager != null) {
mLoaderManager.doStart();
}
@@ -628,6 +635,12 @@
*/
public void onDestroy() {
mCalled = true;
+ //Log.v("foo", "onDestroy: mCheckedForLoaderManager=" + mCheckedForLoaderManager
+ // + " mLoaderManager=" + mLoaderManager);
+ if (!mCheckedForLoaderManager) {
+ mCheckedForLoaderManager = true;
+ mLoaderManager = mActivity.getLoaderManager(mIndex, mStarted, false);
+ }
if (mLoaderManager != null) {
mLoaderManager.doDestroy();
}
@@ -777,6 +790,10 @@
onStop();
if (mStarted) {
mStarted = false;
+ if (!mCheckedForLoaderManager) {
+ mCheckedForLoaderManager = true;
+ mLoaderManager = mActivity.getLoaderManager(mIndex, mStarted, false);
+ }
if (mLoaderManager != null) {
if (mActivity == null || !mActivity.mChangingConfigurations) {
mLoaderManager.doStop();
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 2054e2a..35b4610 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -385,12 +385,12 @@
}
public void addFragment(Fragment fragment, boolean moveToStateNow) {
- if (DEBUG) Log.v(TAG, "add: " + fragment);
if (mAdded == null) {
mAdded = new ArrayList<Fragment>();
}
mAdded.add(fragment);
makeActive(fragment);
+ if (DEBUG) Log.v(TAG, "add: " + fragment);
fragment.mAdded = true;
if (fragment.mHasMenu) {
mNeedMenuInvalidate = true;
@@ -401,18 +401,18 @@
}
public void removeFragment(Fragment fragment, int transition, int transitionStyle) {
- if (DEBUG) Log.v(TAG, "remove: " + fragment);
+ if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);
mAdded.remove(fragment);
final boolean inactive = fragment.mBackStackNesting <= 0;
- if (inactive) {
- makeInactive(fragment);
- }
if (fragment.mHasMenu) {
mNeedMenuInvalidate = true;
}
fragment.mAdded = false;
moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED,
transition, transitionStyle);
+ if (inactive) {
+ makeInactive(fragment);
+ }
}
public void hideFragment(Fragment fragment, int transition, int transitionStyle) {
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 7fd5dde..e7bdd8b 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -18,6 +18,7 @@
import android.content.Loader;
import android.os.Bundle;
+import android.util.Log;
import android.util.SparseArray;
/**
@@ -87,8 +88,20 @@
}
class LoaderManagerImpl implements LoaderManager {
+ static final String TAG = "LoaderManagerImpl";
+ static final boolean DEBUG = true;
+
+ // These are the currently active loaders. A loader is here
+ // from the time its load is started until it has been explicitly
+ // stopped or restarted by the application.
final SparseArray<LoaderInfo> mLoaders = new SparseArray<LoaderInfo>();
+
+ // These are previously run loaders. This list is maintained internally
+ // to avoid destroying a loader while an application is still using it.
+ // It allows an application to restart a loader, but continue using its
+ // previously run loader until the new loader's data is available.
final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>();
+
boolean mStarted;
boolean mRetaining;
boolean mRetainingStarted;
@@ -125,6 +138,7 @@
return;
}
+ if (DEBUG) Log.v(TAG, " Starting: " + this);
if (mLoader == null && mCallbacks != null) {
mLoader = mCallbacks.onCreateLoader(mId, mArgs);
}
@@ -139,6 +153,7 @@
}
void retain() {
+ if (DEBUG) Log.v(TAG, " Retaining: " + this);
mRetaining = true;
mRetainingStarted = mStarted;
mStarted = false;
@@ -147,6 +162,7 @@
void finishRetain() {
if (mRetaining) {
+ if (DEBUG) Log.v(TAG, " Finished Retaining: " + this);
mRetaining = false;
if (mStarted != mRetainingStarted) {
if (!mStarted) {
@@ -167,6 +183,7 @@
}
void stop() {
+ if (DEBUG) Log.v(TAG, " Stopping: " + this);
mStarted = false;
if (mLoader != null && mListenerRegistered) {
// Let the loader know we're done with it
@@ -177,6 +194,7 @@
}
void destroy() {
+ if (DEBUG) Log.v(TAG, " Destroying: " + this);
mDestroyed = true;
mCallbacks = null;
if (mLoader != null) {
@@ -189,6 +207,8 @@
}
@Override public void onLoadComplete(Loader<Object> loader, Object data) {
+ if (DEBUG) Log.v(TAG, "onLoadComplete: " + this + " mDestroyed=" + mDestroyed);
+
if (mDestroyed) {
return;
}
@@ -200,19 +220,33 @@
mCallbacks.onLoadFinished(loader, data);
}
- // Look for an inactive loader and destroy it if found
+ if (DEBUG) Log.v(TAG, "onLoadFinished returned: " + this);
+
+ // We have now given the application the new loader with its
+ // loaded data, so it should have stopped using the previous
+ // loader. If there is a previous loader on the inactive list,
+ // clean it up.
LoaderInfo info = mInactiveLoaders.get(mId);
- if (info != null) {
- Loader<Object> oldLoader = info.mLoader;
- if (oldLoader != null) {
- if (info.mListenerRegistered) {
- oldLoader.unregisterListener(info);
- }
- oldLoader.destroy();
- }
+ if (info != null && info != this) {
+ info.destroy();
mInactiveLoaders.remove(mId);
}
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(64);
+ sb.append("LoaderInfo{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" #");
+ sb.append(mId);
+ if (mArgs != null) {
+ sb.append(" ");
+ sb.append(mArgs.toString());
+ }
+ sb.append("}");
+ return sb.toString();
+ }
}
LoaderManagerImpl(boolean started) {
@@ -238,6 +272,8 @@
public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
LoaderInfo info = mLoaders.get(id);
+ if (DEBUG) Log.v(TAG, "initLoader in " + this + ": cur=" + info);
+
if (info == null) {
// Loader doesn't already exist; create.
info = createLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
@@ -256,16 +292,30 @@
@SuppressWarnings("unchecked")
public <D> Loader<D> restartLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
LoaderInfo info = mLoaders.get(id);
+ if (DEBUG) Log.v(TAG, "restartLoader in " + this + ": cur=" + info);
if (info != null) {
- if (mInactiveLoaders.get(id) != null) {
- // We already have an inactive loader for this ID that we are
- // waiting for! Now we have three active loaders... let's just
- // drop the one in the middle, since we are still waiting for
- // its result but that result is already out of date.
- info.destroy();
+ LoaderInfo inactive = mInactiveLoaders.get(id);
+ if (inactive != null) {
+ if (info.mData != null) {
+ // This loader now has data... we are probably being
+ // called from within onLoadComplete, where we haven't
+ // yet destroyed the last inactive loader. So just do
+ // that now.
+ if (DEBUG) Log.v(TAG, " Removing last inactive loader in " + this);
+ inactive.destroy();
+ mInactiveLoaders.put(id, info);
+ } else {
+ // We already have an inactive loader for this ID that we are
+ // waiting for! Now we have three active loaders... let's just
+ // drop the one in the middle, since we are still waiting for
+ // its result but that result is already out of date.
+ if (DEBUG) Log.v(TAG, " Removing intermediate loader in " + this);
+ info.destroy();
+ }
} else {
// Keep track of the previous instance of this loader so we can destroy
// it when the new one completes.
+ if (DEBUG) Log.v(TAG, " Making inactive: " + info);
mInactiveLoaders.put(id, info);
}
}
@@ -275,6 +325,7 @@
}
public void stopLoader(int id) {
+ if (DEBUG) Log.v(TAG, "stopLoader in " + this + " of " + id);
int idx = mLoaders.indexOfKey(id);
if (idx >= 0) {
LoaderInfo info = mLoaders.valueAt(idx);
@@ -293,6 +344,8 @@
}
void doStart() {
+ if (DEBUG) Log.v(TAG, "Starting: " + this);
+
// Call out to sub classes so they can start their loaders
// Let the existing loaders know that we want to be notified when a load is complete
for (int i = mLoaders.size()-1; i >= 0; i--) {
@@ -302,6 +355,8 @@
}
void doStop() {
+ if (DEBUG) Log.v(TAG, "Stopping: " + this);
+
for (int i = mLoaders.size()-1; i >= 0; i--) {
mLoaders.valueAt(i).stop();
}
@@ -309,6 +364,8 @@
}
void doRetain() {
+ if (DEBUG) Log.v(TAG, "Retaining: " + this);
+
mRetaining = true;
mStarted = false;
for (int i = mLoaders.size()-1; i >= 0; i--) {
@@ -317,6 +374,8 @@
}
void finishRetain() {
+ if (DEBUG) Log.v(TAG, "Finished Retaining: " + this);
+
mRetaining = false;
for (int i = mLoaders.size()-1; i >= 0; i--) {
mLoaders.valueAt(i).finishRetain();
@@ -325,11 +384,13 @@
void doDestroy() {
if (!mRetaining) {
+ if (DEBUG) Log.v(TAG, "Destroying Active: " + this);
for (int i = mLoaders.size()-1; i >= 0; i--) {
mLoaders.valueAt(i).destroy();
}
}
+ if (DEBUG) Log.v(TAG, "Destroying Inactive: " + this);
for (int i = mInactiveLoaders.size()-1; i >= 0; i--) {
mInactiveLoaders.valueAt(i).destroy();
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 24c0305..3d29379 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1583,6 +1583,15 @@
public static final String DOWNLOAD_SERVICE = "download";
/**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.net.sip.SipManager} for accessing the SIP related service.
+ *
+ * @see #getSystemService
+ */
+ /** @hide */
+ public static final String SIP_SERVICE = "sip";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java
index a7d036f..b0160885 100644
--- a/core/java/android/content/SyncOperation.java
+++ b/core/java/android/content/SyncOperation.java
@@ -90,7 +90,7 @@
private String toKey() {
StringBuilder sb = new StringBuilder();
sb.append("authority: ").append(authority);
- sb.append(" account: ").append(account);
+ sb.append(" account {name=" + account.name + ", type=" + account.type + "}");
sb.append(" extras: ");
extrasToStringBuilder(extras, sb, true /* asKey */);
return sb.toString();
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index a17b7fe..72e21de 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -85,6 +85,8 @@
public static native int getPermissions(String file, int[] outPermissions);
+ public static native int setUMask(int mask);
+
/** returns the FAT file system volume ID for the volume mounted
* at the given mount point, or -1 for failure
* @param mount point for FAT volume
diff --git a/core/java/android/util/JsonReader.java b/core/java/android/util/JsonReader.java
new file mode 100644
index 0000000..e47be0d
--- /dev/null
+++ b/core/java/android/util/JsonReader.java
@@ -0,0 +1,1006 @@
+/*
+ * Copyright (C) 2010 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 android.util;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Reads a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
+ * encoded value as a stream of tokens. This stream includes both literal
+ * values (strings, numbers, booleans, and nulls) as well as the begin and
+ * end delimiters of objects and arrays. The tokens are traversed in
+ * depth-first order, the same order that they appear in the JSON document.
+ * Within JSON objects, name/value pairs are represented by a single token.
+ *
+ * <h3>Parsing JSON</h3>
+ * To create a recursive descent parser your own JSON streams, first create an
+ * entry point method that creates a {@code JsonReader}.
+ *
+ * <p>Next, create handler methods for each structure in your JSON text. You'll
+ * need a method for each object type and for each array type.
+ * <ul>
+ * <li>Within <strong>array handling</strong> methods, first call {@link
+ * #beginArray} to consume the array's opening bracket. Then create a
+ * while loop that accumulates values, terminating when {@link #hasNext}
+ * is false. Finally, read the array's closing bracket by calling {@link
+ * #endArray}.
+ * <li>Within <strong>object handling</strong> methods, first call {@link
+ * #beginObject} to consume the object's opening brace. Then create a
+ * while loop that assigns values to local variables based on their name.
+ * This loop should terminate when {@link #hasNext} is false. Finally,
+ * read the object's closing brace by calling {@link #endObject}.
+ * </ul>
+ * <p>When a nested object or array is encountered, delegate to the
+ * corresponding handler method.
+ *
+ * <p>When an unknown name is encountered, strict parsers should fail with an
+ * exception. Lenient parsers should call {@link #skipValue()} to recursively
+ * skip the value's nested tokens, which may otherwise conflict.
+ *
+ * <p>If a value may be null, you should first check using {@link #peek()}.
+ * Null literals can be consumed using either {@link #nextNull()} or {@link
+ * #skipValue()}.
+ *
+ * <h3>Example</h3>
+ * Suppose we'd like to parse a stream of messages such as the following: <pre> {@code
+ * [
+ * {
+ * "id": 912345678901,
+ * "text": "How do I read JSON on Android?",
+ * "geo": null,
+ * "user": {
+ * "name": "android_newb",
+ * "followers_count": 41
+ * }
+ * },
+ * {
+ * "id": 912345678902,
+ * "text": "@android_newb just use android.util.JsonReader!",
+ * "geo": [50.454722, -104.606667],
+ * "user": {
+ * "name": "jesse",
+ * "followers_count": 2
+ * }
+ * }
+ * ]}</pre>
+ * This code implements the parser for the above structure: <pre> {@code
+ *
+ * public List<Message> readJsonStream(InputStream in) throws IOException {
+ * JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
+ * return readMessagesArray(reader);
+ * }
+ *
+ * public List<Message> readMessagesArray(JsonReader reader) throws IOException {
+ * List<Message> messages = new ArrayList<Message>();
+ *
+ * reader.beginArray();
+ * while (reader.hasNext()) {
+ * messages.add(readMessage(reader));
+ * }
+ * reader.endArray();
+ * return messages;
+ * }
+ *
+ * public Message readMessage(JsonReader reader) throws IOException {
+ * long id = -1;
+ * String text = null;
+ * User user = null;
+ * List<Double> geo = null;
+ *
+ * reader.beginObject();
+ * while (reader.hasNext()) {
+ * String name = reader.nextName();
+ * if (name.equals("id")) {
+ * id = reader.nextLong();
+ * } else if (name.equals("text")) {
+ * text = reader.nextString();
+ * } else if (name.equals("geo") && reader.peek() != JsonToken.NULL) {
+ * geo = readDoublesArray(reader);
+ * } else if (name.equals("user")) {
+ * user = readUser(reader);
+ * } else {
+ * reader.skipValue();
+ * }
+ * }
+ * reader.endObject();
+ * return new Message(id, text, user, geo);
+ * }
+ *
+ * public List<Double> readDoublesArray(JsonReader reader) throws IOException {
+ * List<Double> doubles = new ArrayList<Double>();
+ *
+ * reader.beginArray();
+ * while (reader.hasNext()) {
+ * doubles.add(reader.nextDouble());
+ * }
+ * reader.endArray();
+ * return doubles;
+ * }
+ *
+ * public User readUser(JsonReader reader) throws IOException {
+ * String username = null;
+ * int followersCount = -1;
+ *
+ * reader.beginObject();
+ * while (reader.hasNext()) {
+ * String name = reader.nextName();
+ * if (name.equals("name")) {
+ * username = reader.nextString();
+ * } else if (name.equals("followers_count")) {
+ * followersCount = reader.nextInt();
+ * } else {
+ * reader.skipValue();
+ * }
+ * }
+ * reader.endObject();
+ * return new User(username, followersCount);
+ * }}</pre>
+ *
+ * <h3>Number Handling</h3>
+ * This reader permits numeric values to be read as strings and string values to
+ * be read as numbers. For example, both elements of the JSON array {@code
+ * [1, "1"]} may be read using either {@link #nextInt} or {@link #nextString}.
+ * This behavior is intended to prevent lossy numeric conversions: double is
+ * JavaScript's only numeric type and very large values like {@code
+ * 9007199254740993} cannot be represented exactly on that platform. To minimize
+ * precision loss, extremely large values should be written and read as strings
+ * in JSON.
+ *
+ * <p>Each {@code JsonReader} may be used to read a single JSON stream. Instances
+ * of this class are not thread safe.
+ */
+public final class JsonReader implements Closeable {
+
+ /** The input JSON. */
+ private final Reader in;
+
+ /**
+ * Use a manual buffer to easily read and unread upcoming characters, and
+ * also so we can create strings without an intermediate StringBuilder.
+ */
+ private final char[] buffer = new char[1024];
+ private int pos = 0;
+ private int limit = 0;
+
+ private final List<JsonScope> stack = new ArrayList<JsonScope>();
+ {
+ push(JsonScope.EMPTY_DOCUMENT);
+ }
+
+ /**
+ * True if we've already read the next token. If we have, the string value
+ * for that token will be assigned to {@code value} if such a string value
+ * exists. And the token type will be assigned to {@code token} if the token
+ * type is known. The token type may be null for literals, since we derive
+ * that lazily.
+ */
+ private boolean hasToken;
+
+ /**
+ * The type of the next token to be returned by {@link #peek} and {@link
+ * #advance}, or {@code null} if it is unknown and must first be derived
+ * from {@code value}. This value is undefined if {@code hasToken} is false.
+ */
+ private JsonToken token;
+
+ /** The text of the next name. */
+ private String name;
+
+ /** The text of the next literal value. */
+ private String value;
+
+ // TODO: make this parser strict and offer an optional lenient mode?
+ // TODO: document how this reader is non-strict
+
+ /**
+ * Creates a new instance that reads a JSON-encoded stream from {@code in}.
+ */
+ public JsonReader(Reader in) {
+ if (in == null) {
+ throw new NullPointerException("in == null");
+ }
+ this.in = in;
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is the
+ * beginning of a new array.
+ */
+ public void beginArray() throws IOException {
+ expect(JsonToken.BEGIN_ARRAY);
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is the
+ * end of the current array.
+ */
+ public void endArray() throws IOException {
+ expect(JsonToken.END_ARRAY);
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is the
+ * beginning of a new object.
+ */
+ public void beginObject() throws IOException {
+ expect(JsonToken.BEGIN_OBJECT);
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is the
+ * end of the current array.
+ */
+ public void endObject() throws IOException {
+ expect(JsonToken.END_OBJECT);
+ }
+
+ /**
+ * Consumes {@code token}.
+ */
+ private void expect(JsonToken token) throws IOException {
+ if (quickPeek() != token) {
+ throw new IllegalStateException("Expected " + token + " but was " + peek());
+ }
+ advance();
+ }
+
+ /**
+ * Returns true if the current array or object has another element.
+ */
+ public boolean hasNext() throws IOException {
+ JsonToken peek = quickPeek();
+ return peek != JsonToken.END_OBJECT && peek != JsonToken.END_ARRAY;
+ }
+
+ /**
+ * Returns the type of the next token without consuming it.
+ */
+ public JsonToken peek() throws IOException {
+ quickPeek();
+
+ if (token == null) {
+ decodeLiteral();
+ }
+
+ return token;
+ }
+
+ /**
+ * Ensures that a token is ready. After this call either {@code token} or
+ * {@code value} will be non-null.
+ *
+ * @return the type of the next token, of {@code null} if it is unknown. For
+ * a definitive result, use {@link #peek()} which decodes the token
+ * type.
+ */
+ private JsonToken quickPeek() throws IOException {
+ if (hasToken) {
+ return token;
+ }
+
+ switch (peekStack()) {
+ case EMPTY_DOCUMENT:
+ replaceTop(JsonScope.NONEMPTY_DOCUMENT);
+ JsonToken firstToken = nextValue();
+ if (token != JsonToken.BEGIN_ARRAY && token != JsonToken.BEGIN_OBJECT) {
+ throw new IOException(
+ "Expected JSON document to start with '[' or '{' but was " + token);
+ }
+ return firstToken;
+ case EMPTY_ARRAY:
+ return nextInArray(true);
+ case NONEMPTY_ARRAY:
+ return nextInArray(false);
+ case EMPTY_OBJECT:
+ return nextInObject(true);
+ case DANGLING_NAME:
+ return objectValue();
+ case NONEMPTY_OBJECT:
+ return nextInObject(false);
+ case NONEMPTY_DOCUMENT:
+ hasToken = true;
+ return token = JsonToken.END_DOCUMENT;
+ case CLOSED:
+ throw new IllegalStateException("JsonReader is closed");
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Advances the cursor in the JSON stream to the next token.
+ */
+ private JsonToken advance() throws IOException {
+ quickPeek();
+
+ JsonToken result = token;
+ hasToken = false;
+ token = null;
+ value = null;
+ name = null;
+ return result;
+ }
+
+ /**
+ * Returns the next token, a {@link JsonToken#NAME property name}, and
+ * consumes it.
+ *
+ * @throws IOException if the next token in the stream is not a property
+ * name.
+ */
+ public String nextName() throws IOException {
+ if (quickPeek() != JsonToken.NAME) {
+ throw new IllegalStateException("Expected a name but was " + peek());
+ }
+ String result = name;
+ advance();
+ return result;
+ }
+
+ /**
+ * Returns the {@link JsonToken#STRING string} value of the next token,
+ * consuming it. If the next token is a number, this method will return its
+ * string form.
+ *
+ * @throws IllegalStateException if the next token is not a string or if
+ * this reader is closed.
+ */
+ public String nextString() throws IOException {
+ JsonToken peek = peek();
+ if (value == null || (peek != JsonToken.STRING && peek != JsonToken.NUMBER)) {
+ throw new IllegalStateException("Expected a string but was " + peek());
+ }
+
+ String result = value;
+ advance();
+ return result;
+ }
+
+ /**
+ * Returns the {@link JsonToken#BOOLEAN boolean} value of the next token,
+ * consuming it.
+ *
+ * @throws IllegalStateException if the next token is not a boolean or if
+ * this reader is closed.
+ */
+ public boolean nextBoolean() throws IOException {
+ JsonToken peek = quickPeek();
+ if (value == null || peek == JsonToken.STRING) {
+ throw new IllegalStateException("Expected a boolean but was " + peek());
+ }
+
+ boolean result;
+ if (value.equalsIgnoreCase("true")) {
+ result = true;
+ } else if (value.equalsIgnoreCase("false")) {
+ result = false;
+ } else {
+ throw new IllegalStateException("Not a boolean: " + value);
+ }
+
+ advance();
+ return result;
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is a
+ * literal null.
+ *
+ * @throws IllegalStateException if the next token is not null or if this
+ * reader is closed.
+ */
+ public void nextNull() throws IOException {
+ JsonToken peek = quickPeek();
+ if (value == null || peek == JsonToken.STRING) {
+ throw new IllegalStateException("Expected null but was " + peek());
+ }
+
+ if (!value.equalsIgnoreCase("null")) {
+ throw new IllegalStateException("Not a null: " + value);
+ }
+
+ advance();
+ }
+
+ /**
+ * Returns the {@link JsonToken#NUMBER double} value of the next token,
+ * consuming it. If the next token is a string, this method will attempt to
+ * parse it as a double.
+ *
+ * @throws IllegalStateException if the next token is not a literal value.
+ * @throws NumberFormatException if the next literal value cannot be parsed
+ * as a double, or is non-finite.
+ */
+ public double nextDouble() throws IOException {
+ quickPeek();
+ if (value == null) {
+ throw new IllegalStateException("Expected a double but was " + peek());
+ }
+
+ double result = Double.parseDouble(value);
+
+ if ((result >= 1.0d && value.startsWith("0"))
+ || Double.isNaN(result)
+ || Double.isInfinite(result)) {
+ throw new NumberFormatException(
+ "JSON forbids octal prefixes, NaN and infinities: " + value);
+ }
+
+ advance();
+ return result;
+ }
+
+ /**
+ * Returns the {@link JsonToken#NUMBER long} value of the next token,
+ * consuming it. If the next token is a string, this method will attempt to
+ * parse it as a long. If the next token's numeric value cannot be exactly
+ * represented by a Java {@code long}, this method throws.
+ *
+ * @throws IllegalStateException if the next token is not a literal value.
+ * @throws NumberFormatException if the next literal value cannot be parsed
+ * as a number, or exactly represented as a long.
+ */
+ public long nextLong() throws IOException {
+ quickPeek();
+ if (value == null) {
+ throw new IllegalStateException("Expected a long but was " + peek());
+ }
+
+ long result;
+ try {
+ result = Long.parseLong(value);
+ } catch (NumberFormatException ignored) {
+ double asDouble = Double.parseDouble(value); // don't catch this NumberFormatException
+ result = (long) asDouble;
+ if ((double) result != asDouble) {
+ throw new NumberFormatException(value);
+ }
+ }
+
+ if (result >= 1L && value.startsWith("0")) {
+ throw new NumberFormatException("JSON forbids octal prefixes: " + value);
+ }
+
+ advance();
+ return result;
+ }
+
+ /**
+ * Returns the {@link JsonToken#NUMBER int} value of the next token,
+ * consuming it. If the next token is a string, this method will attempt to
+ * parse it as an int. If the next token's numeric value cannot be exactly
+ * represented by a Java {@code int}, this method throws.
+ *
+ * @throws IllegalStateException if the next token is not a literal value.
+ * @throws NumberFormatException if the next literal value cannot be parsed
+ * as a number, or exactly represented as an int.
+ */
+ public int nextInt() throws IOException {
+ quickPeek();
+ if (value == null) {
+ throw new IllegalStateException("Expected an int but was " + peek());
+ }
+
+ int result;
+ try {
+ result = Integer.parseInt(value);
+ } catch (NumberFormatException ignored) {
+ double asDouble = Double.parseDouble(value); // don't catch this NumberFormatException
+ result = (int) asDouble;
+ if ((double) result != asDouble) {
+ throw new NumberFormatException(value);
+ }
+ }
+
+ if (result >= 1L && value.startsWith("0")) {
+ throw new NumberFormatException("JSON forbids octal prefixes: " + value);
+ }
+
+ advance();
+ return result;
+ }
+
+ /**
+ * Closes this JSON reader and the underlying {@link Reader}.
+ */
+ public void close() throws IOException {
+ hasToken = false;
+ value = null;
+ token = null;
+ stack.clear();
+ stack.add(JsonScope.CLOSED);
+ in.close();
+ }
+
+ /**
+ * Skips the next value recursively. If it is an object or array, all nested
+ * elements are skipped. This method is intended for use when the JSON token
+ * stream contains unrecognized or unhandled values.
+ */
+ public void skipValue() throws IOException {
+ // TODO: suppress string creation while elements are being skipped!
+
+ int count = 0;
+ do {
+ JsonToken token = advance();
+ if (token == JsonToken.BEGIN_ARRAY || token == JsonToken.BEGIN_OBJECT) {
+ count++;
+ } else if (token == JsonToken.END_ARRAY || token == JsonToken.END_OBJECT) {
+ count--;
+ }
+ } while (count != 0);
+ }
+
+ private JsonScope peekStack() {
+ return stack.get(stack.size() - 1);
+ }
+
+ private JsonScope pop() {
+ return stack.remove(stack.size() - 1);
+ }
+
+ private void push(JsonScope newTop) {
+ stack.add(newTop);
+ }
+
+ /**
+ * Replace the value on the top of the stack with the given value.
+ */
+ private void replaceTop(JsonScope newTop) {
+ stack.set(stack.size() - 1, newTop);
+ }
+
+ private JsonToken nextInArray(boolean firstElement) throws IOException {
+ if (firstElement) {
+ switch (nextNonWhitespace()) {
+ case ']':
+ pop();
+ hasToken = true;
+ return token = JsonToken.END_ARRAY;
+ case ',':
+ case ';':
+ /* a separator without a value first means "null". */
+ // TODO: forbid this in strict mode
+ hasToken = true;
+ return token = JsonToken.NULL;
+ default:
+ replaceTop(JsonScope.NONEMPTY_ARRAY);
+ pos--;
+ }
+ } else {
+ switch (nextNonWhitespace()) {
+ case ']':
+ pop();
+ hasToken = true;
+ return token = JsonToken.END_ARRAY;
+ case ',':
+ case ';':
+ break;
+ default:
+ throw syntaxError("Unterminated array");
+ }
+ }
+
+ return nextValue();
+ }
+
+ private JsonToken nextInObject(boolean firstElement) throws IOException {
+ /*
+ * Read delimiters. Either a comma/semicolon separating this and the
+ * previous name-value pair, or a close brace to denote the end of the
+ * object.
+ */
+ if (firstElement) {
+ /* Peek to see if this is the empty object. */
+ switch (nextNonWhitespace()) {
+ case '}':
+ pop();
+ hasToken = true;
+ return token = JsonToken.END_OBJECT;
+ default:
+ pos--;
+ }
+ } else {
+ switch (nextNonWhitespace()) {
+ case '}':
+ pop();
+ hasToken = true;
+ return token = JsonToken.END_OBJECT;
+ case ';':
+ case ',':
+ break;
+ default:
+ throw syntaxError("Unterminated object");
+ }
+ }
+
+ /* Read the name. */
+ int quote = nextNonWhitespace();
+ switch (quote) {
+ case '\'':
+ case '"':
+ name = nextString((char) quote);
+ break;
+ default:
+ pos--;
+ name = nextLiteral();
+ if (name.isEmpty()) {
+ throw syntaxError("Expected name");
+ }
+ }
+
+ replaceTop(JsonScope.DANGLING_NAME);
+ hasToken = true;
+ return token = JsonToken.NAME;
+ }
+
+ private JsonToken objectValue() throws IOException {
+ // TODO: accept only ":" in strict mode
+
+ /*
+ * Read the name/value separator. Usually a colon ':', an equals sign
+ * '=', or an arrow "=>". The last two are bogus but we include them
+ * because that's what org.json does.
+ */
+ int separator = nextNonWhitespace();
+ if (separator != ':' && separator != '=') {
+ throw syntaxError("Expected ':'");
+ }
+ if (separator == '=' && (pos < limit || fillBuffer(1)) && buffer[pos] == '>') {
+ pos++;
+ }
+
+ replaceTop(JsonScope.NONEMPTY_OBJECT);
+ return nextValue();
+ }
+
+ private JsonToken nextValue() throws IOException {
+ int c = nextNonWhitespace();
+ switch (c) {
+ case '{':
+ push(JsonScope.EMPTY_OBJECT);
+ hasToken = true;
+ return token = JsonToken.BEGIN_OBJECT;
+
+ case '[':
+ push(JsonScope.EMPTY_ARRAY);
+ hasToken = true;
+ return token = JsonToken.BEGIN_ARRAY;
+
+ case '\'':
+ case '"':
+ value = nextString((char) c);
+ hasToken = true;
+ return token = JsonToken.STRING;
+
+ default:
+ pos--;
+ return readLiteral();
+ }
+ }
+
+ /**
+ * Returns true once {@code limit - pos >= minimum}. If the data is
+ * exhausted before that many characters are available, this returns
+ * false.
+ */
+ private boolean fillBuffer(int minimum) throws IOException {
+ if (limit != pos) {
+ limit -= pos;
+ System.arraycopy(buffer, pos, buffer, 0, limit);
+ } else {
+ limit = 0;
+ }
+
+ pos = 0;
+ int total;
+ while ((total = in.read(buffer, limit, buffer.length - limit)) != -1) {
+ limit += total;
+ if (limit >= minimum) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int nextNonWhitespace() throws IOException {
+ // TODO: no comments in strict mode
+
+ while (pos < limit || fillBuffer(1)) {
+ int c = buffer[pos++];
+ switch (c) {
+ case '\t':
+ case ' ':
+ case '\n':
+ case '\r':
+ continue;
+
+ case '/':
+ if (pos == limit && !fillBuffer(1)) {
+ return c;
+ }
+
+ char peek = buffer[pos];
+ switch (peek) {
+ case '*':
+ // skip a /* c-style comment */
+ pos++;
+ if (!skipTo("*/")) {
+ throw syntaxError("Unterminated comment");
+ }
+ pos += 2;
+ continue;
+
+ case '/':
+ // skip a // end-of-line comment
+ pos++;
+ skipToEndOfLine();
+ continue;
+
+ default:
+ return c;
+ }
+
+ case '#':
+ /*
+ * Skip a # hash end-of-line comment. The JSON RFC doesn't
+ * specify this behaviour, but it's required to parse
+ * existing documents. See http://b/2571423.
+ */
+ skipToEndOfLine();
+ continue;
+
+ default:
+ return c;
+ }
+ }
+
+ throw syntaxError("End of input");
+ }
+
+ /**
+ * Advances the position until after the next newline character. If the line
+ * is terminated by "\r\n", the '\n' must be consumed as whitespace by the
+ * caller.
+ */
+ private void skipToEndOfLine() throws IOException {
+ while (pos < limit || fillBuffer(1)) {
+ char c = buffer[pos++];
+ if (c == '\r' || c == '\n') {
+ break;
+ }
+ }
+ }
+
+ private boolean skipTo(String toFind) throws IOException {
+ outer:
+ for (; pos + toFind.length() < limit || fillBuffer(toFind.length()); pos++) {
+ for (int c = 0; c < toFind.length(); c++) {
+ if (buffer[pos + c] != toFind.charAt(c)) {
+ continue outer;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the string up to but not including {@code quote}, unescaping any
+ * character escape sequences encountered along the way. The opening quote
+ * should have already been read. This consumes the closing quote, but does
+ * not include it in the returned string.
+ *
+ * @param quote either ' or ".
+ * @throws NumberFormatException if any unicode escape sequences are
+ * malformed.
+ */
+ private String nextString(char quote) throws IOException {
+ StringBuilder builder = null;
+ do {
+ /* the index of the first character not yet appended to the builder. */
+ int start = pos;
+ while (pos < limit) {
+ int c = buffer[pos++];
+
+ if (c == quote) {
+ if (builder == null) {
+ return new String(buffer, start, pos - start - 1);
+ } else {
+ builder.append(buffer, start, pos - start - 1);
+ return builder.toString();
+ }
+
+ } else if (c == '\\') {
+ if (builder == null) {
+ builder = new StringBuilder();
+ }
+ builder.append(buffer, start, pos - start - 1);
+ builder.append(readEscapeCharacter());
+ start = pos;
+ }
+ }
+
+ if (builder == null) {
+ builder = new StringBuilder();
+ }
+ builder.append(buffer, start, pos - start);
+ } while (fillBuffer(1));
+
+ throw syntaxError("Unterminated string");
+ }
+
+ /**
+ * Returns the string up to but not including any delimiter characters. This
+ * does not consume the delimiter character.
+ */
+ private String nextLiteral() throws IOException {
+ // TODO: use a much smaller set of permitted literal characters in strict mode;
+ // these characters are derived from org.json's lenient mode
+
+ StringBuilder builder = null;
+ do {
+ /* the index of the first character not yet appended to the builder. */
+ int start = pos;
+ while (pos < limit) {
+ int c = buffer[pos++];
+ switch (c) {
+ case '{':
+ case '}':
+ case '[':
+ case ']':
+ case '/':
+ case '\\':
+ case ':':
+ case '=':
+ case ',':
+ case ';':
+ case '#':
+ case ' ':
+ case '\t':
+ case '\f':
+ case '\r':
+ case '\n':
+ pos--;
+ if (builder == null) {
+ return new String(buffer, start, pos - start);
+ } else {
+ builder.append(buffer, start, pos - start);
+ return builder.toString();
+ }
+ }
+ }
+
+ if (builder == null) {
+ builder = new StringBuilder();
+ }
+ builder.append(buffer, start, pos - start);
+ } while (fillBuffer(1));
+
+ return builder.toString();
+ }
+
+ @Override public String toString() {
+ return getClass().getSimpleName() + " near " + getSnippet();
+ }
+
+ /**
+ * Unescapes the character identified by the character or characters that
+ * immediately follow a backslash. The backslash '\' should have already
+ * been read. This supports both unicode escapes "u000A" and two-character
+ * escapes "\n".
+ *
+ * @throws NumberFormatException if any unicode escape sequences are
+ * malformed.
+ */
+ private char readEscapeCharacter() throws IOException {
+ if (pos == limit && !fillBuffer(1)) {
+ throw syntaxError("Unterminated escape sequence");
+ }
+
+ char escaped = buffer[pos++];
+ switch (escaped) {
+ case 'u':
+ if (pos + 4 > limit && !fillBuffer(4)) {
+ throw syntaxError("Unterminated escape sequence");
+ }
+ String hex = new String(buffer, pos, 4);
+ pos += 4;
+ return (char) Integer.parseInt(hex, 16);
+
+ case 't':
+ return '\t';
+
+ case 'b':
+ return '\b';
+
+ case 'n':
+ return '\n';
+
+ case 'r':
+ return '\r';
+
+ case 'f':
+ return '\f';
+
+ case '\'':
+ case '"':
+ case '\\':
+ default:
+ return escaped;
+ }
+ }
+
+ /**
+ * Reads a null, boolean, numeric or unquoted string literal value.
+ */
+ private JsonToken readLiteral() throws IOException {
+ String literal = nextLiteral();
+ if (literal.isEmpty()) {
+ throw syntaxError("Expected literal value");
+ }
+ value = literal;
+ hasToken = true;
+ return token = null; // use decodeLiteral() to get the token type
+ }
+
+ /**
+ * Assigns {@code nextToken} based on the value of {@code nextValue}.
+ */
+ private void decodeLiteral() {
+ if (value.equalsIgnoreCase("null")) {
+ token = JsonToken.NULL;
+ } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
+ token = JsonToken.BOOLEAN;
+ } else {
+ try {
+ Double.parseDouble(value); // this work could potentially be cached
+ token = JsonToken.NUMBER;
+ } catch (NumberFormatException ignored) {
+ /* an unquoted string. This document is not well-formed! */
+ token = JsonToken.STRING;
+ }
+ }
+ }
+
+ /**
+ * Throws a new IO exception with the given message and a context snippet
+ * with this reader's content.
+ */
+ public IOException syntaxError(String message) throws IOException {
+ throw new JsonSyntaxException(message + " near " + getSnippet());
+ }
+
+ private CharSequence getSnippet() {
+ StringBuilder snippet = new StringBuilder();
+ int beforePos = Math.min(pos, 20);
+ snippet.append(buffer, pos - beforePos, beforePos);
+ int afterPos = Math.min(limit - pos, 20);
+ snippet.append(buffer, pos, afterPos);
+ return snippet;
+ }
+
+ private static class JsonSyntaxException extends IOException {
+ private JsonSyntaxException(String s) {
+ super(s);
+ }
+ }
+}
diff --git a/core/java/android/util/JsonScope.java b/core/java/android/util/JsonScope.java
new file mode 100644
index 0000000..ca534e9
--- /dev/null
+++ b/core/java/android/util/JsonScope.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2010 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 android.util;
+
+/**
+ * Lexical scoping elements within a JSON reader or writer.
+ */
+enum JsonScope {
+
+ /**
+ * An array with no elements requires no separators or newlines before
+ * it is closed.
+ */
+ EMPTY_ARRAY,
+
+ /**
+ * A array with at least one value requires a comma and newline before
+ * the next element.
+ */
+ NONEMPTY_ARRAY,
+
+ /**
+ * An object with no name/value pairs requires no separators or newlines
+ * before it is closed.
+ */
+ EMPTY_OBJECT,
+
+ /**
+ * An object whose most recent element is a key. The next element must
+ * be a value.
+ */
+ DANGLING_NAME,
+
+ /**
+ * An object with at least one name/value pair requires a comma and
+ * newline before the next element.
+ */
+ NONEMPTY_OBJECT,
+
+ /**
+ * No object or array has been started.
+ */
+ EMPTY_DOCUMENT,
+
+ /**
+ * A document with at an array or object.
+ */
+ NONEMPTY_DOCUMENT,
+
+ /**
+ * A document that's been closed and cannot be accessed.
+ */
+ CLOSED,
+}
diff --git a/core/java/android/util/JsonToken.java b/core/java/android/util/JsonToken.java
new file mode 100644
index 0000000..45bc6ca
--- /dev/null
+++ b/core/java/android/util/JsonToken.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2010 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 android.util;
+
+/**
+ * A structure, name or value type in a JSON-encoded string.
+ */
+public enum JsonToken {
+
+ /**
+ * The opening of a JSON array. Written using {@link JsonWriter#beginObject}
+ * and read using {@link JsonReader#beginObject}.
+ */
+ BEGIN_ARRAY,
+
+ /**
+ * The closing of a JSON array. Written using {@link JsonWriter#endArray}
+ * and read using {@link JsonReader#endArray}.
+ */
+ END_ARRAY,
+
+ /**
+ * The opening of a JSON object. Written using {@link JsonWriter#beginObject}
+ * and read using {@link JsonReader#beginObject}.
+ */
+ BEGIN_OBJECT,
+
+ /**
+ * The closing of a JSON object. Written using {@link JsonWriter#endObject}
+ * and read using {@link JsonReader#endObject}.
+ */
+ END_OBJECT,
+
+ /**
+ * A JSON property name. Within objects, tokens alternate between names and
+ * their values. Written using {@link JsonWriter#name} and read using {@link
+ * JsonReader#nextName}
+ */
+ NAME,
+
+ /**
+ * A JSON string.
+ */
+ STRING,
+
+ /**
+ * A JSON number represented in this API by a Java {@code double}, {@code
+ * long}, or {@code int}.
+ */
+ NUMBER,
+
+ /**
+ * A JSON {@code true} or {@code false}.
+ */
+ BOOLEAN,
+
+ /**
+ * A JSON {@code null}.
+ */
+ NULL,
+
+ /**
+ * The end of the JSON stream. This sentinel value is returned by {@link
+ * JsonReader#peek()} to signal that the JSON-encoded value has no more
+ * tokens.
+ */
+ END_DOCUMENT
+}
diff --git a/core/java/android/util/JsonWriter.java b/core/java/android/util/JsonWriter.java
new file mode 100644
index 0000000..913b047
--- /dev/null
+++ b/core/java/android/util/JsonWriter.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2010 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 android.util;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Writes a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
+ * encoded value to a stream, one token at a time. The stream includes both
+ * literal values (strings, numbers, booleans and nulls) as well as the begin
+ * and end delimiters of objects and arrays.
+ *
+ * <h3>Encoding JSON</h3>
+ * To encode your data as JSON, create a new {@code JsonWriter}. Each JSON
+ * document must contain one top-level array or object. Call methods on the
+ * writer as you walk the structure's contents, nesting arrays and objects as
+ * necessary:
+ * <ul>
+ * <li>To write <strong>arrays</strong>, first call {@link #beginArray()}.
+ * Write each of the array's elements with the appropriate {@link #value}
+ * methods or by nesting other arrays and objects. Finally close the array
+ * using {@link #endArray()}.
+ * <li>To write <strong>objects</strong>, first call {@link #beginObject()}.
+ * Write each of the object's properties by alternating calls to
+ * {@link #name} with the property's value. Write property values with the
+ * appropriate {@link #value} method or by nesting other objects or arrays.
+ * Finally close the object using {@link #endObject()}.
+ * </ul>
+ *
+ * <h3>Example</h3>
+ * Suppose we'd like to encode a stream of messages such as the following: <pre> {@code
+ * [
+ * {
+ * "id": 912345678901,
+ * "text": "How do I write JSON on Android?",
+ * "geo": null,
+ * "user": {
+ * "name": "android_newb",
+ * "followers_count": 41
+ * }
+ * },
+ * {
+ * "id": 912345678902,
+ * "text": "@android_newb just use android.util.JsonWriter!",
+ * "geo": [50.454722, -104.606667],
+ * "user": {
+ * "name": "jesse",
+ * "followers_count": 2
+ * }
+ * }
+ * ]}</pre>
+ * This code encodes the above structure: <pre> {@code
+ * public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
+ * JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
+ * writer.setIndentSpaces(4);
+ * writeMessagesArray(writer, messages);
+ * writer.close();
+ * }
+ *
+ * public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException {
+ * writer.beginArray();
+ * for (Message message : messages) {
+ * writeMessage(writer, message);
+ * }
+ * writer.endArray();
+ * }
+ *
+ * public void writeMessage(JsonWriter writer, Message message) throws IOException {
+ * writer.beginObject();
+ * writer.name("id").value(message.getId());
+ * writer.name("text").value(message.getText());
+ * if (message.getGeo() != null) {
+ * writer.name("geo");
+ * writeDoublesArray(writer, message.getGeo());
+ * } else {
+ * writer.name("geo").nullValue();
+ * }
+ * writer.name("user");
+ * writeUser(writer, message.getUser());
+ * writer.endObject();
+ * }
+ *
+ * public void writeUser(JsonWriter writer, User user) throws IOException {
+ * writer.beginObject();
+ * writer.name("name").value(user.getName());
+ * writer.name("followers_count").value(user.getFollowersCount());
+ * writer.endObject();
+ * }
+ *
+ * public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException {
+ * writer.beginArray();
+ * for (Double value : doubles) {
+ * writer.value(value);
+ * }
+ * writer.endArray();
+ * }}</pre>
+ *
+ * <p>Each {@code JsonWriter} may be used to write a single JSON stream.
+ * Instances of this class are not thread safe. Calls that would result in a
+ * malformed JSON string will fail with an {@link IllegalStateException}.
+ */
+public final class JsonWriter {
+
+ /** The output data, containing at most one top-level array or object. */
+ private final Writer out;
+
+ private final List<JsonScope> stack = new ArrayList<JsonScope>();
+ {
+ stack.add(JsonScope.EMPTY_DOCUMENT);
+ }
+
+ /**
+ * A string containing a full set of spaces for a single level of
+ * indentation, or null for no pretty printing.
+ */
+ private String indent;
+
+ /**
+ * The name/value separator; either ":" or ": ".
+ */
+ private String separator = ":";
+
+ /**
+ * Creates a new instance that writes a JSON-encoded stream to {@code out}.
+ * For best performance, ensure {@link Writer} is buffered; wrapping in
+ * {@link java.io.BufferedWriter BufferedWriter} if necessary.
+ */
+ public JsonWriter(Writer out) {
+ if (out == null) {
+ throw new NullPointerException("out == null");
+ }
+ this.out = out;
+ }
+
+ /**
+ * Sets the number of spaces to indent each line in the encoded document.
+ * If {@code indent == 0} the encoded document will be compact. If {@code
+ * indent > 0}, the encoded document will be more human-readable.
+ */
+ public void setIndentSpaces(int indent) {
+ if (indent < 0) {
+ throw new IllegalArgumentException("indent < 0");
+ }
+
+ if (indent > 0) {
+ char[] indentChars = new char[indent];
+ Arrays.fill(indentChars, ' ');
+ this.indent = new String(indentChars);
+ this.separator = ": ";
+ } else {
+ this.indent = null;
+ this.separator = ":";
+ }
+ }
+
+ /**
+ * Begins encoding a new array. Each call to this method must be paired with
+ * a call to {@link #endArray}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter beginArray() throws IOException {
+ return open(JsonScope.EMPTY_ARRAY, "[");
+ }
+
+ /**
+ * Ends encoding the current array.
+ *
+ * @return this writer.
+ */
+ public JsonWriter endArray() throws IOException {
+ return close(JsonScope.EMPTY_ARRAY, JsonScope.NONEMPTY_ARRAY, "]");
+ }
+
+ /**
+ * Begins encoding a new object. Each call to this method must be paired
+ * with a call to {@link #endObject}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter beginObject() throws IOException {
+ return open(JsonScope.EMPTY_OBJECT, "{");
+ }
+
+ /**
+ * Ends encoding the current object.
+ *
+ * @return this writer.
+ */
+ public JsonWriter endObject() throws IOException {
+ return close(JsonScope.EMPTY_OBJECT, JsonScope.NONEMPTY_OBJECT, "}");
+ }
+
+ /**
+ * Enters a new scope by appending any necessary whitespace and the given
+ * bracket.
+ */
+ private JsonWriter open(JsonScope empty, String openBracket) throws IOException {
+ beforeValue(true);
+ stack.add(empty);
+ out.write(openBracket);
+ return this;
+ }
+
+ /**
+ * Closes the current scope by appending any necessary whitespace and the
+ * given bracket.
+ */
+ private JsonWriter close(JsonScope empty, JsonScope nonempty, String closeBracket)
+ throws IOException {
+ JsonScope context = peek();
+ if (context != nonempty && context != empty) {
+ throw new IllegalStateException("Nesting problem: " + stack);
+ }
+
+ stack.remove(stack.size() - 1);
+ if (context == nonempty) {
+ newline();
+ }
+ out.write(closeBracket);
+ return this;
+ }
+
+ /**
+ * Returns the value on the top of the stack.
+ */
+ private JsonScope peek() {
+ return stack.get(stack.size() - 1);
+ }
+
+ /**
+ * Replace the value on the top of the stack with the given value.
+ */
+ private void replaceTop(JsonScope topOfStack) {
+ stack.set(stack.size() - 1, topOfStack);
+ }
+
+ /**
+ * Encodes the property name.
+ *
+ * @param name the name of the forthcoming value. May not be null.
+ * @return this writer.
+ */
+ public JsonWriter name(String name) throws IOException {
+ if (name == null) {
+ throw new NullPointerException("name == null");
+ }
+ beforeName();
+ string(name);
+ return this;
+ }
+
+ /**
+ * Encodes {@code value}.
+ *
+ * @param value the literal string value, or null to encode a null literal.
+ * @return this writer.
+ */
+ public JsonWriter value(String value) throws IOException {
+ if (value == null) {
+ return nullValue();
+ }
+ beforeValue(false);
+ string(value);
+ return this;
+ }
+
+ /**
+ * Encodes {@code null}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter nullValue() throws IOException {
+ beforeValue(false);
+ out.write("null");
+ return this;
+ }
+
+ /**
+ * Encodes {@code value}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter value(boolean value) throws IOException {
+ beforeValue(false);
+ out.write(value ? "true" : "false");
+ return this;
+ }
+
+ /**
+ * Encodes {@code value}.
+ *
+ * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+ * {@link Double#isInfinite() infinities}.
+ * @return this writer.
+ */
+ public JsonWriter value(double value) throws IOException {
+ if (Double.isNaN(value) || Double.isInfinite(value)) {
+ throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
+ }
+ beforeValue(false);
+ out.append(Double.toString(value));
+ return this;
+ }
+
+ /**
+ * Encodes {@code value}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter value(long value) throws IOException {
+ beforeValue(false);
+ out.write(Long.toString(value));
+ return this;
+ }
+
+ /**
+ * Ensures all buffered data is written to the underlying {@link Writer}
+ * and flushes that writer.
+ */
+ public void flush() throws IOException {
+ out.flush();
+ }
+
+ /**
+ * Flushes and closes this writer and the underlying {@link Writer}.
+ *
+ * @throws IOException if the JSON document is incomplete.
+ */
+ public void close() throws IOException {
+ out.close();
+
+ if (peek() != JsonScope.NONEMPTY_DOCUMENT) {
+ throw new IOException("Incomplete document");
+ }
+ }
+
+ private void string(String value) throws IOException {
+ out.write("\"");
+ for (int i = 0, length = value.length(); i < length; i++) {
+ char c = value.charAt(i);
+
+ /*
+ * From RFC 4627, "All Unicode characters may be placed within the
+ * quotation marks except for the characters that must be escaped:
+ * quotation mark, reverse solidus, and the control characters
+ * (U+0000 through U+001F)."
+ */
+ switch (c) {
+ case '"':
+ case '\\':
+ case '/':
+ out.write('\\');
+ out.write(c);
+ break;
+
+ case '\t':
+ out.write("\\t");
+ break;
+
+ case '\b':
+ out.write("\\b");
+ break;
+
+ case '\n':
+ out.write("\\n");
+ break;
+
+ case '\r':
+ out.write("\\r");
+ break;
+
+ case '\f':
+ out.write("\\f");
+ break;
+
+ default:
+ if (c <= 0x1F) {
+ out.write(String.format("\\u%04x", (int) c));
+ } else {
+ out.write(c);
+ }
+ break;
+ }
+
+ }
+ out.write("\"");
+ }
+
+ private void newline() throws IOException {
+ if (indent == null) {
+ return;
+ }
+
+ out.write("\n");
+ for (int i = 1; i < stack.size(); i++) {
+ out.write(indent);
+ }
+ }
+
+ /**
+ * Inserts any necessary separators and whitespace before a name. Also
+ * adjusts the stack to expect the name's value.
+ */
+ private void beforeName() throws IOException {
+ JsonScope context = peek();
+ if (context == JsonScope.NONEMPTY_OBJECT) { // first in object
+ out.write(',');
+ } else if (context != JsonScope.EMPTY_OBJECT) { // not in an object!
+ throw new IllegalStateException("Nesting problem: " + stack);
+ }
+ newline();
+ replaceTop(JsonScope.DANGLING_NAME);
+ }
+
+ /**
+ * Inserts any necessary separators and whitespace before a literal value,
+ * inline array, or inline object. Also adjusts the stack to expect either a
+ * closing bracket or another element.
+ *
+ * @param root true if the value is a new array or object, the two values
+ * permitted as top-level elements.
+ */
+ private void beforeValue(boolean root) throws IOException {
+ switch (peek()) {
+ case EMPTY_DOCUMENT: // first in document
+ if (!root) {
+ throw new IllegalStateException(
+ "JSON must start with an array or an object.");
+ }
+ replaceTop(JsonScope.NONEMPTY_DOCUMENT);
+ break;
+
+ case EMPTY_ARRAY: // first in array
+ replaceTop(JsonScope.NONEMPTY_ARRAY);
+ newline();
+ break;
+
+ case NONEMPTY_ARRAY: // another in array
+ out.append(',');
+ newline();
+ break;
+
+ case DANGLING_NAME: // value for name
+ out.append(separator);
+ replaceTop(JsonScope.NONEMPTY_OBJECT);
+ break;
+
+ case NONEMPTY_DOCUMENT:
+ throw new IllegalStateException(
+ "JSON must have only one top-level value.");
+
+ default:
+ throw new IllegalStateException("Nesting problem: " + stack);
+ }
+ }
+}
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 63a0c4c..8e1338d 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -350,7 +350,6 @@
@Override
public void setDrawFilter(DrawFilter filter) {
- // TODO: Implement PaintDrawFilter
mFilter = filter;
}
@@ -522,9 +521,13 @@
@Override
public void drawPath(Path path, Paint paint) {
- throw new UnsupportedOperationException();
+ boolean hasModifier = setupModifiers(paint);
+ nDrawPath(mRenderer, path.mNativePath, paint.mNativePaint);
+ if (hasModifier) nResetModifiers(mRenderer);
}
+ private native void nDrawPath(int renderer, int path, int paint);
+
@Override
public void drawPicture(Picture picture) {
throw new UnsupportedOperationException();
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 681ef70..6e89a8b 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -179,6 +179,7 @@
private boolean mSupportMultipleWindows = false;
private boolean mShrinksStandaloneImagesToFit = false;
private long mMaximumDecodedImageSize = 0; // 0 means default
+ private boolean mPrivateBrowsingEnabled = false;
// HTML5 API flags
private boolean mAppCacheEnabled = false;
private boolean mDatabaseEnabled = false;
@@ -1468,6 +1469,24 @@
}
}
+ /**
+ * Returns whether private browsing is enabled.
+ */
+ /* package */ boolean isPrivateBrowsingEnabled() {
+ return mPrivateBrowsingEnabled;
+ }
+
+ /**
+ * Sets whether private browsing is enabled.
+ * @param flag Whether private browsing should be enabled.
+ */
+ /* package */ synchronized void setPrivateBrowsingEnabled(boolean flag) {
+ if (mPrivateBrowsingEnabled != flag) {
+ mPrivateBrowsingEnabled = flag;
+ postSync();
+ }
+ }
+
int getDoubleTapToastCount() {
return mDoubleTapToastCount;
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 127f196..d1b0902 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -21,8 +21,8 @@
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
import android.database.DataSetObserver;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -43,7 +43,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.speech.tts.TextToSpeech;
import android.text.Selection;
@@ -74,18 +73,22 @@
import android.widget.AbsoluteLayout;
import android.widget.Adapter;
import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.CheckedTextView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.Scroller;
import android.widget.Toast;
-import android.widget.AdapterView.OnItemClickListener;
+
+import junit.framework.Assert;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
@@ -93,8 +96,6 @@
import java.util.Map;
import java.util.Set;
-import junit.framework.Assert;
-
/**
* <p>A View that displays web pages. This class is the basis upon which you
* can roll your own web browser or simply display some online content within your Activity.
@@ -289,7 +290,7 @@
* property to {@code device-dpi}. This stops Android from performing scaling in your web page and
* allows you to make the necessary adjustments for each density via CSS and JavaScript.</p>
*
- *
+ *
*/
@Widget
public class WebView extends AbsoluteLayout
@@ -825,7 +826,18 @@
* @param defStyle The default style resource ID.
*/
public WebView(Context context, AttributeSet attrs, int defStyle) {
- this(context, attrs, defStyle, null);
+ this(context, attrs, defStyle, false);
+ }
+
+ /**
+ * Construct a new WebView with layout parameters and a default style.
+ * @param context A Context object used to access application assets.
+ * @param attrs An AttributeSet passed to our parent.
+ * @param defStyle The default style resource ID.
+ */
+ public WebView(Context context, AttributeSet attrs, int defStyle,
+ boolean privateBrowsing) {
+ this(context, attrs, defStyle, null, privateBrowsing);
}
/**
@@ -841,7 +853,7 @@
* @hide pending API council approval.
*/
protected WebView(Context context, AttributeSet attrs, int defStyle,
- Map<String, Object> javascriptInterfaces) {
+ Map<String, Object> javascriptInterfaces, boolean privateBrowsing) {
super(context, attrs, defStyle);
if (AccessibilityManager.getInstance(context).isEnabled()) {
@@ -863,6 +875,10 @@
*/
init();
updateMultiTouchSupport(context);
+
+ if (privateBrowsing) {
+ startPrivateBrowsing();
+ }
}
void updateMultiTouchSupport(Context context) {
@@ -1040,8 +1056,10 @@
/*
* Return the amount of the titlebarview (if any) that is visible
+ *
+ * @hide
*/
- int getVisibleTitleHeight() {
+ public int getVisibleTitleHeight() {
return Math.max(getTitleHeight() - mScrollY, 0);
}
@@ -1668,6 +1686,34 @@
}
}
+ /**
+ * Returns true if private browsing is enabled in this WebView.
+ */
+ public boolean isPrivateBrowsingEnabled () {
+ return getSettings().isPrivateBrowsingEnabled();
+ }
+
+ private void startPrivateBrowsing () {
+ boolean wasPrivateBrowsingEnabled = isPrivateBrowsingEnabled();
+
+ getSettings().setPrivateBrowsingEnabled(true);
+
+ if (!wasPrivateBrowsingEnabled) {
+ StringBuilder data = new StringBuilder(1024);
+ try {
+ InputStreamReader file = new InputStreamReader(mContext.getResources().openRawResource(com.android.internal.R.raw.incognito_mode_start_page));
+ int size;
+ char[] buffer = new char[1024];
+ while ((size = file.read(buffer)) != -1) {
+ data.append(buffer, 0, size);
+ }
+ } catch (IOException e) {
+ // This should never happen since this is a static resource.
+ }
+ loadDataWithBaseURL(null, data.toString(), "text/html", "utf-8", null);
+ }
+ }
+
private boolean extendScroll(int y) {
int finalY = mScroller.getFinalY();
int newY = pinLocY(finalY + y);
@@ -2650,6 +2696,16 @@
mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response);
}
+ /**
+ * Request the scroller to abort any ongoing animation
+ *
+ * @hide
+ */
+ public void stopScroll() {
+ mScroller.forceFinished(true);
+ mLastVelocity = 0;
+ }
+
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
@@ -3706,6 +3762,19 @@
private boolean mGotCenterDown = false;
@Override
+ public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+ // send complex characters to webkit for use by JS and plugins
+ if (keyCode == KeyEvent.KEYCODE_UNKNOWN && event.getCharacters() != null) {
+ // pass the key to DOM
+ mWebViewCore.sendMessage(EventHub.KEY_DOWN, event);
+ mWebViewCore.sendMessage(EventHub.KEY_UP, event);
+ // return true as DOM handles the key
+ return true;
+ }
+ return false;
+ }
+
+ @Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (DebugFlags.WEB_VIEW) {
Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 21af570..3c28c94 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -1627,9 +1627,16 @@
+ evt);
}
int keyCode = evt.getKeyCode();
- if (!nativeKey(keyCode, evt.getUnicodeChar(),
- evt.getRepeatCount(), evt.isShiftPressed(), evt.isAltPressed(),
- evt.isSymPressed(),
+ int unicodeChar = evt.getUnicodeChar();
+
+ if (keyCode == KeyEvent.KEYCODE_UNKNOWN && evt.getCharacters() != null
+ && evt.getCharacters().length() > 0) {
+ // we should only receive individual complex characters
+ unicodeChar = evt.getCharacters().codePointAt(0);
+ }
+
+ if (!nativeKey(keyCode, unicodeChar, evt.getRepeatCount(), evt.isShiftPressed(),
+ evt.isAltPressed(), evt.isSymPressed(),
isDown) && keyCode != KeyEvent.KEYCODE_ENTER) {
if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
&& keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index f8a77a1..9c84e0e 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -22,6 +22,7 @@
import android.graphics.drawable.Drawable;
import android.net.LocalServerSocket;
import android.os.Debug;
+import android.os.FileUtils;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Config;
@@ -506,6 +507,9 @@
closeServerSocket();
+ // set umask to 0077 so new files and directories will default to owner-only permissions.
+ FileUtils.setUMask(FileUtils.S_IRWXG | FileUtils.S_IRWXO);
+
/*
* Pass the remaining arguments to SystemServer.
* "--nice-name=system_server com.android.server.SystemServer"
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b3b33fd..1e6d219 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -700,15 +700,6 @@
}
}
- /* enable poisoning of memory of freed objects */
- property_get("dalvik.vm.gc.overwritefree", propBuf, "false");
- if (strcmp(propBuf, "true") == 0) {
- opt.optionString = "-Xgc:overwritefree";
- mOptions.add(opt);
- } else if (strcmp(propBuf, "false") != 0) {
- LOGW("dalvik.vm.gc.overwritefree should be 'true' or 'false'");
- }
-
/* enable debugging; set suspend=y to pause during VM init */
#ifdef HAVE_ANDROID_OS
/* use android ADB transport */
diff --git a/core/jni/android_os_FileUtils.cpp b/core/jni/android_os_FileUtils.cpp
index 21cb919..d3faa2f 100644
--- a/core/jni/android_os_FileUtils.cpp
+++ b/core/jni/android_os_FileUtils.cpp
@@ -113,6 +113,11 @@
#endif
}
+jint android_os_FileUtils_setUMask(JNIEnv* env, jobject clazz, jint mask)
+{
+ return umask(mask);
+}
+
jint android_os_FileUtils_getFatVolumeId(JNIEnv* env, jobject clazz, jstring path)
{
#if HAVE_ANDROID_OS
@@ -170,6 +175,7 @@
static const JNINativeMethod methods[] = {
{"setPermissions", "(Ljava/lang/String;III)I", (void*)android_os_FileUtils_setPermissions},
{"getPermissions", "(Ljava/lang/String;[I)I", (void*)android_os_FileUtils_getPermissions},
+ {"setUMask", "(I)I", (void*)android_os_FileUtils_setUMask},
{"getFatVolumeId", "(Ljava/lang/String;)I", (void*)android_os_FileUtils_getFatVolumeId},
{"getFileStatus", "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z", (void*)android_os_FileUtils_getFileStatus},
};
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 142e194..9fd2b86 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -228,6 +228,11 @@
renderer->drawRect(left, top, right, bottom, paint);
}
+static void android_view_GLES20Canvas_drawPath(JNIEnv* env, jobject canvas,
+ OpenGLRenderer* renderer, SkPath* path, SkPaint* paint) {
+ renderer->drawPath(path, paint);
+}
+
// ----------------------------------------------------------------------------
// Shaders and color filters
// ----------------------------------------------------------------------------
@@ -317,6 +322,7 @@
{ "nDrawPatch", "(II[BFFFFI)V", (void*) android_view_GLES20Canvas_drawPatch },
{ "nDrawColor", "(III)V", (void*) android_view_GLES20Canvas_drawColor },
{ "nDrawRect", "(IFFFFI)V", (void*) android_view_GLES20Canvas_drawRect },
+ { "nDrawPath", "(III)V", (void*) android_view_GLES20Canvas_drawPath },
{ "nResetModifiers", "(I)V", (void*) android_view_GLES20Canvas_resetModifiers },
{ "nSetupShader", "(II)V", (void*) android_view_GLES20Canvas_setupShader },
diff --git a/core/res/res/raw/incognito_mode_start_page.html b/core/res/res/raw/incognito_mode_start_page.html
new file mode 100644
index 0000000..b070c6d
--- /dev/null
+++ b/core/res/res/raw/incognito_mode_start_page.html
@@ -0,0 +1,24 @@
+<html>
+ <head>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
+ <title>New incognito window</title>
+ </head>
+ <body>
+ <p><strong>You've gone incognito</strong>. Pages you view in this window
+ won't appear in your browser history or search history, and they won't
+ leave other traces, like cookies, on your computer after you close the
+ incognito window. Any files you download or bookmarks you create will be
+ preserved, however.</p>
+
+ <p><strong>Going incognito doesn't affect the behavior of other people,
+ servers, or software. Be wary of:</strong></p>
+
+ <ul>
+ <li>Websites that collect or share information about you</li>
+ <li>Internet service providers or employers that track the pages you visit</li>
+ <li>Malicious software that tracks your keystrokes in exchange for free smileys</li>
+ <li>Surveillance by secret agents</li>
+ <li>People standing behind you</li>
+ </ul>
+ </body>
+</html>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 5e7b693..f7f4d69 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Hlasová schránka"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"Problém s připojením nebo neplatný kód MMI."</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"Operace je omezena pouze na povolená telefonní čísla."</string>
<string name="serviceEnabled" msgid="8147278346414714315">"Služba byla zapnuta."</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"Služba byla zapnuta pro:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"Služba byla vypnuta."</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"Přístup ke kartě SD."</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"zakázání či změny stavového řádku"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"Umožňuje aplikaci zakázat stavový řádek nebo přidat či odebrat systémové ikony."</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"stavový řádek"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"Umožňuje aplikaci být stavovým řádkem."</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"rozbalení a sbalení stavového řádku"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Umožňuje aplikaci rozbalit či sbalit stavový řádek."</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"zachycení odchozích hovorů"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Umožňuje aplikaci změnit globální nastavení zvuku, například hlasitost či směrování."</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"nahrání zvuku"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"Umožňuje aplikaci získat přístup k nahrávání zvuku."</string>
- <string name="permlab_camera" msgid="8059288807274039014">"pořizování fotografií"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"Umožňuje aplikaci pořizovat fotografie pomocí fotoaparátu. Toto nastavení aplikaci umožní shromažďovat fotografie toho, na co je zrovna fotoaparát namířen."</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"pořizování fotografií a videí"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"Umožňuje aplikaci pořizovat fotografie a videa pomocí fotoaparátu. Toto nastavení aplikaci umožní získávat snímky toho, na co je zrovna fotoaparát namířen."</string>
<string name="permlab_brick" msgid="8337817093326370537">"trvalé vypnutí telefonu"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"Umožňuje aplikaci trvale vypnout celý telefon. Toto je velmi nebezpečné nastavení."</string>
<string name="permlab_reboot" msgid="2898560872462638242">"vynucení restartování telefonu"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"Umožňuje aplikaci ovládat vibrace."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"ovládání kontrolky"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"Umožňuje aplikaci ovládat kontrolku."</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"přístup k zařízením USB"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"Umožní aplikaci přístup k zařízením USB."</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"testování hardwaru"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"Umožňuje aplikaci ovládat různé periferie pro účely testování hardwaru."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"přímé volání na telefonní čísla"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"Určuje, kdy dojde k uzamčení zařízení a bude požadováno opětovné zadání hesla."</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"Vymazání všech dat"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"Bez dalšího potvrzení obnoví výchozí nastavení z výroby a smaže všechna data."</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Domů"</item>
<item msgid="869923650527136615">"Mobil"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"Práce"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Jiné"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Vlastní"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"pomocí <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> pomocí <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Zadejte kód PIN"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Zadejte heslo pro odblokování"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Zadejte kód PIN pro odblokování"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Nesprávný kód PIN"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Chcete-li telefon odemknout, stiskněte Menu a poté 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Číslo tísňové linky"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"Zavolat zpět"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Správně!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Zkuste to prosím znovu"</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Zkuste to prosím znovu"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Nabíjení (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Nabito."</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"Karta SIM je zablokována."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Odblokování karty SIM..."</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"<xliff:g id="NUMBER_0">%d</xliff:g>krát jste nakreslili nesprávné bezpečnostní gesto. "\n\n"Opakujte prosím akci za <xliff:g id="NUMBER_1">%d</xliff:g> s."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Vícekrát (<xliff:g id="NUMBER_0">%d</xliff:g>) jste nesprávně zadali heslo. "\n\n"Zkuste to znovu za <xliff:g id="NUMBER_1">%d</xliff:g> s."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Vícekrát (<xliff:g id="NUMBER_0">%d</xliff:g>) jste nesprávně zadali kód PIN. "\n\n"Zkuste to znovu za <xliff:g id="NUMBER_1">%d</xliff:g> s."</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"<xliff:g id="NUMBER_0">%d</xliff:g>krát jste nesprávně nakreslili své bezpečnostní gesto. Po dalších neúspěšných pokusech (<xliff:g id="NUMBER_1">%d</xliff:g>) budete požádáni o odemčení telefonu pomocí přihlášení do účtu Google."\n\n" Akci prosím opakujte za několik sekund (<xliff:g id="NUMBER_2">%d</xliff:g>)."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Sekundy zbývající do dalšího pokusu: <xliff:g id="NUMBER">%d</xliff:g>."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Zapomněli jste gesto?"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"Alt"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Vymazat"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Žádná oznámení"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Probíhající"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Oznámení"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"Nabíjení..."</string>
- <string name="battery_low_title" msgid="7923774589611311406">"Prosím připojte dobíjecí zařízení"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"Baterie je vybitá:"</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"Zbývá <xliff:g id="NUMBER">%d%%</xliff:g> nebo méně."</string>
- <string name="battery_low_why" msgid="7279169609518386372">"Využití baterie"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Test továrního nastavení se nezdařil"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Test FACTORY_TEST lze provést pouze u balíčků nainstalovaných ve složce /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Nebyl nalezen žádný balíček umožňující test FACTORY_TEST."</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"Vybrat vše"</string>
- <string name="selectText" msgid="3889149123626888637">"Označit text"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"Zastavit označování textu"</string>
<string name="cut" msgid="3092569408438626261">"Vyjmout"</string>
- <string name="cutAll" msgid="2436383270024931639">"Vyjmout vše"</string>
<string name="copy" msgid="2681946229533511987">"Kopírovat"</string>
- <string name="copyAll" msgid="2590829068100113057">"Kopírovat vše"</string>
<string name="paste" msgid="5629880836805036433">"Vložit"</string>
<string name="copyUrl" msgid="2538211579596067402">"Kopírovat adresu URL"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"Metoda zadávání dat"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"Přidat slovo „<xliff:g id="WORD">%s</xliff:g>“ do slovníku"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"Úpravy textu"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"Málo paměti"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"V telefonu zbývá málo místa pro ukládání dat."</string>
<string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"OK"</string>
<string name="no" msgid="5141531044935541497">"Zrušit"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"Upozornění"</string>
+ <string name="loading" msgid="1760724998928255250">"Načítání..."</string>
<string name="capital_on" msgid="1544682755514494298">"ZAPNUTO"</string>
<string name="capital_off" msgid="6815870386972805832">"VYPNOUT"</string>
<string name="whichApplication" msgid="4533185947064773386">"Dokončit akci pomocí aplikace"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"Ukončit aplikaci"</string>
<string name="report" msgid="4060218260984795706">"Nahlásit"</string>
<string name="wait" msgid="7147118217226317732">"Počkat"</string>
+ <string name="smv_application" msgid="295583804361236288">"Aplikace <xliff:g id="APPLICATION">%1$s</xliff:g> (proces<xliff:g id="PROCESS">%2$s</xliff:g>) porušila své samovynucované zásady StrictMode."</string>
+ <string name="smv_process" msgid="5120397012047462446">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> porušil své samovynucované zásady StrictMode."</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"Běží aplikace <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Tuto možnost vyberte, chcete-li přepnout na aplikaci."</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Přepnout mezi aplikacemi?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Než spustíte novou aplikaci, je třeba zastavit jinou spuštěnou aplikaci."</string>
+ <string name="old_app_action" msgid="493129172238566282">"Návrat do aplikace <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="old_app_description" msgid="942967900237208466">"Nespouštět novou aplikaci."</string>
+ <string name="new_app_action" msgid="5472756926945440706">"Spustit aplikaci <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"Zastavit starou aplikaci bez uložení."</string>
<string name="sendText" msgid="5132506121645618310">"Vyberte činnost s textem"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Hlasitost vyzvánění"</string>
<string name="volume_music" msgid="5421651157138628171">"Hlasitost médií"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Síť VPN L2TP/IPSec s předsdíleným klíčem"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Síť VPN L2TP/IPSec s certifikátem"</string>
<string name="upload_file" msgid="2897957172366730416">"Zvolit soubor"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"Není vybrán žádný soubor"</string>
<string name="reset" msgid="2448168080964209908">"Resetovat"</string>
<string name="submit" msgid="1602335572089911941">"Odeslat"</string>
- <string name="description_star" msgid="2654319874908576133">"oblíbené"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Aktivován režim V autě"</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"Vyberte, chcete-li ukončit režim Na cestě."</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"Je aktivní tethering nebo hotspot"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"Dotykem zahájíte konfiguraci"</string>
+ <string name="back_button_label" msgid="2300470004503343439">"Zpět"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"Další"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"Vysoké využití mobilních dat"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"Dotykem zobrazíte další informace o využití mobilních dat"</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"Byl překročen limit mobilních dat"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 4b6668c..2b67a2a 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Voicemail"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"Forbindelsesproblemer eller ugyldigt MMI-nummer."</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"Du kan kun foretage handlinger med faste opkaldsnumre."</string>
<string name="serviceEnabled" msgid="8147278346414714315">"Tjenesten blev aktiveret."</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"Tjenesten blev aktiveret for:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"Tjenesten er deaktiveret."</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"Få adgang til SD-kortet."</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"deaktiver eller rediger statuslinje"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"Tillader, at et program deaktiverer statuslinjen eller tilføjer eller fjerner systemikoner."</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"statusbjælke"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"Tillader, at programmet er statusbjælken."</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"udvid/skjul statuslinje"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Tillader, at et program udvider eller skjuler statuslinjen."</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"opfang udgående opkald"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Tillader, at et program ændrer globale lydindstillinger som f.eks. lydstyrke og kanalisering."</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"optag lyd"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"Tillader, at et program får adgang til lydregistreringsstien."</string>
- <string name="permlab_camera" msgid="8059288807274039014">"tag billeder"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"Tillader, at programmet tager billeder med kameraet. Dette giver programmet mulighed for altid at indsamle de billeder, kameraet ser."</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"tag billeder og optag video"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"Tillader, at programmet tager billeder og optager video med kameraet. Dette giver programmet mulighed for altid at indsamle de billeder, kameraet ser."</string>
<string name="permlab_brick" msgid="8337817093326370537">"deaktiver telefonen permanent"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"Tillader, at programmet deaktiverer hele telefonen permanent. Dette er meget farligt."</string>
<string name="permlab_reboot" msgid="2898560872462638242">"tving telefon til at genstarte"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"Lader programmet kontrollere vibratoren."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"kontroller lommelygte"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"Tillader, at programmet kontrollerer lommelygten."</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"adgang til USB-enheder."</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"Tillader, at programmet har adgang til USB-enheder."</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"test hardware"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"Tillader, at et program kontrollerer forskellige perifere enheder for at teste hardwaren."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"ring direkte op til telefonnumre"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"Kontrol når enheden låses, så du skal indtaste adgangskoden igen."</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"Slet alle data"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"Foretag en fabriksnulstilling, der sletter alle dine data uden bekræftelse."</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Hjem"</item>
<item msgid="869923650527136615">"Mobil"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"Arbejde"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Andre"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Tilpasset"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Indtast PIN-kode"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Indtast adgangskode for at låse op"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Indtast pinkode for at låse op"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Forkert PIN-kode!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Tryk på Menu og dernæst på 0 for at låse op."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Nødnummer"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"Tilbage til opkald"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Rigtigt!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Beklager! Prøv igen"</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Beklager! Prøv igen"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Oplader (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Opladt."</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM-kortet er låst."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Låser SIM-kortet op ..."</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Du har tegnet dit oplåsningsmønster forkert <xliff:g id="NUMBER_0">%d</xliff:g> gange. "\n\n"Prøv igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Du har indtastet din adgangskode forkert <xliff:g id="NUMBER_0">%d</xliff:g> gange. "\n\n"Prøv igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Du har indtastet din pinkode forkert <xliff:g id="NUMBER_0">%d</xliff:g> gange. "\n\n"Prøv igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Du har tegnet dit oplåsningsmønster forkert <xliff:g id="NUMBER_0">%d</xliff:g> gange. Efter <xliff:g id="NUMBER_1">%d</xliff:g> forsøg mere vil du blive bedt om at låse din telefon op ved hjælp af dit Google-login"\n\n" Prøv igen om <xliff:g id="NUMBER_2">%d</xliff:g> sekunder."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Prøv igen om <xliff:g id="NUMBER">%d</xliff:g> sekunder."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Har du glemt mønstret?"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Ryd"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Ingen meddelelser"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"I gang"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Meddelelser"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"Oplader ..."</string>
- <string name="battery_low_title" msgid="7923774589611311406">"Forbind oplader"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"Batteriet er ved at være fladt:"</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> eller mindre tilbage."</string>
- <string name="battery_low_why" msgid="7279169609518386372">"Batteriforbrug"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Fabrikstest mislykkedes"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Handlingen FACTORY_TEST understøttes kun af pakker installeret i /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Der blev ikke fundet nogen pakke, som leverer handlingen FACTORY_TEST."</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"Vælg alle"</string>
- <string name="selectText" msgid="3889149123626888637">"Marker tekst"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"Hold op med at markere tekst"</string>
<string name="cut" msgid="3092569408438626261">"Klip"</string>
- <string name="cutAll" msgid="2436383270024931639">"Klip alle"</string>
<string name="copy" msgid="2681946229533511987">"Kopier"</string>
- <string name="copyAll" msgid="2590829068100113057">"Kopier alle"</string>
<string name="paste" msgid="5629880836805036433">"Indsæt"</string>
<string name="copyUrl" msgid="2538211579596067402">"Kopier webadresse"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"Inputmetode"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"Føj \"<xliff:g id="WORD">%s</xliff:g>\" til ordbog"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"Rediger tekst"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"Der er ikke så meget plads tilbage"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"Der er næsten ikke mere plads på telefonen."</string>
<string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"OK"</string>
<string name="no" msgid="5141531044935541497">"Annuller"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"Bemærk"</string>
+ <string name="loading" msgid="1760724998928255250">"Indlæser..."</string>
<string name="capital_on" msgid="1544682755514494298">"TIL"</string>
<string name="capital_off" msgid="6815870386972805832">"FRA"</string>
<string name="whichApplication" msgid="4533185947064773386">"Fuldfør handling ved hjælp af"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"Tving til at lukke"</string>
<string name="report" msgid="4060218260984795706">"Rapporter"</string>
<string name="wait" msgid="7147118217226317732">"Vent"</string>
+ <string name="smv_application" msgid="295583804361236288">"Programmet <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) har overtrådt sin egen StrictMode-politik."</string>
+ <string name="smv_process" msgid="5120397012047462446">"Processen <xliff:g id="PROCESS">%1$s</xliff:g> har overtrådt sin egen StrictMode-politik."</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> er i gang"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Vælg for at skifte program"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Skift program?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Der kører et andet program, der skal stoppes, før du kan starte et nyt."</string>
+ <string name="old_app_action" msgid="493129172238566282">"Tilbage til <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="old_app_description" msgid="942967900237208466">"Start ikke det nye program."</string>
+ <string name="new_app_action" msgid="5472756926945440706">"Start <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"Stop det gamle program uden at gemme."</string>
<string name="sendText" msgid="5132506121645618310">"Vælg en handling for teksten"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Lydstyrke for opkald"</string>
<string name="volume_music" msgid="5421651157138628171">"Lydstyrke for medier"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPN baseret på forhåndsdelt nøglekodning"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certifikatbaseret L2TP/IPSec VPN"</string>
<string name="upload_file" msgid="2897957172366730416">"Vælg fil"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil er valgt"</string>
<string name="reset" msgid="2448168080964209908">"Nulstil"</string>
<string name="submit" msgid="1602335572089911941">"Send"</string>
- <string name="description_star" msgid="2654319874908576133">"favorit"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Biltilstand er aktiveret"</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"Vælg for at afslutte biltilstand."</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"Tethering eller hotspot er aktivt"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"Tryk for at konfigurere"</string>
+ <string name="back_button_label" msgid="2300470004503343439">"Tilbage"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"Næste"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"Højt mobildataforbrug"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"Tryk for oplysninger om brug af mobildata"</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"Grænsen for mobildata er overskredet"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 11095c0..ac42fd1 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Mailbox"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"Verbindungsproblem oder ungültiger MMI-Code."</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"Der Vorgang ist nur für Ihre zugelassenen Rufnummern möglich."</string>
<string name="serviceEnabled" msgid="8147278346414714315">"Dienst wurde aktiviert."</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"Dienst wurde aktiviert für:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"Dienst wurde deaktiviert."</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"Greift auf die SD-Karte zu."</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"Statusleiste deaktivieren oder ändern"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"Ermöglicht der Anwendung, die Statusanzeige zu deaktivieren oder Systemsymbole hinzuzufügen oder zu entfernen."</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"Statusleiste"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"Die Anwendung wird damit in der Statusleiste angezeigt."</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"Statusleiste ein-/ausblenden"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Ermöglicht der Anwendung, die Statusleiste ein- oder auszublenden."</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"Abgehende Anrufe abfangen"</string>
@@ -239,7 +242,7 @@
<string name="permdesc_readInputState" msgid="5132879321450325445">"Ermöglicht Anwendungen, die von Ihnen gedrückten Tasten zu überwachen (etwa die Eingabe eines Passworts). Dies gilt auch für die Interaktion mit anderen Anwendungen. Sollte für normale Anwendungen nicht benötigt werden."</string>
<string name="permlab_bindInputMethod" msgid="3360064620230515776">"An eine Eingabemethode binden"</string>
<string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Ermöglicht dem Halter, sich an die Oberfläche einer Eingabemethode auf oberster Ebene zu binden. Sollte nie für normale Anwendungen benötigt werden."</string>
- <string name="permlab_bindWallpaper" msgid="8716400279937856462">"An ein Hintergrundbild binden"</string>
+ <string name="permlab_bindWallpaper" msgid="8716400279937856462">"An einen Hintergrund binden"</string>
<string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Ermöglicht dem Halter, sich an die Oberfläche einer Eingabemethode auf oberster Ebene zu binden. Sollte nie für normale Anwendungen benötigt werden."</string>
<string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"Interaktion mit einem Geräteadministrator"</string>
<string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Ermöglicht dem Halter, Intents an einen Geräteadministrator zu senden. Sollte nie für normale Anwendungen benötigt werden."</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Ermöglicht der Anwendung, Änderungen an allgemeinen Audioeinstellungen wie Lautstärke und Weiterleitung vorzunehmen."</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"Audio aufnehmen"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"Ermöglicht der Anwendung, auf den Pfad für Audioaufzeichnungen zuzugreifen."</string>
- <string name="permlab_camera" msgid="8059288807274039014">"Fotos aufnehmen"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"Ermöglicht der Anwendung, Fotos mit der Kamera aufzunehmen. So kann die Anwendung jederzeit Bilder zusammentragen, die von der Kamera erfasst werden."</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"Bilder und Videos aufnehmen"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"Ermöglicht der Anwendung, Fotos und Videos mit der Kamera aufzunehmen. So kann die Anwendung jederzeit Bilder zusammentragen, die von der Kamera erfasst werden."</string>
<string name="permlab_brick" msgid="8337817093326370537">"Telefon dauerhaft deaktivieren."</string>
<string name="permdesc_brick" msgid="5569526552607599221">"Ermöglicht der Anwendung, das gesamte Telefon dauerhaft zu deaktivieren. Dies birgt hohe Risiken."</string>
<string name="permlab_reboot" msgid="2898560872462638242">"Neustart des Telefons erzwingen"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"Ermöglicht der Anwendung, den Vibrationsalarm zu steuern."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"Lichtanzeige steuern"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"Ermöglicht der Anwendung, die Lichtanzeige zu steuern."</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"auf USB-Geräte zugreifen"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"Die Anwendung erhält Zugriff auf USB-Geräte."</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"Hardware testen"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"Ermöglicht einer Anwendung, verschiedene Peripherie-Geräte zu Hardware-Testzwecken zu steuern."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"Telefonnummern direkt anrufen"</string>
@@ -359,10 +364,10 @@
<string name="permdesc_devicePower" msgid="4577331933252444818">"Ermöglicht der Anwendung, das Telefon ein- oder auszuschalten."</string>
<string name="permlab_factoryTest" msgid="3715225492696416187">"In Werkstestmodus ausführen"</string>
<string name="permdesc_factoryTest" msgid="8136644990319244802">"Führt einen systemnahen Herstellertest durch, in dessen Rahmen auf die gesamte Telefonhardware zugegriffen werden kann. Nur verfügbar, wenn ein Telefon im Werkstestmodus ausgeführt wird."</string>
- <string name="permlab_setWallpaper" msgid="6627192333373465143">"Hintergrundbild festlegen"</string>
- <string name="permdesc_setWallpaper" msgid="6417041752170585837">"Ermöglicht der Anwendung, das System-Hintergrundbild festzulegen."</string>
- <string name="permlab_setWallpaperHints" msgid="3600721069353106851">"Größenhinweise für Hintergrundbild festlegen"</string>
- <string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Ermöglicht der Anwendung, die Größenhinweise für das Hintergrundbild festzulegen."</string>
+ <string name="permlab_setWallpaper" msgid="6627192333373465143">"Hintergrund festlegen"</string>
+ <string name="permdesc_setWallpaper" msgid="6417041752170585837">"Ermöglicht der Anwendung, den System-Hintergrund festzulegen."</string>
+ <string name="permlab_setWallpaperHints" msgid="3600721069353106851">"Größenhinweise für Hintergrund festlegen"</string>
+ <string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Ermöglicht der Anwendung, die Größenhinweise für den Hintergrund festzulegen."</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"System auf Werkseinstellung zurücksetzen"</string>
<string name="permdesc_masterClear" msgid="5033465107545174514">"Ermöglicht einer Anwendung, das System komplett auf Werkseinstellung zurückzusetzen. Hierbei werden alle Daten, Konfigurationen und installierten Anwendungen gelöscht."</string>
<string name="permlab_setTime" msgid="2021614829591775646">"Zeit einstellen"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"Steuerung der Gerätesperre; erfordert die erneute Passworteingabe"</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"Alle Daten löschen"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"Zurücksetzen auf die Werkseinstellungen. Dabei werden alle Ihre Daten ohne Nachfrage gelöscht."</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Privat"</item>
<item msgid="869923650527136615">"Mobil"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"Beruflich"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Andere"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Benutzerdefiniert"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"über <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> über <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN-Code eingeben"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Passwort zum Entsperren eingeben"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"PIN zum Entsperren eingeben"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Falscher PIN-Code!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Drücken Sie zum Entsperren die Menütaste und dann auf \"0\"."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Notrufnummer"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"Zurück zum Anruf"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Korrekt!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Tut uns leid. Versuchen Sie es noch einmal."</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Tut uns leid. Versuchen Sie es noch einmal."</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Wird geladen (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Aufgeladen"</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"Bitte PIN-Code eingeben"</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"SIM-Karte wird entsperrt..."</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Sie haben Ihr Entsperrungsmuster <xliff:g id="NUMBER_0">%d</xliff:g>-mal falsch gezeichnet. "\n\n"Versuchen Sie es in <xliff:g id="NUMBER_1">%d</xliff:g> Sekunden erneut."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Sie haben Ihr Passwort <xliff:g id="NUMBER_0">%d</xliff:g> Mal falsch eingegeben. "\n\n"Versuchen Sie es in <xliff:g id="NUMBER_1">%d</xliff:g> Sekunden erneut."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Sie haben Ihre PIN <xliff:g id="NUMBER_0">%d</xliff:g> Mal falsch eingegeben. "\n\n"Versuchen Sie es in <xliff:g id="NUMBER_1">%d</xliff:g> Sekunden erneut."</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Sie haben Ihr Entsperrungsmuster <xliff:g id="NUMBER_0">%d</xliff:g>-mal falsch gezeichnet. Nach <xliff:g id="NUMBER_1">%d</xliff:g> weiteren erfolglosen Versuchen werden Sie aufgefordert, Ihr Telefon mithilfe Ihrer Google-Anmeldeinformationen zu entsperren. "\n\n"Versuchen Sie es in <xliff:g id="NUMBER_2">%d</xliff:g> Sekunden erneut."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Versuchen Sie es in <xliff:g id="NUMBER">%d</xliff:g> Sekunden erneut."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Muster vergessen?"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Löschen"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Keine Benachrichtigungen"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Aktuell"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Benachrichtigungen"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"Wird aufgeladen..."</string>
- <string name="battery_low_title" msgid="7923774589611311406">"Ladegerät anschließen"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"Akku ist fast leer."</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> oder weniger verbleiben."</string>
- <string name="battery_low_why" msgid="7279169609518386372">"Akkuverbrauch"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Werkstest fehlgeschlagen"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Die Aktion FACTORY_TEST wird nur für unter \"/system/app\" gespeicherte Pakete unterstützt."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Es wurden kein Paket mit der Aktion FACTORY_TEST gefunden."</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"Alles auswählen"</string>
- <string name="selectText" msgid="3889149123626888637">"Text auswählen"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"Textauswahl beenden"</string>
<string name="cut" msgid="3092569408438626261">"Ausschneiden"</string>
- <string name="cutAll" msgid="2436383270024931639">"Alles ausschneiden"</string>
<string name="copy" msgid="2681946229533511987">"Kopieren"</string>
- <string name="copyAll" msgid="2590829068100113057">"Alles kopieren"</string>
<string name="paste" msgid="5629880836805036433">"Einfügen"</string>
<string name="copyUrl" msgid="2538211579596067402">"URL kopieren"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"Eingabemethode"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"\"<xliff:g id="WORD">%s</xliff:g>\" zum Wörterbuch hinzufügen"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"Text bearbeiten"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"Geringer Speicher"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"Kaum noch freier Telefonspeicher verfügbar."</string>
<string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"OK"</string>
<string name="no" msgid="5141531044935541497">"Abbrechen"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"Achtung"</string>
+ <string name="loading" msgid="1760724998928255250">"Wird geladen..."</string>
<string name="capital_on" msgid="1544682755514494298">"EIN"</string>
<string name="capital_off" msgid="6815870386972805832">"AUS"</string>
<string name="whichApplication" msgid="4533185947064773386">"Aktion durchführen mit"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"Schließen erzwingen"</string>
<string name="report" msgid="4060218260984795706">"Bericht"</string>
<string name="wait" msgid="7147118217226317732">"Warten"</string>
+ <string name="smv_application" msgid="295583804361236288">"Die Anwendung <xliff:g id="APPLICATION">%1$s</xliff:g> (Prozess <xliff:g id="PROCESS">%2$s</xliff:g>) hat gegen ihre selbsterzwungene StrictMode-Richtlinie verstoßen."</string>
+ <string name="smv_process" msgid="5120397012047462446">"Der Prozess <xliff:g id="PROCESS">%1$s</xliff:g> hat gegen seine selbsterzwungene StrictMode-Richtlinie verstoßen."</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> läuft"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Auswahl zum Wechseln in die Anwendung"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Anwendung wechseln?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Eine andere Anwendung wird bereits ausgeführt und muss vor dem Start einer neuen Anwendung beendet werden."</string>
+ <string name="old_app_action" msgid="493129172238566282">"Zu <xliff:g id="OLD_APP">%1$s</xliff:g> zurückkehren"</string>
+ <string name="old_app_description" msgid="942967900237208466">"Starten Sie die neue Anwendung nicht."</string>
+ <string name="new_app_action" msgid="5472756926945440706">"<xliff:g id="OLD_APP">%1$s</xliff:g> starten"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"Beenden Sie die Anwendung ohne zu speichern."</string>
<string name="sendText" msgid="5132506121645618310">"Aktion für Text auswählen"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Klingeltonlautstärke"</string>
<string name="volume_music" msgid="5421651157138628171">"Medienlautstärke"</string>
@@ -844,19 +856,21 @@
<string name="sync_binding_label" msgid="3687969138375092423">"Synchronisieren"</string>
<string name="accessibility_binding_label" msgid="4148120742096474641">"Eingabehilfen"</string>
<string name="wallpaper_binding_label" msgid="1240087844304687662">"Hintergrund"</string>
- <string name="chooser_wallpaper" msgid="7873476199295190279">"Hintergrundbild ändern"</string>
+ <string name="chooser_wallpaper" msgid="7873476199295190279">"Hintergrund ändern"</string>
<string name="pptp_vpn_description" msgid="2688045385181439401">"Point-to-Point-Tunneling-Protokoll"</string>
<string name="l2tp_vpn_description" msgid="3750692169378923304">"Layer-2-Tunneling-Protokoll"</string>
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec-VPN mit vorinstalliertem Schlüssel"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Zertifikat mit vorinstalliertem Schlüssel"</string>
<string name="upload_file" msgid="2897957172366730416">"Datei auswählen"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"Keine Datei ausgewählt"</string>
<string name="reset" msgid="2448168080964209908">"Zurücksetzen"</string>
<string name="submit" msgid="1602335572089911941">"Senden"</string>
- <string name="description_star" msgid="2654319874908576133">"Favorit"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Automodus aktiviert"</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"Zum Beenden des Automodus auswählen"</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"Tethering oder Hotspot aktiv"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"Zum Konfigurieren berühren"</string>
+ <string name="back_button_label" msgid="2300470004503343439">"Zurück"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"Weiter"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"Hohe Mobildatennutzung"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"Weitere Informationen über die Mobildatennutzung durch Berühren aufrufen"</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"Mobildatenlimit überschritten"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index d6b1e09..c08e1da 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Αυτόματος τηλεφωνητής"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"Πρόβλημα σύνδεσης ή μη έγκυρος κώδικας MMI."</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"Η λειτουργία περιορίζεται μόνο σε καθορισμένους αριθμούς κλήσης."</string>
<string name="serviceEnabled" msgid="8147278346414714315">"Η υπηρεσία ενεργοποιήθηκε."</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"Η υπηρεσία ενεργοποιήθηκε για:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"Η υπηρεσία έχει απενεργοποιηθεί."</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"Πρόσβαση στην κάρτα SD."</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"απενεργοποίηση ή τροποποίηση γραμμής κατάστασης"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"Επιτρέπει στην εφαρμογή να απενεργοποιεί τη γραμμή κατάστασης ή να προσθέτει και να αφαιρεί εικονίδια συστήματος."</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"γραμμή κατάστασης"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"Επιτρέπει στην εφαρμογή να βρίσκεται στη γραμμή κατάστασης."</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"ανάπτυξη/σύμπτυξη γραμμής κατάστασης"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Επιτρέπει σε μια εφαρμογή να αναπτύξει ή να συμπτύξει την γραμμή κατάστασης."</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"φραγή εξερχόμενων κλήσεων"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Επιτρέπει σε μια εφαρμογή να τροποποιήσει καθολικές ρυθμίσεις ήχου όπως ένταση ήχου και δρομολόγηση ήχου."</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"εγγραφή ήχου"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"Επιτρέπει σε μια εφαρμογή την πρόσβαση στη διαδρομή εγγραφής ήχου."</string>
- <string name="permlab_camera" msgid="8059288807274039014">"λήψη φωτογραφιών"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"Επιτρέπει σε μια εφαρμογή τη λήψη φωτογραφιών με την κάμερα. Αυτό επιτρέπει στην εφαρμογή να συλλέξει εικόνες, στις οποίες εστιάζει η κάμερα οποιαδήποτε στιγμή."</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"λήψη φωτογραφιών και βίντεο"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"Επιτρέπει σε μια εφαρμογή τη λήψη φωτογραφιών και βίντεο με την κάμερα. Αυτό επιτρέπει στην εφαρμογή να συλλέξει εικόνες, στις οποίες εστιάζει η κάμερα οποιαδήποτε στιγμή."</string>
<string name="permlab_brick" msgid="8337817093326370537">"μόνιμη απενεργοποίηση τηλεφώνου"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"Επιτρέπει στην εφαρμογή τη μόνιμη απενεργοποίηση όλων των λειτουργιών του τηλεφώνου, το οποίο είναι εξαιρετικά επικίνδυνο."</string>
<string name="permlab_reboot" msgid="2898560872462638242">"αναγκαστική επανεκκίνηση τηλεφώνου"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"Επιτρέπει στην εφαρμογή τον έλεγχο του δονητή."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"έλεγχος φακού"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"Επιτρέπει στην εφαρμογή τον έλεγχο του φακού."</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"πρόσβαση σε συσκευές USB"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"Επιτρέπει στην εφαρμογή την πρόσβαση σε συσκευές USB."</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"δοκιμή υλικού"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"Επιτρέπει σε μια εφαρμογή τον έλεγχο διαφόρων περιφερειακών για την εκτέλεση δοκιμών υλικού."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"απευθείας κλήση τηλεφωνικών αριθμών"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"Ελέγχει πότε κλειδώνει η συσκευή, απαιτώντας κωδικό πρόσβασης για επανείσοδο."</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"Διαγραφή όλων των δεδομένων"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"Πραγματοποιείται επαναφορά εργοστασιακών ρυθμίσεων, με τη διαγραφή όλων των δεδομένων σας χωρίς επιβεβαίωση."</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Οικία"</item>
<item msgid="869923650527136615">"Κινητό"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"Εργασία"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Άλλο"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Προσαρμοσμένο"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"μέσω <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> μέσω <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Πληκτρολογήστε τον κωδικό αριθμό PIN"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Εισαγάγετε τον κωδικό πρόσβασης για ξεκλείδωμα"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Εισαγάγετε το PIN για ξεκλείδωμα"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Εσφαλμένος κωδικός αριθμός PIN!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Για ξεκλείδωμα, πατήστε το πλήκτρο Menu και, στη συνέχεια, το πλήκτρο 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Αριθμός έκτακτης ανάγκης"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"Επιστροφή στην κλήση"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Σωστό!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Προσπαθήστε αργότερα"</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Προσπαθήστε αργότερα"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Φόρτιση (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Φορτίστηκε."</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"Η κάρτα SIM είναι κλειδωμένη."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Ξεκλείδωμα κάρτας SIM..."</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Σχεδιάσατε εσφαλμένα το μοτίβο ξεκλειδώματος<xliff:g id="NUMBER_0">%d</xliff:g> φορές. "\n\n"Προσπαθήστε ξανά σε <xliff:g id="NUMBER_1">%d</xliff:g> δευτερόλεπτα."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Καταχωρίσατε εσφαλμένα τον κωδικό πρόσβασης <xliff:g id="NUMBER_0">%d</xliff:g> φορές. "\n\n"Προσπαθήστε ξανά σε <xliff:g id="NUMBER_1">%d</xliff:g> δευτερόλεπτα."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Καταχωρίσατε εσφαλμένα το PIN σας<xliff:g id="NUMBER_0">%d</xliff:g> φορές. "\n\n"Προσπαθήστε ξανά σε <xliff:g id="NUMBER_1">%d</xliff:g> δευτερόλεπτα."</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Σχεδιάσατε το μοτίβο ξεκλειδώματος εσφαλμένα <xliff:g id="NUMBER_0">%d</xliff:g> φορές. Μετά από <xliff:g id="NUMBER_1">%d</xliff:g> ανεπιτυχείς προσπάθειες ακόμη, θα σας ζητηθεί να ξεκλειδώσετε το τηλέφωνό σας με τη χρήση της σύνδεσής σας Google."\n\n" Προσπαθήστε ξανά σε <xliff:g id="NUMBER_2">%d</xliff:g> \nδευτερόλεπτα."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Προσπαθήστε ξανά σε <xliff:g id="NUMBER">%d</xliff:g> δευτερόλεπτα."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Ξεχάσατε το μοτίβο;"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Εκκαθάριση"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Δεν υπάρχουν ειδοποιήσεις"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Εν εξελίξει"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Ειδοποιήσεις"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"Φόρτιση..."</string>
- <string name="battery_low_title" msgid="7923774589611311406">"Συνδέστε τον φορτιστή"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"Η στάθμη της μπαταρίας είναι χαμηλή:"</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"Απομένει <xliff:g id="NUMBER">%d%%</xliff:g> ή λιγότερο."</string>
- <string name="battery_low_why" msgid="7279169609518386372">"Χρήση μπαταρίας"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Η εργοστασιακή δοκιμή απέτυχε"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Η ενέργεια FACTORY_TEST υποστηρίζεται μόνο για πακέτα που είναι εγκατεστημένα στον κατάλογο /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Δεν βρέθηκε πακέτο που να παρέχει την ενέργεια FACTORY_TEST."</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"Επιλογή όλων"</string>
- <string name="selectText" msgid="3889149123626888637">"Επιλογή κειμένου"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"Διακοπή επιλογής κειμένου"</string>
<string name="cut" msgid="3092569408438626261">"Αποκοπή"</string>
- <string name="cutAll" msgid="2436383270024931639">"Αποκοπή όλων"</string>
<string name="copy" msgid="2681946229533511987">"Αντιγραφή"</string>
- <string name="copyAll" msgid="2590829068100113057">"Αντιγραφή όλων"</string>
<string name="paste" msgid="5629880836805036433">"Επικόλληση"</string>
<string name="copyUrl" msgid="2538211579596067402">"Αντιγραφή διεύθυνσης URL"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"Μέθοδος εισόδου"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"Προσθήκη της λέξης \"<xliff:g id="WORD">%s</xliff:g>\" στο λεξικό"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"Επεξεργασία κειμένου"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"Απομένει λίγος ελεύθερος χώρος"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"Έχει απομείνει λίγος ελεύθερος αποθηκευτικός χώρος στο τηλέφωνο."</string>
<string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"OK"</string>
<string name="no" msgid="5141531044935541497">"Ακύρωση"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"Προσοχή"</string>
+ <string name="loading" msgid="1760724998928255250">"Φόρτωση..."</string>
<string name="capital_on" msgid="1544682755514494298">"Ενεργοποιημένο"</string>
<string name="capital_off" msgid="6815870386972805832">"Απενεργοποίηση"</string>
<string name="whichApplication" msgid="4533185947064773386">"Ολοκλήρωση ενέργειας με τη χρήση"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"Αναγκαστικό κλείσιμο"</string>
<string name="report" msgid="4060218260984795706">"Αναφορά"</string>
<string name="wait" msgid="7147118217226317732">"Αναμονή"</string>
+ <string name="smv_application" msgid="295583804361236288">"Η εφαρμογή <xliff:g id="APPLICATION">%1$s</xliff:g> (διεργασία <xliff:g id="PROCESS">%2$s</xliff:g>) παραβίασε την αυτοεπιβαλόμενη πολιτική StrictMode."</string>
+ <string name="smv_process" msgid="5120397012047462446">"Η διεργασία <xliff:g id="PROCESS">%1$s</xliff:g> παραβίασε την αυτοεπιβαλόμενη πολιτική StrictMode."</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"Η εφαρμογή <xliff:g id="APP">%1$s</xliff:g> εκτελείται"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Επιλέξτε για αλλαγή σε εφαρμογή"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Αλλαγή εφαρμογών;"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Εκτελείται ήδη άλλη εφαρμογή την οποία πρέπει να διακόψετε για να είναι δυνατή η εκτέλεση της νέας."</string>
+ <string name="old_app_action" msgid="493129172238566282">"Επιστροφή σε <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="old_app_description" msgid="942967900237208466">"Μην εκκινήσετε τη νέα εφαρμογή."</string>
+ <string name="new_app_action" msgid="5472756926945440706">"Εκκίνηση της εφαρμογής <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"Διακόψτε την παλιά εφαρμογή χωρίς αποθήκευση."</string>
<string name="sendText" msgid="5132506121645618310">"Επιλέξτε μια ενέργεια για το κείμενο"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Ένταση ειδοποίησης ήχου"</string>
<string name="volume_music" msgid="5421651157138628171">"Ένταση ήχου πολυμέσων"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Κλειδί pre-shared βάσει L2TP/IPSec VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Πιστοποιητικό βάσει L2TP/IPSec VPN"</string>
<string name="upload_file" msgid="2897957172366730416">"Επιλογή αρχείου"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"Δεν έχει επιλεγεί αρχείο"</string>
<string name="reset" msgid="2448168080964209908">"Επαναφορά"</string>
<string name="submit" msgid="1602335572089911941">"Υποβολή"</string>
- <string name="description_star" msgid="2654319874908576133">"αγαπημένο"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Η λειτουργία αυτοκινήτου είναι ενεργοποιημένη"</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"Επιλέξτε για έξοδο από τη λειτουργία αυτοκινήτου."</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"Σύνδεση μέσω κινητής συσκευής ή σημείου πρόσβασης ενεργή"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"Αγγίξτε για να γίνει διαμόρφωση"</string>
+ <string name="back_button_label" msgid="2300470004503343439">"Πίσω"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"Επόμενο"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"Υψηλή χρήση δεδομένων κινητής τηλεφωνίας"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"Αγγίξτε για να μάθετε περισσότερα σχετικά με τη χρήση δεδομένων κινητής τηλεφωνίας"</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"Ξεπεράστηκε το όριο δεδομένων κινητής τηλεφωνίας"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 0953202..26d329e 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Correo de voz"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"Problema de conexión o código incorrecto de MMI."</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"La operación está limitada a números de marcación fija."</string>
<string name="serviceEnabled" msgid="8147278346414714315">"Se ha activado el servicio."</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"Se activó el servicio para:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"Se ha desactivado el servicio."</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"Acceder a la tarjeta SD."</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"desactivar o modificar la barra de estado"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"Admite que la aplicación desactive la barra de estado, o agregue y elimine íconos del sistema."</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"barra de estado"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"Permite que la aplicación sea la barra de estado."</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"expandir o reducir la barra de estado"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Admite que la aplicación expanda o reduzca la barra de estado."</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"interceptar llamadas salientes"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Admite que la aplicación modifique la configuración global de audio, como el volumen y el enrutamiento."</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"grabar audio"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"Admite que la aplicación acceda a la ruta de grabación de audio."</string>
- <string name="permlab_camera" msgid="8059288807274039014">"tomar fotografías"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"Admite una aplicación que toma fotografías con la cámara. Esto permite que la aplicación en cualquier momento recopile imágenes que esté viendo la cámara."</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"tomar fotografías y grabar videos"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"Admite una aplicación que toma fotografías y graba video con la cámara. Esto permite que la aplicación en cualquier momento recopile imágenes que esté viendo la cámara."</string>
<string name="permlab_brick" msgid="8337817093326370537">"desactivar teléfono de manera permanente"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"Admite que la aplicación desactive todo el teléfono de manera permanente. Esto es muy peligroso."</string>
<string name="permlab_reboot" msgid="2898560872462638242">"provocar el reinicio del teléfono"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"Admite que la aplicación controle el vibrador."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"controlar linterna"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"Admite que la aplicación controle la linterna."</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"acceder a dispositivos USB"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"Permite que la aplicación acceda a dispositivos USB."</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"probar el hardware"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"Admite que la aplicación controle diversos periféricos con el fin de probar el hardware."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"llamar directamente a números de teléfono"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"Controlar cuando se bloquee el dispositivo, requiere que vuelvas a ingresar tu contraseña."</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"Borrar todos los datos"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"Realizar un reestablecimiento de fábrica y borrar todos tus datos sin ninguna confirmación."</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Casa"</item>
<item msgid="869923650527136615">"Celular"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"Trabajo"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Otro"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Personalizado"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"a través de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> a través de <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Ingresar el código de PIN"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Ingresar la contraseña para desbloquear"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Ingresa el PIN para desbloquear"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"¡Código de PIN incorrecto!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear, presiona el menú y luego 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emergencia"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"Regresar a llamada"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Correcto"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Lo sentimos, vuelve a intentarlo"</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Lo sentimos, vuelve a intentarlo"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Cargando (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Cargada."</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"Segmento <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"La tarjeta SIM está bloqueada."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Desbloqueando tarjeta SIM…"</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Has extraído incorrectamente tu patrón de desbloqueo <xliff:g id="NUMBER_0">%d</xliff:g> veces. "\n\n"Vuelve a intentarlo en <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Has ingresado tu contraseña de manera incorrecta <xliff:g id="NUMBER_0">%d</xliff:g> veces. "\n\n"Vuelve a intentarlo en <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Has ingresado tu PIN de manera incorrecta <xliff:g id="NUMBER_0">%d</xliff:g> veces. "\n\n"Vuelve a intentarlo en <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Has extraído incorrectamente tu patrón de desbloqueo <xliff:g id="NUMBER_0">%d</xliff:g> veces. Luego de <xliff:g id="NUMBER_1">%d</xliff:g> intentos incorrectos, se te solicitará que desbloquees tu teléfono al iniciar sesión en Google. "\n\n" Vuelve a intentarlo en <xliff:g id="NUMBER_2">%d</xliff:g> segundos."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Vuelve a intentarlo en <xliff:g id="NUMBER">%d</xliff:g> segundos."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"¿Olvidaste el patrón?"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Borrar"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"No hay notificaciones"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Continuo"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Notificaciones"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"Cargando..."</string>
- <string name="battery_low_title" msgid="7923774589611311406">"Conecta el cargador"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"Hay poca batería:"</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"Restan <xliff:g id="NUMBER">%d%%</xliff:g> o menos."</string>
- <string name="battery_low_why" msgid="7279169609518386372">"Uso de la batería"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Error en la prueba de fábrica"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"La acción FACTORY_TEST se admite solamente en paquetes instalados en /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"No se ha encontrado ningún paquete que proporcione la acción FACTORY_TEST ."</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"Seleccionar todos"</string>
- <string name="selectText" msgid="3889149123626888637">"Seleccionar texto"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"Detener la selección de texto"</string>
<string name="cut" msgid="3092569408438626261">"Cortar"</string>
- <string name="cutAll" msgid="2436383270024931639">"Cortar llamada"</string>
<string name="copy" msgid="2681946229533511987">"Copiar"</string>
- <string name="copyAll" msgid="2590829068100113057">"Copiar todo"</string>
<string name="paste" msgid="5629880836805036433">"Pegar"</string>
<string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"Agregar \"<xliff:g id="WORD">%s</xliff:g>\" al diccionario"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"Editar texto"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"Poco espacio de almacenamiento"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"Hay poco espacio de almacenamiento en el teléfono."</string>
<string name="ok" msgid="5970060430562524910">"Aceptar"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"Aceptar"</string>
<string name="no" msgid="5141531044935541497">"Cancelar"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"Atención"</string>
+ <string name="loading" msgid="1760724998928255250">"Cargando..."</string>
<string name="capital_on" msgid="1544682755514494298">"Encendido"</string>
<string name="capital_off" msgid="6815870386972805832">"APAGADO"</string>
<string name="whichApplication" msgid="4533185947064773386">"Completar la acción mediante"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"Provocar acercamiento"</string>
<string name="report" msgid="4060218260984795706">"Notificar"</string>
<string name="wait" msgid="7147118217226317732">"Espera"</string>
+ <string name="smv_application" msgid="295583804361236288">"La aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> (proceso <xliff:g id="PROCESS">%2$s</xliff:g>) ha violado su política StrictMode autoimpuesta."</string>
+ <string name="smv_process" msgid="5120397012047462446">"El proceso <xliff:g id="PROCESS">%1$s</xliff:g> ha violado su política StrictMode autoimpuesta."</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> Correr"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Selecciona cambiar la aplicación"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"¿Deseas cambiar aplicaciones?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Ya se está ejecutando una aplicación que debe detenerse antes de iniciar una nueva."</string>
+ <string name="old_app_action" msgid="493129172238566282">"Regresar a <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="old_app_description" msgid="942967900237208466">"No inicies la nueva aplicación."</string>
+ <string name="new_app_action" msgid="5472756926945440706">"Inicio <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"Interrumpe la aplicación anterior sin guardar."</string>
<string name="sendText" msgid="5132506121645618310">"Selecciona una acción para el texto"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Volumen del timbre"</string>
<string name="volume_music" msgid="5421651157138628171">"Volumen de los medios"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Clave previamente compartida según L2TP/IPSec VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certificado según L2TP/IPSec VPN"</string>
<string name="upload_file" msgid="2897957172366730416">"Elegir archivo"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"No se seleccionó un archivo."</string>
<string name="reset" msgid="2448168080964209908">"Restablecer"</string>
<string name="submit" msgid="1602335572089911941">"Enviar"</string>
- <string name="description_star" msgid="2654319874908576133">"favorito"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Modo auto habilitado"</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"Seleccionar para salir del modo auto"</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"Anclaje a red o zona activa conectados"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"Tocar para configurar"</string>
+ <string name="back_button_label" msgid="2300470004503343439">"Atrás"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"Siguiente"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"Amplia utilización de datos móviles"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"Toca para obtener más información acerca de la utilización de datos móviles."</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"Límite de datos móviles excedido "</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 3cb068e..8568211 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Buzón de voz"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"Se ha producido un problema de conexión o el código MMI no es válido."</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"La operación solo es válida para números de marcación fija."</string>
<string name="serviceEnabled" msgid="8147278346414714315">"El servicio se ha habilitado."</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"Se ha habilitado el servicio para:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"El servicio se ha inhabilitado."</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"Acceder a la tarjeta SD"</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"inhabilitar o modificar la barra de estado"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"Permite que las aplicaciones inhabiliten la barra de estado, o añadan y eliminen iconos del sistema."</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"barra de estado"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"La aplicación puede aparecer en la barra de estado."</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"expandir/contraer la barra de estado"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Permite que la aplicación expanda y contraiga la barra de estado."</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"interceptar llamadas salientes"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Permite que la aplicación modifique la configuración de audio global como, por ejemplo, el volumen y la salida."</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"grabar sonido"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"Permite que la aplicación acceda a la ruta de grabación de audio."</string>
- <string name="permlab_camera" msgid="8059288807274039014">"realizar fotografías"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"Permite que la aplicación realice fotografías con la cámara. De esta forma, la aplicación puede recopilar en cualquier momento las imágenes que ve la cámara."</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"realizar fotografías y vídeos"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"Permite que la aplicación realice fotografías y vídeos con la cámara. De este modo, puede recopilar en cualquier momento las imágenes que ve la cámara."</string>
<string name="permlab_brick" msgid="8337817093326370537">"inhabilitar el teléfono de forma permanente"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"Permite que la aplicación inhabilite todas las funciones del teléfono de forma permanente. Este permiso es muy peligroso."</string>
<string name="permlab_reboot" msgid="2898560872462638242">"forzar reinicio del teléfono"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"Permite que la aplicación controle la función de vibración."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"controlar linterna"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"Permite que la aplicación controle la función de linterna."</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"acceso a dispositivos USB"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"La aplicación puede acceder a dispositivos USB."</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"probar hardware"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"Permite que la aplicación controle distintos periféricos con fines de prueba del hardware."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"llamar directamente a números de teléfono"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"Permite controlar el momento de bloqueo del dispositivo solicitando al usuario que vuelva a introducir la contraseña."</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"Borrar todos los datos"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"Permite realizar un restablecimiento de fábrica eliminando todos los datos sin confirmación."</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Casa"</item>
<item msgid="869923650527136615">"Móvil"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"Trabajo"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Otra"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Personalizada"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"a través de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> a través de <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Introduce el código PIN"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Introducir contraseña para desbloquear"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Introducir PIN para desbloquear"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"El código PIN es incorrecto."</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear el teléfono, pulsa la tecla de menú y, a continuación, pulsa 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emergencia"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"Volver a llamada"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Correcto"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Inténtalo de nuevo"</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Inténtalo de nuevo"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Cargando (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Cargado"</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"Introduce el código PIN."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Desbloqueando tarjeta SIM..."</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Has realizado <xliff:g id="NUMBER_0">%d</xliff:g> intentos fallidos de creación de un patrón de desbloqueo. "\n\n"Inténtalo de nuevo dentro de <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Has introducido una contraseña incorrecta <xliff:g id="NUMBER_0">%d</xliff:g> veces. "\n\n"Inténtalo de nuevo dentro de <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Has introducido un PIN incorrecto <xliff:g id="NUMBER_0">%d</xliff:g> veces. "\n\n"Inténtalo de nuevo dentro de <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Has realizado <xliff:g id="NUMBER_0">%d</xliff:g> intentos fallidos de creación del patrón de desbloqueo. Si realizas <xliff:g id="NUMBER_1">%d</xliff:g> intentos fallidos más, se te pedirá que desbloquees el teléfono con tus credenciales de acceso de Google."\n\n" Espera <xliff:g id="NUMBER_2">%d</xliff:g> segundos e inténtalo de nuevo."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Espera <xliff:g id="NUMBER">%d</xliff:g> segundos y vuelve a intentarlo."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"¿Has olvidado el patrón?"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Borrar"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"No tienes notificaciones"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Entrante"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Notificaciones"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"Cargando..."</string>
- <string name="battery_low_title" msgid="7923774589611311406">"Conecta el cargador"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"Se está agotando la batería:"</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> o menos disponible"</string>
- <string name="battery_low_why" msgid="7279169609518386372">"Uso de la batería"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Fallo en la prueba de fábrica"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"La acción FACTORY_TEST solo es compatible con los paquetes instalados en /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"No se ha encontrado ningún paquete que proporcione la acción FACTORY_TEST."</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"Seleccionar todo"</string>
- <string name="selectText" msgid="3889149123626888637">"Seleccionar texto"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"Detener selección de texto"</string>
<string name="cut" msgid="3092569408438626261">"Cortar"</string>
- <string name="cutAll" msgid="2436383270024931639">"Cortar todo"</string>
<string name="copy" msgid="2681946229533511987">"Copiar"</string>
- <string name="copyAll" msgid="2590829068100113057">"Copiar todo"</string>
<string name="paste" msgid="5629880836805036433">"Pegar"</string>
<string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"Método de introducción de texto"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"Añadir \"<xliff:g id="WORD">%s</xliff:g>\" al diccionario"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"Editar texto"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"Poco espacio"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"Se está agotando el espacio de almacenamiento del teléfono."</string>
<string name="ok" msgid="5970060430562524910">"Aceptar"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"Aceptar"</string>
<string name="no" msgid="5141531044935541497">"Cancelar"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"Atención"</string>
+ <string name="loading" msgid="1760724998928255250">"Cargando…"</string>
<string name="capital_on" msgid="1544682755514494298">"Activado"</string>
<string name="capital_off" msgid="6815870386972805832">"Desconectado"</string>
<string name="whichApplication" msgid="4533185947064773386">"Completar acción utilizando"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"Forzar cierre"</string>
<string name="report" msgid="4060218260984795706">"Informe"</string>
<string name="wait" msgid="7147118217226317732">"Esperar"</string>
+ <string name="smv_application" msgid="295583804361236288">"La aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> (proceso <xliff:g id="PROCESS">%2$s</xliff:g>) ha infringido su política StrictMode autoaplicable."</string>
+ <string name="smv_process" msgid="5120397012047462446">"El proceso <xliff:g id="PROCESS">%1$s</xliff:g> ha infringido su política StrictMode autoaplicable."</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> en ejecución"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Seleccionar para cambiar a la aplicación"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"¿Cambiar de aplicación?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Se está ejecutando otra aplicación. Para iniciar una aplicación nueva debes detenerla."</string>
+ <string name="old_app_action" msgid="493129172238566282">"Volver a <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="old_app_description" msgid="942967900237208466">"No iniciar la aplicación nueva"</string>
+ <string name="new_app_action" msgid="5472756926945440706">"Iniciar <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"Detener la aplicación anterior sin guardar"</string>
<string name="sendText" msgid="5132506121645618310">"Seleccionar la opción para compartir"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Volumen del timbre"</string>
<string name="volume_music" msgid="5421651157138628171">"Volumen multimedia"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Red privada virtual L2TP/IPSec basada en clave compartida previamente"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Red privada virtual L2TP/IPSec basada en certificado"</string>
<string name="upload_file" msgid="2897957172366730416">"Seleccionar archivo"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"Archivo no seleccionado"</string>
<string name="reset" msgid="2448168080964209908">"Restablecer"</string>
<string name="submit" msgid="1602335572089911941">"Enviar"</string>
- <string name="description_star" msgid="2654319874908576133">"favoritos"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Se ha habilitado el modo coche."</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"Selecciona esta opción para salir del modo coche."</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"Anclaje a red activo o zona Wi-Fi"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"Toca para iniciar la configuración."</string>
+ <string name="back_button_label" msgid="2300470004503343439">"Atrás"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"Siguiente"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"Uso elevado datos móviles"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"Más información sobre uso de datos"</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"Límite datos superado"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 5efcf32..c2dd90b 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Messagerie vocale"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"Problème de connexion ou code IHM non valide."</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"Opération réservée aux numéros autorisés"</string>
<string name="serviceEnabled" msgid="8147278346414714315">"Le service a été activé."</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"Ce service a été activé pour :"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"Ce service a été désactivé."</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"Accès à la carte SD"</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"Désactivation ou modification de la barre d\'état"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"Permet à une application de désactiver la barre d\'état ou d\'ajouter/supprimer des icônes système."</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"barre d\'état"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"Permet à l\'application de faire office de barre d\'état."</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"Agrandir/réduire la barre d\'état"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Permet à l\'application de réduire ou d\'agrandir la barre d\'état."</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"Interception d\'appels sortants"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Permet à l\'application de modifier les paramètres audio généraux (p. ex. le volume et le routage)."</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"Enregistrement de fichier audio"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"Permet à l\'application d\'accéder au chemin de l\'enregistrement audio."</string>
- <string name="permlab_camera" msgid="8059288807274039014">"Prise de photos"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"Permet à l\'application de prendre des clichés avec l\'appareil photo. Cette fonctionnalité permet à l\'application de récupérer à tout moment les images perçues par l\'appareil."</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"prendre des photos et enregistrer des vidéos"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"Permet de prendre des photos et d\'enregistrer des vidéos avec l\'appareil photo. Cette fonctionnalité permet à l\'application de récupérer à tout moment les images perçues par l\'appareil."</string>
<string name="permlab_brick" msgid="8337817093326370537">"Désactivation définitive du téléphone"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"Permet à l\'application de désactiver définitivement le téléphone. Cette fonctionnalité est très dangereuse."</string>
<string name="permlab_reboot" msgid="2898560872462638242">"Redémarrage forcé du téléphone"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"Permet à l\'application de contrôler le vibreur."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"Contrôle de la lampe de poche"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"Permet à l\'application de contrôler la lampe de poche."</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"accéder aux périphériques USB"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"Autorise l\'application à accéder aux périphériques USB"</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"Tests du matériel"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"Permet à l\'application de contrôler différents périphériques à des fins de test matériel."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"Appel direct des numéros de téléphone"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"Contrôle le verrouillage du périphérique, avec obligation de saisir à nouveau le mot de passe."</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"Effacer toutes les données"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"Rétablit les paramètres d\'usine, supprimant toutes vos données sans demande de confirmation."</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Domicile"</item>
<item msgid="869923650527136615">"Portable"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"Bureau"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Autre"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Personnalisé"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Saisissez le code PIN"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Saisissez le mot de passe pour procéder au déverrouillage."</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Saisissez le code PIN pour procéder au déverrouillage."</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Le code PIN est incorrect !"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Pour débloquer le clavier, appuyez sur \"Menu\" puis sur 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Numéro d\'urgence"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"Retour à l\'appel"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Combinaison correcte !"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Incorrect. Merci de réessayer."</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Incorrect. Merci de réessayer."</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Chargement (<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Chargé"</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"La carte SIM est verrouillée."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Déblocage de la carte SIM..."</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Vous avez mal reproduit le schéma de déverrouillage <xliff:g id="NUMBER_0">%d</xliff:g> fois. "\n\n"Veuillez réessayer dans <xliff:g id="NUMBER_1">%d</xliff:g> secondes."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Vous avez saisi un mot de passe incorrect à <xliff:g id="NUMBER_0">%d</xliff:g> reprises. "\n\n"Veuillez réessayer dans <xliff:g id="NUMBER_1">%d</xliff:g> secondes."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Vous avez saisi un code PIN incorrect à <xliff:g id="NUMBER_0">%d</xliff:g> reprises. "\n\n"Veuillez réessayer dans <xliff:g id="NUMBER_1">%d</xliff:g> secondes."</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Vous avez mal saisi le schéma de déverrouillage <xliff:g id="NUMBER_0">%d</xliff:g> fois. Au bout de <xliff:g id="NUMBER_1">%d</xliff:g> tentatives supplémentaires, vous devrez débloquer votre téléphone à l\'aide de votre identifiant Google."\n\n"Merci de réessayer dans <xliff:g id="NUMBER_2">%d</xliff:g> secondes."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Réessayez dans <xliff:g id="NUMBER">%d</xliff:g> secondes."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Schéma oublié ?"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Effacer"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Aucune notification"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"En cours"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Notifications"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"Chargement..."</string>
- <string name="battery_low_title" msgid="7923774589611311406">"Branchez le chargeur"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"Le niveau de la batterie est bas :"</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"Maximum <xliff:g id="NUMBER">%d%%</xliff:g> restants."</string>
- <string name="battery_low_why" msgid="7279169609518386372">"Utilisation de la batterie"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Échec du test usine"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"L\'action FACTORY_TEST est uniquement prise en charge pour les paquets de données installés dans in/system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Impossible de trouver un paquet proposant l\'action FACTORY_TEST."</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"Tout sélectionner"</string>
- <string name="selectText" msgid="3889149123626888637">"Sélectionner le texte"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"Arrêter sélection de texte"</string>
<string name="cut" msgid="3092569408438626261">"Couper"</string>
- <string name="cutAll" msgid="2436383270024931639">"Tout couper"</string>
<string name="copy" msgid="2681946229533511987">"Copier"</string>
- <string name="copyAll" msgid="2590829068100113057">"Tout copier"</string>
<string name="paste" msgid="5629880836805036433">"Coller"</string>
<string name="copyUrl" msgid="2538211579596067402">"Copier l\'URL"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"Mode de saisie"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"Ajouter \"<xliff:g id="WORD">%s</xliff:g>\" au dictionnaire"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"Modifier le texte"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"Espace disponible faible"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"La mémoire du téléphone commence à être pleine."</string>
<string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"OK"</string>
<string name="no" msgid="5141531044935541497">"Annuler"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"Attention"</string>
+ <string name="loading" msgid="1760724998928255250">"Chargement en cours..."</string>
<string name="capital_on" msgid="1544682755514494298">"ON"</string>
<string name="capital_off" msgid="6815870386972805832">"OFF"</string>
<string name="whichApplication" msgid="4533185947064773386">"Continuer avec"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"Forcer la fermeture"</string>
<string name="report" msgid="4060218260984795706">"Rapport"</string>
<string name="wait" msgid="7147118217226317732">"Attendre"</string>
+ <string name="smv_application" msgid="295583804361236288">"L\'application <xliff:g id="APPLICATION">%1$s</xliff:g> (processus <xliff:g id="PROCESS">%2$s</xliff:g>) a enfreint ses propres règles StrictMode."</string>
+ <string name="smv_process" msgid="5120397012047462446">"Le processus <xliff:g id="PROCESS">%1$s</xliff:g> a enfreint ses propres règles StrictMode."</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> en cours d\'exécution"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Sélectionner pour changer d\'application"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Passer d\'une application à l\'autre ?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Une autre application est déjà en cours d\'exécution. Veuillez l\'arrêter avant d\'en démarrer une nouvelle."</string>
+ <string name="old_app_action" msgid="493129172238566282">"Revenir à <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="old_app_description" msgid="942967900237208466">"Ne pas démarrer la nouvelle application"</string>
+ <string name="new_app_action" msgid="5472756926945440706">"Démarrer <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"Arrêtez l\'ancienne application sans enregistrer."</string>
<string name="sendText" msgid="5132506121645618310">"Sélectionner une action pour le texte"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Volume de la sonnerie"</string>
<string name="volume_music" msgid="5421651157138628171">"Volume"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"VPN L2TP/IPSec basé sur une clé pré-partagée"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"VPN L2TP/IPSec basé sur un certificat"</string>
<string name="upload_file" msgid="2897957172366730416">"Sélectionner un fichier"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"Aucun fichier sélectionné"</string>
<string name="reset" msgid="2448168080964209908">"Réinitialiser"</string>
<string name="submit" msgid="1602335572089911941">"Envoyer"</string>
- <string name="description_star" msgid="2654319874908576133">"favori"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Mode Voiture activé"</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"Sélectionnez cette option pour quitter le mode Voiture."</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"Partage de connexion ou point d\'accès sans fil activé"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"Toucher pour configurer"</string>
+ <string name="back_button_label" msgid="2300470004503343439">"Retour"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"Suivant"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"Utilisation élevée des données mobiles"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"Touchez pour en savoir plus sur l\'utilisation des données mobiles"</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"Quota d\'utilisation des données mobiles dépassé"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index d0b3b8a..7870ccf 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Segreteria"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"Problema di connessione o codice MMI non valido."</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"Operazione limitata solo ai numeri di selezione fissa."</string>
<string name="serviceEnabled" msgid="8147278346414714315">"Il servizio è stato attivato."</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"Il servizio è stato attivato per:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"Il servizio è stato disattivato."</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"Accesso alla scheda SD."</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"disattivare o modificare la barra di stato"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"Consente all\'applicazione di disattivare la barra di stato o di aggiungere e rimuovere icone di sistema."</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"barra di stato"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"Consente di visualizzare l\'applicazione nella barra di stato."</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"espansione/compressione barra di stato"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Consente all\'applicazione di espandere o comprimere la barra di stato."</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"intercettazione chiamate in uscita"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Consente all\'applicazione di modificare impostazioni audio globali come volume e routing."</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"registrazione audio"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"Consente l\'accesso dell\'applicazione al percorso di registrazione dell\'audio."</string>
- <string name="permlab_camera" msgid="8059288807274039014">"acquisizione foto"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"Consente di scattare foto nell\'applicazione con la fotocamera. L\'applicazione può acquisire in qualsiasi momento le immagini rilevate dalla fotocamera."</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"acquisizione di foto e video"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"Consente di scattare foto e riprendere video nell\'applicazione con la fotocamera. L\'applicazione può acquisire in qualsiasi momento le immagini rilevate dalla fotocamera."</string>
<string name="permlab_brick" msgid="8337817093326370537">"disattivazione telefono"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"Consente all\'applicazione di disattivare l\'intero telefono in modo definitivo. Questa autorizzazione è molto pericolosa."</string>
<string name="permlab_reboot" msgid="2898560872462638242">"riavvio forzato del telefono"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"Consente all\'applicazione di controllare la vibrazione."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"controllo flash"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"Consente all\'applicazione di controllare il flash."</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"accesso a dispositivi USB"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"Consente all\'applicazione di accedere ai dispositivi USB"</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"esecuzione test hardware"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"Consente all\'applicazione di controllare varie periferiche per il test dell\'hardware."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"chiamata diretta n. telefono"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"Controlla quando il dispositivo si blocca, chiedendoti di reinserire la password."</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"Cancella tutti i dati"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"Esegui un ripristino di fabbrica, eliminando tutti i tuoi dati senza conferma."</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Casa"</item>
<item msgid="869923650527136615">"Cellulare"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"Ufficio"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Altro"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Personalizzato"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"tramite <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> tramite <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Inserisci il PIN"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Inserisci password per sbloccare"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Inserisci PIN per sbloccare"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Codice PIN errato."</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Per sbloccare, premi Menu, poi 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Numero di emergenza"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"Torna a chiamata"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Corretta."</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Riprova"</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Riprova"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"In carica (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Carico."</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"La SIM è bloccata."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Sblocco SIM..."</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"<xliff:g id="NUMBER_0">%d</xliff:g> tentativi di inserimento della sequenza di sblocco. "\n\n"Riprova fra <xliff:g id="NUMBER_1">%d</xliff:g> secondi."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"<xliff:g id="NUMBER_0">%d</xliff:g> tentativi errati di inserimento della password. "\n\n"Riprova tra <xliff:g id="NUMBER_1">%d</xliff:g> secondi."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"<xliff:g id="NUMBER_0">%d</xliff:g> tentativi errati di inserimento del PIN. "\n\n"Riprova tra <xliff:g id="NUMBER_1">%d</xliff:g> secondi."</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"<xliff:g id="NUMBER_0">%d</xliff:g> tentativi di inserimento della sequenza di sblocco. Dopo altri <xliff:g id="NUMBER_1">%d</xliff:g> tentativi falliti, ti verrà chiesto di sbloccare il telefono tramite i dati di accesso di Google."\n\n"Riprova fra <xliff:g id="NUMBER_2">%d</xliff:g> secondi."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Riprova fra <xliff:g id="NUMBER">%d</xliff:g> secondi."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Hai dimenticato la sequenza?"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Cancella"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Nessuna notifica"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"In corso"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Notifiche"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"In carica..."</string>
- <string name="battery_low_title" msgid="7923774589611311406">"Collegare il caricabatterie"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"Batteria quasi scarica:"</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> rimanente o meno."</string>
- <string name="battery_low_why" msgid="7279169609518386372">"Utilizzo batteria"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Test di fabbrica non riuscito"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"L\'azione FACTORY_TEST è supportata soltanto per i pacchetti installati in /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Nessun pacchetto trovato che fornisca l\'azione FACTORY_TEST."</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"Seleziona tutto"</string>
- <string name="selectText" msgid="3889149123626888637">"Seleziona testo"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"Termina selezione testo"</string>
<string name="cut" msgid="3092569408438626261">"Taglia"</string>
- <string name="cutAll" msgid="2436383270024931639">"Taglia tutto"</string>
<string name="copy" msgid="2681946229533511987">"Copia"</string>
- <string name="copyAll" msgid="2590829068100113057">"Copia tutto"</string>
<string name="paste" msgid="5629880836805036433">"Incolla"</string>
<string name="copyUrl" msgid="2538211579596067402">"Copia URL"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"Metodo inserimento"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"Aggiungi \"<xliff:g id="WORD">%s</xliff:g>\" al dizionario"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"Modifica testo"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"Spazio in esaurimento"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"Spazio di archiviazione del telefono in esaurimento."</string>
<string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"OK"</string>
<string name="no" msgid="5141531044935541497">"Annulla"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"Attenzione"</string>
+ <string name="loading" msgid="1760724998928255250">"Caricamento in corso..."</string>
<string name="capital_on" msgid="1544682755514494298">"ON"</string>
<string name="capital_off" msgid="6815870386972805832">"OFF"</string>
<string name="whichApplication" msgid="4533185947064773386">"Completa l\'azione con"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"Termina"</string>
<string name="report" msgid="4060218260984795706">"Segnala"</string>
<string name="wait" msgid="7147118217226317732">"Attendi"</string>
+ <string name="smv_application" msgid="295583804361236288">"L\'applicazione <xliff:g id="APPLICATION">%1$s</xliff:g> (processo <xliff:g id="PROCESS">%2$s</xliff:g>) ha violato la norma StrictMode autoimposta."</string>
+ <string name="smv_process" msgid="5120397012047462446">"Il processo <xliff:g id="PROCESS">%1$s</xliff:g> ha violato la norma StrictMode autoimposta."</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> in esecuzione"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Seleziona per passare all\'applicazione"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Scambiare le applicazioni?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Un\'altra applicazione già in esecuzione deve essere chiusa prima di poterne avviare un\'altra."</string>
+ <string name="old_app_action" msgid="493129172238566282">"Torna a <xliff:g id="OLD_APP">%1$s</xliff:g> "</string>
+ <string name="old_app_description" msgid="942967900237208466">"Non avviare la nuova applicazione."</string>
+ <string name="new_app_action" msgid="5472756926945440706">"Avvia <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"Interrompi la vecchia applicazione senza salvare."</string>
<string name="sendText" msgid="5132506121645618310">"Selezione un\'opzione di invio"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Volume suoneria"</string>
<string name="volume_music" msgid="5421651157138628171">"Volume app. multimediali"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"VPN L2TP/IPSec basata su chiave precondivisa"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"VPN L2TP/IPSec basata su certificato"</string>
<string name="upload_file" msgid="2897957172366730416">"Scegli file"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"Nessun file è stato scelto"</string>
<string name="reset" msgid="2448168080964209908">"Reimposta"</string>
<string name="submit" msgid="1602335572089911941">"Invia"</string>
- <string name="description_star" msgid="2654319874908576133">"preferiti"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Modalità automobile attivata"</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"Seleziona per uscire dalla modalità automobile."</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"Tethering oppure hotspot attivo"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"Tocca per configurare"</string>
+ <string name="back_button_label" msgid="2300470004503343439">"Indietro"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"Avanti"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"Utilizzo dati cell. elevato"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"Tocca per informazioni sull\'utilizzo dati cell."</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"Limite dati cell. superato"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 5816ef7..a0b7e0e 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"ボイスメール"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"接続に問題があるか、MMIコードが正しくありません。"</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"発信番号制限で指定された番号に対してのみ操作できます。"</string>
<string name="serviceEnabled" msgid="8147278346414714315">"サービスが有効になりました。"</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"次のサービスが有効になりました:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"サービスが無効になりました。"</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"SDカードにアクセスします。"</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"ステータスバーの無効化や変更"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"ステータスバーの無効化やシステムアイコンの追加や削除をアプリケーションに許可します。"</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"ステータスバーへの表示"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"アプリケーションのステータスバーへの表示を許可します。"</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"ステータスバーの拡大/縮小"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"ステータスバーの拡大や縮小をアプリケーションに許可します。"</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"発信の傍受"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"音量や転送などの音声全般の設定の変更をアプリケーションに許可します。"</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"録音"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"オーディオ録音パスへのアクセスをアプリケーションに許可します。"</string>
- <string name="permlab_camera" msgid="8059288807274039014">"写真の撮影"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"カメラでの写真撮影をアプリケーションに許可します。アプリケーションはカメラから画像をいつでも収集できます。"</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"写真と動画の撮影"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"カメラでの写真と動画の撮影をアプリケーションに許可します。許可すると、カメラがとらえている画像をアプリケーションがいつでも取得できるようになります。"</string>
<string name="permlab_brick" msgid="8337817093326370537">"端末を永続的に無効にする"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"携帯電話全体を永続的に無効にすることをアプリケーションに許可します。この許可は非常に危険です。"</string>
<string name="permlab_reboot" msgid="2898560872462638242">"端末の再起動"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"バイブレーションの制御をアプリケーションに許可します。"</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"ライトのコントロール"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"ライトの制御をアプリケーションに許可します。"</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"USBデバイスへのアクセス"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"USBデバイスへのアクセスをアプリケーションに許可します。"</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"ハードウェアのテスト"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"ハードウェアのテストのためにさまざまな周辺機器を制御することをアプリケーションに許可します。"</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"電話番号発信"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"携帯電話のロック時を管理します。パスワードの再入力が必要となります。"</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"すべてのデータを消去"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"出荷時設定にリセットします。確認なしでデータがすべて削除されます。"</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"自宅"</item>
<item msgid="869923650527136615">"携帯"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"勤務先"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"その他"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"カスタム"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"<xliff:g id="SOURCE">%1$s</xliff:g>経由"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g>、更新元: <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PINコードを入力"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"ロックを解除するにはパスワードを入力"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"ロックを解除するにはPINを入力"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PINコードが正しくありません。"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"MENU、0キーでロック解除"</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"緊急通報番号"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"通話に戻る"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"一致しました"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"やり直してください"</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"やり直してください"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"充電中(<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"充電完了。"</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIMカードはロックされています。"</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"SIMカードのロック解除中..."</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"ロック解除のパターンは<xliff:g id="NUMBER_0">%d</xliff:g>回とも正しく指定されていません。"\n\n"<xliff:g id="NUMBER_1">%d</xliff:g>秒後にもう一度指定してください。"</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"入力したパスワードは<xliff:g id="NUMBER_0">%d</xliff:g>回とも正しくありませんでした。"\n\n"<xliff:g id="NUMBER_1">%d</xliff:g>秒後にもう一度入力してください。"</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"入力したPINは<xliff:g id="NUMBER_0">%d</xliff:g>回とも正しくありませんでした。"\n\n"<xliff:g id="NUMBER_1">%d</xliff:g>秒後にもう一度入力してください。"</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"指定したパターンは<xliff:g id="NUMBER_0">%d</xliff:g>回とも正しくありません。あと<xliff:g id="NUMBER_1">%d</xliff:g>回指定に失敗すると、携帯電話のロックの解除にGoogleへのログインが必要になります。"\n\n"<xliff:g id="NUMBER_2">%d</xliff:g>秒後にもう一度指定してください。"</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"<xliff:g id="NUMBER">%d</xliff:g>秒後にやり直してください。"</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"パターンを忘れた場合"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"通知を消去"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"通知なし"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"実行中"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"通知"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"充電中..."</string>
- <string name="battery_low_title" msgid="7923774589611311406">"充電してください"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"電池が残り少なくなっています:"</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"残り<xliff:g id="NUMBER">%d%%</xliff:g>未満です。"</string>
- <string name="battery_low_why" msgid="7279169609518386372">"電池使用量"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"出荷時試験が失敗"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST操作は、/system/appにインストールされたパッケージのみが対象です。"</string>
<string name="factorytest_no_action" msgid="872991874799998561">"FACTORY_TEST操作を行うパッケージは見つかりませんでした。"</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"すべて選択"</string>
- <string name="selectText" msgid="3889149123626888637">"テキストを選択"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"テキストの選択を終了"</string>
<string name="cut" msgid="3092569408438626261">"切り取り"</string>
- <string name="cutAll" msgid="2436383270024931639">"すべて切り取り"</string>
<string name="copy" msgid="2681946229533511987">"コピー"</string>
- <string name="copyAll" msgid="2590829068100113057">"すべてコピー"</string>
<string name="paste" msgid="5629880836805036433">"貼り付け"</string>
<string name="copyUrl" msgid="2538211579596067402">"URLをコピー"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"入力方法"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"辞書に「<xliff:g id="WORD">%s</xliff:g>」を追加"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"テキストを編集"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"空き容量低下"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"携帯電話の空き容量が少なくなっています。"</string>
<string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"OK"</string>
<string name="no" msgid="5141531044935541497">"キャンセル"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"注意"</string>
+ <string name="loading" msgid="1760724998928255250">"読み込み中..."</string>
<string name="capital_on" msgid="1544682755514494298">"ON"</string>
<string name="capital_off" msgid="6815870386972805832">"OFF"</string>
<string name="whichApplication" msgid="4533185947064773386">"アプリケーションを選択"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"強制終了"</string>
<string name="report" msgid="4060218260984795706">"レポート"</string>
<string name="wait" msgid="7147118217226317732">"待機"</string>
+ <string name="smv_application" msgid="295583804361236288">"アプリケーション<xliff:g id="APPLICATION">%1$s</xliff:g>(プロセス<xliff:g id="PROCESS">%2$s</xliff:g>)でStrictModeポリシー違反がありました。"</string>
+ <string name="smv_process" msgid="5120397012047462446">"プロセス<xliff:g id="PROCESS">%1$s</xliff:g>でStrictModeポリシー違反がありました。"</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g>を実行中"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"アプリケーションを切り替える"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"アプリケーションを切り替えますか?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"別のアプリケーションが既に実行中です。新しいアプリケーションを起動する前に実行中のアプリケーションを停止してください。"</string>
+ <string name="old_app_action" msgid="493129172238566282">"<xliff:g id="OLD_APP">%1$s</xliff:g>に戻る"</string>
+ <string name="old_app_description" msgid="942967900237208466">"新しいアプリケーションを起動しません。"</string>
+ <string name="new_app_action" msgid="5472756926945440706">"<xliff:g id="OLD_APP">%1$s</xliff:g>を起動"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"実行中のアプリケーションを保存せずに停止します。"</string>
<string name="sendText" msgid="5132506121645618310">"アプリケーションを選択"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"着信音量"</string>
<string name="volume_music" msgid="5421651157138628171">"メディアの音量"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPNベースの事前共有鍵"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"L2TP/IPSec VPNベースの証明書"</string>
<string name="upload_file" msgid="2897957172366730416">"ファイルを選択"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"ファイルが選択されていません"</string>
<string name="reset" msgid="2448168080964209908">"リセット"</string>
<string name="submit" msgid="1602335572089911941">"送信"</string>
- <string name="description_star" msgid="2654319874908576133">"お気に入り"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"運転モードを有効にする"</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"運転モードを終了するには選択してください。"</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"テザリングまたはアクセスポイントが有効です"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"タップして設定する"</string>
+ <string name="back_button_label" msgid="2300470004503343439">"戻る"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"次へ"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"モバイルデータの使用量が増えています"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"タップしてモバイルデータ利用の詳細を表示します"</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"モバイルデータの制限を超えました"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 8aaf761..bffb034 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"음성메일"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"연결에 문제가 있거나 MMI 코드가 잘못되었습니다."</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"발신 허용 번호에서만 수행할 수 있는 작업입니다."</string>
<string name="serviceEnabled" msgid="8147278346414714315">"서비스를 사용하도록 설정했습니다."</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"사용 설정된 서비스 목록:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"서비스가 사용 중지되었습니다."</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"SD 카드에 액세스합니다."</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"상태 표시줄 사용 중지 또는 수정"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"애플리케이션이 상태 표시줄을 사용 중지하거나 시스템 아이콘을 추가 및 제거할 수 있도록 합니다."</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"상태 표시줄"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"애플리케이션이 상태 표시줄이 되도록 허용합니다."</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"상태 표시줄 확장/축소"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"애플리케이션이 상태 표시줄을 확장하거나 축소할 수 있도록 합니다."</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"발신전화 가로채기"</string>
@@ -195,8 +198,8 @@
<string name="permdesc_setDebugApp" msgid="5584310661711990702">"애플리케이션이 다른 애플리케이션에 대해 디버깅을 사용할 수 있도록 합니다. 이 경우 악성 애플리케이션이 다른 애플리케이션을 중지시킬 수 있습니다."</string>
<string name="permlab_changeConfiguration" msgid="8214475779521218295">"UI 설정 변경"</string>
<string name="permdesc_changeConfiguration" msgid="3465121501528064399">"애플리케이션이 로케일 또는 전체 글꼴 크기와 같은 현재 구성을 변경할 수 있도록 합니다."</string>
- <string name="permlab_enableCarMode" msgid="5684504058192921098">"차량 모드 사용"</string>
- <string name="permdesc_enableCarMode" msgid="5673461159384850628">"애플리케이션이 차량 모드를 사용할 수 있도록 합니다."</string>
+ <string name="permlab_enableCarMode" msgid="5684504058192921098">"운전모드 사용"</string>
+ <string name="permdesc_enableCarMode" msgid="5673461159384850628">"애플리케이션이 운전모드를 사용할 수 있도록 합니다."</string>
<string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"백그라운드 프로세스 종료"</string>
<string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"메모리가 부족하지 않은 경우에도 애플리케이션이 다른 애플리케이션의 백그라운드 프로세스를 중단할 수 있도록 합니다."</string>
<string name="permlab_forceStopPackages" msgid="1447830113260156236">"다른 애플리케이션 강제 종료"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"애플리케이션이 볼륨 및 경로 지정 같은 전체 오디오 설정을 수정할 수 있도록 합니다."</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"오디오 녹음"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"애플리케이션이 오디오 레코드 경로에 액세스할 수 있도록 합니다."</string>
- <string name="permlab_camera" msgid="8059288807274039014">"사진 촬영"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"애플리케이션이 카메라로 사진을 찍을 수 있도록 합니다. 이 경우 애플리케이션이 카메라에 보여지는 화면을 언제든지 수집할 수 있습니다."</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"사진과 동영상 찍기"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"애플리케이션이 카메라로 사진과 동영상을 찍을 수 있도록 합니다. 이 경우 애플리케이션이 카메라에 보여지는 화면을 언제든지 수집할 수 있습니다."</string>
<string name="permlab_brick" msgid="8337817093326370537">"휴대전화를 영구적으로 사용 중지"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"애플리케이션이 휴대전화를 영구적으로 사용 중지할 수 있게 합니다. 이 기능은 매우 위험합니다."</string>
<string name="permlab_reboot" msgid="2898560872462638242">"휴대전화 강제로 다시 부팅"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"애플리케이션이 진동을 제어할 수 있도록 합니다."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"카메라 플래시 제어"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"애플리케이션이 카메라 플래시를 제어할 수 있도록 합니다."</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"USB 장치 액세스"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"애플리케이션이 USB 장치에 액세스하도록 허용합니다."</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"하드웨어 테스트"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"애플리케이션이 하드웨어를 테스트할 목적으로 다양한 주변장치를 제어할 수 있도록 합니다."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"전화번호 자동 연결"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"기기가 잠겨 있을 때 작동하려면 비밀번호를 다시 입력해야 합니다."</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"모든 데이터 삭제"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"초기화를 수행하여 모든 데이터를 확인하지 않고 삭제합니다."</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"집"</item>
<item msgid="869923650527136615">"모바일"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"직장"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"기타"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"맞춤설정"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"<xliff:g id="SOURCE">%1$s</xliff:g>을(를) 통해"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g>(<xliff:g id="SOURCE">%2$s</xliff:g> 사용)"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN 코드 입력"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"잠금을 해제하려면 비밀번호 입력"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"잠금을 해제하려면 PIN 입력"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PIN 코드가 잘못되었습니다."</string>
<string name="keyguard_label_text" msgid="861796461028298424">"잠금해제하려면 메뉴를 누른 다음 0을 누릅니다."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"비상 전화번호"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"통화로 돌아가기"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"맞습니다."</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"죄송합니다. 다시 시도하세요."</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"죄송합니다. 다시 시도해 주세요."</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"충전 중(<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"충전되었습니다."</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM 카드가 잠겨 있습니다."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"SIM 카드 잠금해제 중..."</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"잠금해제 패턴을 <xliff:g id="NUMBER_0">%d</xliff:g>회 잘못 그렸습니다. "\n\n"<xliff:g id="NUMBER_1">%d</xliff:g>초 후에 다시 시도하세요."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"비밀번호를 <xliff:g id="NUMBER_0">%d</xliff:g>회 잘못 입력했습니다. "\n\n"<xliff:g id="NUMBER_1">%d</xliff:g>초 후에 다시 시도해 주세요."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"PIN을 <xliff:g id="NUMBER_0">%d</xliff:g>회 잘못 입력했습니다. "\n\n"<xliff:g id="NUMBER_1">%d</xliff:g>초 후에 다시 시도해 주세요."</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"잠금해제 패턴을 <xliff:g id="NUMBER_0">%d</xliff:g>회 잘못 그렸습니다. <xliff:g id="NUMBER_1">%d</xliff:g>회 더 실패하면 Google 로그인을 통해 휴대전화를 잠금해제하도록 요청됩니다. "\n\n" <xliff:g id="NUMBER_2">%d</xliff:g>초 후에 다시 시도하세요."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"<xliff:g id="NUMBER">%d</xliff:g>초 후에 다시 시도하세요."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"패턴을 잊으셨나요?"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="AMPM">%P</xliff:g> <xliff:g id="HOUR">%-l</xliff:g>:00"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="AMPM">%p</xliff:g> <xliff:g id="HOUR">%-l</xliff:g>:00"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"지우기"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"알림 없음"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"진행 중"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"알림"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"충전 중..."</string>
- <string name="battery_low_title" msgid="7923774589611311406">"충전기를 연결하세요."</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"배터리 전원이 부족합니다."</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"잔여 배터리가 <xliff:g id="NUMBER">%d%%</xliff:g> 이하입니다."</string>
- <string name="battery_low_why" msgid="7279169609518386372">"배터리 사용량"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"출고 테스트 불합격"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST 작업은 /system/app 디렉토리에 설치된 패키지에 대해서만 지원됩니다."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"FACTORY_TEST 작업을 제공하는 패키지가 없습니다."</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"모두 선택"</string>
- <string name="selectText" msgid="3889149123626888637">"텍스트 선택"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"텍스트 선택 중지"</string>
<string name="cut" msgid="3092569408438626261">"잘라내기"</string>
- <string name="cutAll" msgid="2436383270024931639">"모두 잘라내기"</string>
<string name="copy" msgid="2681946229533511987">"복사"</string>
- <string name="copyAll" msgid="2590829068100113057">"모두 복사"</string>
<string name="paste" msgid="5629880836805036433">"붙여넣기"</string>
<string name="copyUrl" msgid="2538211579596067402">"URL 복사"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"입력 방법"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"사전에 \'<xliff:g id="WORD">%s</xliff:g>\' 추가"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"텍스트 수정"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"저장공간 부족"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"휴대전화 저장공간이 부족합니다."</string>
<string name="ok" msgid="5970060430562524910">"확인"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"확인"</string>
<string name="no" msgid="5141531044935541497">"취소"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"주의"</string>
+ <string name="loading" msgid="1760724998928255250">"로드 중..."</string>
<string name="capital_on" msgid="1544682755514494298">"사용"</string>
<string name="capital_off" msgid="6815870386972805832">"사용 안함"</string>
<string name="whichApplication" msgid="4533185947064773386">"작업을 수행할 때 사용하는 애플리케이션"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"닫기"</string>
<string name="report" msgid="4060218260984795706">"신고"</string>
<string name="wait" msgid="7147118217226317732">"대기"</string>
+ <string name="smv_application" msgid="295583804361236288">"애플리케이션 <xliff:g id="APPLICATION">%1$s</xliff:g>(프로세스 <xliff:g id="PROCESS">%2$s</xliff:g>)이(가) 자체 시행 StrictMode 정책을 위반했습니다."</string>
+ <string name="smv_process" msgid="5120397012047462446">"프로세스(<xliff:g id="PROCESS">%1$s</xliff:g>)가 자체 시행 StrictMode 정책을 위반했습니다."</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> 실행 중"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"애플리케이션으로 전환하려면 선택"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"애플리케이션을 전환하시겠습니까?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"다른 애플리케이션이 이미 실행 중입니다. 새 애플리케이션을 시작하려면 실행 중인 애플리케이션을 중단해야 합니다."</string>
+ <string name="old_app_action" msgid="493129172238566282">"<xliff:g id="OLD_APP">%1$s</xliff:g>(으)로 돌아가기"</string>
+ <string name="old_app_description" msgid="942967900237208466">"새 애플리케이션을 시작하지 마세요."</string>
+ <string name="new_app_action" msgid="5472756926945440706">"<xliff:g id="OLD_APP">%1$s</xliff:g> 시작"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"저장하지 않고 이전 애플리케이션을 중단합니다."</string>
<string name="sendText" msgid="5132506121645618310">"텍스트에 대한 작업 선택"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"벨소리 볼륨"</string>
<string name="volume_music" msgid="5421651157138628171">"미디어 볼륨"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"사전 공유 키 기반 L2TP/IPSec VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"인증서 기반 L2TP/IPSec VPN"</string>
<string name="upload_file" msgid="2897957172366730416">"파일 선택"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"파일을 선택하지 않았습니다."</string>
<string name="reset" msgid="2448168080964209908">"재설정"</string>
<string name="submit" msgid="1602335572089911941">"제출"</string>
- <string name="description_star" msgid="2654319874908576133">"즐겨찾기"</string>
- <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"차량 모드 사용"</string>
- <string name="car_mode_disable_notification_message" msgid="668663626721675614">"차량 모드를 종료하려면 선택하세요."</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"운전모드 사용"</string>
+ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"운전모드를 종료하려면 선택하세요."</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"테더링 또는 핫스팟 사용"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"구성하려면 터치하세요."</string>
+ <string name="back_button_label" msgid="2300470004503343439">"뒤로"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"다음"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"높은 모바일 데이터 사용량"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"모바일 데이터 사용에 대해 자세히 알아보려면 터치하세요."</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"모바일 데이터 제한을 초과했습니다."</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 6acf4a6..052eb08 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Telefonsvarer"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"Tilkoblingsproblem eller ugyldig MMI-kode."</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"Handlingen kan kun utføres på numre med anropsbegrensning."</string>
<string name="serviceEnabled" msgid="8147278346414714315">"Tjenesten ble aktivert."</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"Tjenesten ble aktivert for:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"Tjenesten ble deaktivert."</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"Tilgang til minnekortet."</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"deaktivere eller endre statusfeltet"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"Lar applikasjonen deaktivere statusfeltet, samt legge til og fjerne systemikoner."</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"statusrad"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"Tillater programmet å vises i statusfeltet."</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"utvide/slå sammen statusfeltet"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Lar applikasjonen utvide eller slå sammen statusfeltet."</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"avskjære utgående anrop"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Lar applikasjonen endre globale lydinnstillinger som volum og ruting."</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"ta opp lyd"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"Gir applikasjonen tilgang til opptaksstien for lyd."</string>
- <string name="permlab_camera" msgid="8059288807274039014">"ta bilder"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"Lar applikasjonen ta bilder med kameraet. Dette gir applikasjonen til når som helst å se og lagre det kameraet ser."</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"ta bilder og videoer"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"Tillat programmet å ta bilder og videoer med kameraet. Programmet kan dermed til enhver tid samle inn bilder som kameraet fanger inn."</string>
<string name="permlab_brick" msgid="8337817093326370537">"deaktivere telefonen permanent"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"Lar applikasjonen deaktivere hele telefonen permanent. Dette er svært farlig."</string>
<string name="permlab_reboot" msgid="2898560872462638242">"tvinge omstart av telefon"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"Lar applikasjonen kontrollere vibratoren."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"kontrollere lommelykten"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"Lar applikasjonen kontrollere lommelykten."</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"tilgang til USB-enheter"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"Tillater programmet å få tilgang til USB-enheter."</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"teste maskinvare"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"Lar applikasjonen styre diverse enheter med det formål å teste maskinvaren."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"ringe telefonnummer direkte"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"Kontrollerer når enheten låses. Du må skrive inn passordet på nytt for å låse den opp."</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"Slett alle data"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"Utfører tilbakestilling til fabrikkstandard. Alle data slettes uten varsel."</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Hjemmenummer"</item>
<item msgid="869923650527136615">"Mobil"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"Arbeid"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Annen"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Egendefinert"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Skriv inn PIN-kode:"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Skriv inn passord for å låse opp"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Skriv inn personlig kode for å låse opp"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Gal PIN-kode!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"For å låse opp, trykk på menyknappen og deretter 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Nødnummer"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"Tilbake til samtale"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Riktig!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Beklager, prøv igjen:"</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Prøv igjen"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Lader (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Fullt ladet"</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM-kortet er låst."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Låser opp SIM-kort…"</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%d</xliff:g> times. "\n\n"Please try again in <xliff:g id="NUMBER_1">%d</xliff:g> seconds."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Du har oppgitt feil passord <xliff:g id="NUMBER_0">%d</xliff:g> ganger. "\n\n"Prøv igjen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Du har oppgitt feil personlig kode <xliff:g id="NUMBER_0">%d</xliff:g> ganger. "\n\n"Prøv igjen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%d</xliff:g> times. After <xliff:g id="NUMBER_1">%d</xliff:g> more unsuccessful attempts, you will be asked to unlock your phone using your Google sign-in."\n\n" Please try again in <xliff:g id="NUMBER_2">%d</xliff:g> seconds."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Prøv igjen om <xliff:g id="NUMBER">%d</xliff:g> sekunder."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Glemt mønsteret?"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Fjern"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Ingen varslinger"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Aktiviteter"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Varslinger"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"Lader…"</string>
- <string name="battery_low_title" msgid="7923774589611311406">"Koble til en lader"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"Batteriet er nesten tomt:"</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"mindre enn <xliff:g id="NUMBER">%d%%</xliff:g> igjen."</string>
- <string name="battery_low_why" msgid="7279169609518386372">"Batteribruk"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Fabrikktesten feilet"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"The FACTORY_TEST action is only supported for packages installed in /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"No package was found that provides the FACTORY_TEST action."</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"Merk alt"</string>
- <string name="selectText" msgid="3889149123626888637">"Merk tekst"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"Slutt å merke tekst"</string>
<string name="cut" msgid="3092569408438626261">"Klipp ut"</string>
- <string name="cutAll" msgid="2436383270024931639">"Klipp ut alt"</string>
<string name="copy" msgid="2681946229533511987">"Kopier"</string>
- <string name="copyAll" msgid="2590829068100113057">"Kopier alt"</string>
<string name="paste" msgid="5629880836805036433">"Lim inn"</string>
<string name="copyUrl" msgid="2538211579596067402">"Kopier URL"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"Inndatametode"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"Legg «<xliff:g id="WORD">%s</xliff:g>» til ordlisten"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"Rediger tekst"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"Lite plass"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"Det begynner å bli lite lagringsplass på telefonen."</string>
<string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"OK"</string>
<string name="no" msgid="5141531044935541497">"Avbryt"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"Merk"</string>
+ <string name="loading" msgid="1760724998928255250">"Laster inn ..."</string>
<string name="capital_on" msgid="1544682755514494298">"På"</string>
<string name="capital_off" msgid="6815870386972805832">"Av"</string>
<string name="whichApplication" msgid="4533185947064773386">"Fullfør med"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"Tving avslutning"</string>
<string name="report" msgid="4060218260984795706">"Rapportér"</string>
<string name="wait" msgid="7147118217226317732">"Vent"</string>
+ <string name="smv_application" msgid="295583804361236288">"Programmet <xliff:g id="APPLICATION">%1$s</xliff:g> (prosessen <xliff:g id="PROCESS">%2$s</xliff:g>) har brutt de selvpålagte StrictMode-retningslinjene."</string>
+ <string name="smv_process" msgid="5120397012047462446">"Prosessen<xliff:g id="PROCESS">%1$s</xliff:g> har brutt de selvpålagte StrictMode-retningslinjene."</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> kjører"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Velg for å bytte til programmet"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Bytt programmer?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Et annet program kjører og må stoppes før du kan starte et nytt program."</string>
+ <string name="old_app_action" msgid="493129172238566282">"Gå tilbake til <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="old_app_description" msgid="942967900237208466">"Ikke start det nye programmet."</string>
+ <string name="new_app_action" msgid="5472756926945440706">"Start <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"Stopp det gamle programmet uten å lagre det."</string>
<string name="sendText" msgid="5132506121645618310">"Velg mål for tekst"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Ringetonevolum"</string>
<string name="volume_music" msgid="5421651157138628171">"Medievolum"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Passordbasert L2TP/IPSec-VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Sertifikatbasert L2TP/IPSec-VPN"</string>
<string name="upload_file" msgid="2897957172366730416">"Velg fil"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil er valgt"</string>
<string name="reset" msgid="2448168080964209908">"Tilbakestill"</string>
<string name="submit" msgid="1602335572089911941">"Send inn"</string>
- <string name="description_star" msgid="2654319874908576133">"favoritt"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Bilmodus er aktivert"</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"Velg for å avslutte bilmodus"</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"Tilknytning eller trådløs sone er aktiv"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"Trykk for å konfigurere"</string>
+ <string name="back_button_label" msgid="2300470004503343439">"Tilbake"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"Neste"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"Høy mobildatabruk"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"Berør for å lese mer om bruk av mobildata"</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"Grensen for mobildatabruk er overskredet"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index a9586e1..10ebc4e 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Voicemail"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"Verbindingsprobleem of ongeldige MMI-code."</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"Bewerking is beperkt tot vaste nummers."</string>
<string name="serviceEnabled" msgid="8147278346414714315">"Service is ingeschakeld."</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"Service is ingeschakeld voor:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"Service is uitgeschakeld."</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"Toegang tot de SD-kaart."</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"statusbalk uitschakelen of wijzigen"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"Hiermee kan een toepassing de statusbalk uitschakelen of systeempictogrammen toevoegen en verwijderen."</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"statusbalk"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"Hiermee kan de toepassing de statusbalk zijn."</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"statusbalk uitvouwen/samenvouwen"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Hiermee kan de toepassing de statusbalk uitvouwen of samenvouwen."</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"uitgaande oproepen onderscheppen"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Hiermee kan een toepassing de algemene audio-instellingen, zoals volume en omleiding, wijzigen."</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"audio opnemen"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"Hiermee krijgt de toepassing toegang tot het audio-opnamepad."</string>
- <string name="permlab_camera" msgid="8059288807274039014">"foto\'s maken"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"Hiermee kan een toepassing foto\'s maken met de camera. De toepassing kan op deze manier op elk gewenste moment foto\'s verzamelen van wat de camera ziet."</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"foto\'s en video\'s maken"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"Hiermee kan een toepassing foto\'s en video\'s maken met de camera. De toepassing kan op deze manier op elk gewenste moment beelden verzamelen van wat de camera ziet."</string>
<string name="permlab_brick" msgid="8337817093326370537">"telefoon permanent uitschakelen"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"Hiermee kan de toepassing de telefoon permanent uitschakelen. Dit is erg gevaarlijk."</string>
<string name="permlab_reboot" msgid="2898560872462638242">"telefoon nu opnieuw opstarten"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"Hiermee kan de toepassing de trilstand beheren."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"zaklamp bedienen"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"Hiermee kan de toepassing de zaklamp bedienen."</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"toegang krijgen tot USB-apparaten"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"Hiermee kan de toepassing toegang krijgen tot USB-apparaten."</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"hardware testen"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"Hiermee kan de toepassing verschillende randapparaten beheren om de hardware te testen."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"telefoonnummers rechtstreeks bellen"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"Beheren wanneer het apparaat wordt vergrendeld, wat vereist dat u het wachtwoord opnieuw invoert."</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"Alle gegevens wissen"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"De fabrieksinstellingen herstellen, waarbij alle gegevens worden verwijderd zonder bevestiging."</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Thuis"</item>
<item msgid="869923650527136615">"Mobiel"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"Werk"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Overig"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Aangepast"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN-code invoeren"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Voer het wachtwoord in om te ontgrendelen"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Voer de PIN-code in om te ontgrendelen"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Onjuiste PIN-code!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Druk op \'Menu\' en vervolgens op 0 om te ontgrendelen."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Alarmnummer"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"Terug naar gesprek"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Juist!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Probeer het opnieuw"</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Probeer het opnieuw"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Opladen (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Opgeladen."</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM-kaart is vergrendeld."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"SIM-kaart ontgrendelen..."</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"U heeft uw ontgrendelingspatroon <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist getekend. "\n\n"Probeer het over <xliff:g id="NUMBER_1">%d</xliff:g> seconden opnieuw."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"U heeft uw wachtwoord <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist ingevoerd. "\n\n"Probeer het over <xliff:g id="NUMBER_1">%d</xliff:g> seconden opnieuw."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"U heeft uw PIN-code <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist ingevoerd. "\n\n"Probeer het over <xliff:g id="NUMBER_1">%d</xliff:g> seconden opnieuw."</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"U heeft uw ontgrendelingspatroon <xliff:g id="NUMBER_0">%d</xliff:g> keer onjuist getekend. Na nog eens <xliff:g id="NUMBER_1">%d</xliff:g> mislukte pogingen, wordt u gevraagd om uw telefoon te ontgrendelen met uw Google aanmelding."\n\n" Probeer het over <xliff:g id="NUMBER_2">%d</xliff:g> seconden opnieuw."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Probeer het over <xliff:g id="NUMBER">%d</xliff:g> seconden opnieuw."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Patroon vergeten?"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"Alt"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Wissen"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Geen meldingen"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Actief"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Meldingen"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"Opladen..."</string>
- <string name="battery_low_title" msgid="7923774589611311406">"Sluit de oplader aan"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"De accu raakt op:"</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> of minder resterend."</string>
- <string name="battery_low_why" msgid="7279169609518386372">"Accugebruik"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Fabriekstest mislukt"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"De actie FACTORY_TEST wordt alleen ondersteund voor pakketten die zijn geïnstalleerd in /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Er is geen pakket gevonden dat de actie FACTORY_TEST levert."</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"Alles selecteren"</string>
- <string name="selectText" msgid="3889149123626888637">"Tekst selecteren"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"Stoppen met tekst selecteren"</string>
<string name="cut" msgid="3092569408438626261">"Knippen"</string>
- <string name="cutAll" msgid="2436383270024931639">"Alles knippen"</string>
<string name="copy" msgid="2681946229533511987">"Kopiëren"</string>
- <string name="copyAll" msgid="2590829068100113057">"Alles kopiëren"</string>
<string name="paste" msgid="5629880836805036433">"Plakken"</string>
<string name="copyUrl" msgid="2538211579596067402">"URL kopiëren"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"Invoermethode"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"\'<xliff:g id="WORD">%s</xliff:g>\' toevoegen aan woordenboek"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"Tekst bewerken"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"Weinig ruimte"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"Opslagruimte van telefoon raakt op."</string>
<string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"OK"</string>
<string name="no" msgid="5141531044935541497">"Annuleren"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"Let op"</string>
+ <string name="loading" msgid="1760724998928255250">"Laden..."</string>
<string name="capital_on" msgid="1544682755514494298">"AAN"</string>
<string name="capital_off" msgid="6815870386972805832">"UIT"</string>
<string name="whichApplication" msgid="4533185947064773386">"Actie voltooien met"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"Nu sluiten"</string>
<string name="report" msgid="4060218260984795706">"Rapport"</string>
<string name="wait" msgid="7147118217226317732">"Wachten"</string>
+ <string name="smv_application" msgid="295583804361236288">"De toepassing <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) heeft het zelf afgedwongen StrictMode-beleid geschonden."</string>
+ <string name="smv_process" msgid="5120397012047462446">"Het proces <xliff:g id="PROCESS">%1$s</xliff:g> heeft het zelf afgedwongen StrictMode-beleid geschonden."</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> wordt uitgevoerd"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Selecteren om over te schakelen naar toepassing"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Toepassingen wijzigen?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Er wordt al een andere toepassing uitgevoerd die moet worden gestopt voordat u een nieuwe toepassing kunt starten."</string>
+ <string name="old_app_action" msgid="493129172238566282">"Terug naar <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="old_app_description" msgid="942967900237208466">"De nieuwe toepassing niet starten."</string>
+ <string name="new_app_action" msgid="5472756926945440706">"<xliff:g id="OLD_APP">%1$s</xliff:g> starten"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"De oude toepassing stoppen zonder opslaan."</string>
<string name="sendText" msgid="5132506121645618310">"Selecteer een actie voor tekst"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Belvolume"</string>
<string name="volume_music" msgid="5421651157138628171">"Mediavolume"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Vooraf gedeelde sleutel op basis van L2TP/IPSec VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certificaat op basis van L2TP/IPSec VPN"</string>
<string name="upload_file" msgid="2897957172366730416">"Bestand kiezen"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"Geen bestand geselecteerd"</string>
<string name="reset" msgid="2448168080964209908">"Opnieuw instellen"</string>
<string name="submit" msgid="1602335572089911941">"Verzenden"</string>
- <string name="description_star" msgid="2654319874908576133">"favoriet"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Automodus ingeschakeld"</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"Selecteer dit om de automodus af te sluiten."</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"Tethering of hotspot actief"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"Aanraken om te configureren"</string>
+ <string name="back_button_label" msgid="2300470004503343439">"Vorige"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"Volgende"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"Hoog mobiel gegevensgebruik"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"Raak aan voor meer informatie over mobiel gegevensgebruik"</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"Mobiele gegevenslimiet overschreden"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index c1f4033..07946c7 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Poczta głosowa"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"Problem z połączeniem lub błędny kod MMI."</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"Operacja jest ograniczona wyłącznie do numerów ustalonych."</string>
<string name="serviceEnabled" msgid="8147278346414714315">"Usługa była włączona."</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"Usługa została włączona dla:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"Usługa została wyłączona."</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"Dostęp do karty SD."</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"wyłączanie lub zmienianie paska stanu"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"Pozwala aplikacjom na wyłączenie paska stanu lub dodawanie i usuwanie ikon systemowych."</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"pasek stanu"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"Zezwala aplikacji na występowanie na pasku stanu."</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"rozwijanie/zwijanie paska stanu"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Pozwala aplikacji na rozwijanie lub zwijanie paska stanu."</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"przechwytywanie połączeń wychodzących"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Pozwala aplikacjom na zmianę globalnych ustawień audio, takich jak głośność i routing."</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"nagrywanie dźwięku"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"Pozwala aplikacji na dostęp do ścieżki nagrywania dźwięku."</string>
- <string name="permlab_camera" msgid="8059288807274039014">"robienie zdjęć"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"Pozwala aplikacji na wykonywanie zdjęć za pomocą aparatu. Dzięki temu może ona pobierać zdjęcia z aparatu w dowolnym momencie."</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"wykonywanie zdjęć i filmów wideo"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"Zezwala aplikacji na wykonywanie zdjęć i rejestrowanie filmów wideo przy użyciu aparatu fotograficznego. Umożliwia to aplikacji gromadzenie w dowolnym momencie zdjęć tego, na co skierowany jest obiektyw aparatu."</string>
<string name="permlab_brick" msgid="8337817093326370537">"wyłączenie telefonu na stałe"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"Pozwala aplikacji na wyłączenie całego telefonu na stałe. Jest to bardzo niebezpieczne."</string>
<string name="permlab_reboot" msgid="2898560872462638242">"wymuszanie ponownego uruchomienia telefonu"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"Pozwala aplikacjom na kontrolowanie wibracji."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"kontrolowanie latarki"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"Pozwala aplikacji kontrolować latarkę."</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"dostęp do urządzeń USB"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"Zezwala aplikacji na dostęp do urządzeń USB."</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"testowanie sprzętu"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"Pozwala aplikacji na kontrolowanie różnych urządzeń peryferyjnych w celu testowania sprzętu."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"bezpośrednie wybieranie numerów telefonów"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"Kontroluje, kiedy urządzenie jest blokowane, wymagając ponownego wprowadzenia hasła."</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"Usuń wszystkie dane"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"Wykonuje reset fabryczny, usuwając wszystkie dane użytkownika bez żadnego potwierdzenia."</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Dom"</item>
<item msgid="869923650527136615">"Komórka"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"Służbowy"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Inny"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Niestandardowy"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"przez <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> przez <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Wprowadź kod PIN"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Wprowadź hasło, aby odblokować"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Wprowadź kod PIN, aby odblokować"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Błędny kod PIN!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Aby odblokować, naciśnij Menu, a następnie 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Numer alarmowy"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"Powrót do połączenia"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Poprawnie!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Niestety, spróbuj ponownie"</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Spróbuj ponownie"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Ładowanie (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Naładowany."</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"Karta SIM jest zablokowana."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Odblokowywanie karty SIM..."</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Wzór odblokowania został nieprawidłowo narysowany <xliff:g id="NUMBER_0">%d</xliff:g> razy. "\n\n"Spróbuj ponownie za <xliff:g id="NUMBER_1">%d</xliff:g> sekund."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Hasło zostało nieprawidłowo wprowadzone <xliff:g id="NUMBER_0">%d</xliff:g> razy. "\n\n"Spróbuj ponownie za <xliff:g id="NUMBER_1">%d</xliff:g> s."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Niepoprawnie wprowadzono kod PIN <xliff:g id="NUMBER_0">%d</xliff:g> razy. "\n\n"Spróbuj ponownie za <xliff:g id="NUMBER_1">%d</xliff:g> s."</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Wzór odblokowania został narysowany nieprawidłowo <xliff:g id="NUMBER_0">%d</xliff:g> razy. Po kolejnych <xliff:g id="NUMBER_1">%d</xliff:g> nieudanych próbach telefon trzeba będzie odblokować przez zalogowanie na koncie Google."\n\n" Spróbuj ponownie za <xliff:g id="NUMBER_2">%d</xliff:g> sekund."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Spróbuj ponownie za <xliff:g id="NUMBER">%d</xliff:g> sekund."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Zapomniałeś wzoru?"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Wyczyść"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Brak powiadomień"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Bieżące"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Powiadomienia"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"Ładowanie..."</string>
- <string name="battery_low_title" msgid="7923774589611311406">"Podłącz ładowarkę"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"Bateria się rozładowuje:"</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"Pozostało: <xliff:g id="NUMBER">%d%%</xliff:g> lub mniej."</string>
- <string name="battery_low_why" msgid="7279169609518386372">"Użycie baterii"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Nieudany test fabryczny"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Czynność FACTORY_TEST jest obsługiwana tylko dla pakietów zainstalowanych w katalogu /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Nie znaleziono żadnego pakietu, który zapewnia działanie FACTORY_TEST."</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"Zaznacz wszystko"</string>
- <string name="selectText" msgid="3889149123626888637">"Zaznacz tekst"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"Zatrzymaj wybieranie tekstu"</string>
<string name="cut" msgid="3092569408438626261">"Wytnij"</string>
- <string name="cutAll" msgid="2436383270024931639">"Wytnij wszystko"</string>
<string name="copy" msgid="2681946229533511987">"Kopiuj"</string>
- <string name="copyAll" msgid="2590829068100113057">"Kopiuj wszystko"</string>
<string name="paste" msgid="5629880836805036433">"Wklej"</string>
<string name="copyUrl" msgid="2538211579596067402">"Kopiuj adres URL"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"Metoda wprowadzania"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"Dodaj termin „<xliff:g id="WORD">%s</xliff:g>” do słownika"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"Edytuj tekst"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"Mało miejsca"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"Maleje ilość dostępnej pamięci telefonu."</string>
<string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"OK"</string>
<string name="no" msgid="5141531044935541497">"Anuluj"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"Uwaga"</string>
+ <string name="loading" msgid="1760724998928255250">"Wczytywanie..."</string>
<string name="capital_on" msgid="1544682755514494298">"Włącz"</string>
<string name="capital_off" msgid="6815870386972805832">"Wyłącz"</string>
<string name="whichApplication" msgid="4533185947064773386">"Zakończ czynność korzystając z"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"Wymuś zamknięcie"</string>
<string name="report" msgid="4060218260984795706">"Zgłoś"</string>
<string name="wait" msgid="7147118217226317732">"Czekaj"</string>
+ <string name="smv_application" msgid="295583804361236288">"Aplikacja <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) naruszyła wymuszone przez siebie zasady StrictMode."</string>
+ <string name="smv_process" msgid="5120397012047462446">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> naruszył wymuszone przez siebie zasady StrictMode."</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"Działa <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Wybierz, aby przełączyć się na aplikację"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Czy przełączyć aplikacje?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Działa już inna aplikacja, którą trzeba zatrzymać, aby możliwe było uruchomienie nowej."</string>
+ <string name="old_app_action" msgid="493129172238566282">"Powrót do aplikacji <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="old_app_description" msgid="942967900237208466">"Nie uruchamiaj nowej aplikacji."</string>
+ <string name="new_app_action" msgid="5472756926945440706">"Uruchom <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"Zatrzymaj starą aplikację bez zapisywania."</string>
<string name="sendText" msgid="5132506121645618310">"Jak wysłać tekst?"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Głośność dzwonka"</string>
<string name="volume_music" msgid="5421651157138628171">"Głośność multimediów"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"Sieć VPN L2TP/IPSec z kluczem PSK"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Sieć VPN L2TP/IPSec z certyfikatem"</string>
<string name="upload_file" msgid="2897957172366730416">"Wybierz plik"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"Nie wybrano pliku"</string>
<string name="reset" msgid="2448168080964209908">"Resetuj"</string>
<string name="submit" msgid="1602335572089911941">"Prześlij"</string>
- <string name="description_star" msgid="2654319874908576133">"ulubione"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Tryb samochodowy włączony"</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"Wybierz, aby zakończyć tryb samochodowy."</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"Jest aktywne powiązanie lub punkt dostępu"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"Dotknij, aby skonfigurować"</string>
+ <string name="back_button_label" msgid="2300470004503343439">"Wróć"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"Dalej"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"Wysoki poziom użycia danych"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"Dotknij, aby zobaczyć statystyki przesyłu danych"</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"Przekroczono limit danych"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 750d257..d44a80d 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Correio de voz"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"Problema de ligação ou código MMI inválido."</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"A operação está restringida a números fixos autorizados."</string>
<string name="serviceEnabled" msgid="8147278346414714315">"O serviço foi activado."</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"O serviço foi activado para:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"O serviço foi desactivado."</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"Aceder ao cartão SD."</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"desactivar ou modificar barra de estado"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"Permite à aplicação desactivar a barra de estado ou adicionar e remover ícones do sistema."</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"barra de estado"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"Permite que a aplicação seja apresentada na barra de estado."</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"expandir/fechar barra de estado"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Permite à aplicação expandir ou fechar a barra de estado."</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"interceptar chamadas efectuadas"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Permite à aplicação modificar as definições de áudio globais, tais como o volume e o encaminhamento."</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"gravar áudio"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"Permite à aplicação aceder ao caminho de gravação de áudio."</string>
- <string name="permlab_camera" msgid="8059288807274039014">"tirar fotografias"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"Permite à aplicação tirar fotografias com a câmara. Isto permite que a aplicação recolha imagens captadas pela câmara em qualquer momento."</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"tirar fotografias e vídeos"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"Permite à aplicação tirar fotografias e vídeos com a câmara, permitindo que a aplicação recolha imagens captadas pela câmara em qualquer momento."</string>
<string name="permlab_brick" msgid="8337817093326370537">"desactivar telefone de forma permanente"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"Permite à aplicação desactivar permanentemente todo o telefone. Esta acção é muito perigosa."</string>
<string name="permlab_reboot" msgid="2898560872462638242">"forçar reinício do telefone"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"Permite à aplicação controlar o vibrador."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"controlar lanterna"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"Permite à aplicação controlar a lanterna."</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"aceder a dispositivos USB"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"Permite à aplicação aceder a dispositivos USB."</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"testar hardware"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"Permite à aplicação controlar vários periféricos para fins de teste de hardware."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"marcar números de telefone directamente"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"Controle quando o dispositivo é bloqueado, sendo necessário reintroduzir a respectiva palavra-passe."</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"Apagar todos os dados"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"Efectue uma reposição de fábrica, eliminando todos os dados sem qualquer confirmação."</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Residência"</item>
<item msgid="869923650527136615">"Móvel"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"Emprego"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Outro"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Personalizado"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"através do <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> através de <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Introduzir código PIN"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Introduza a palavra-passe para desbloquear"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Introduza o PIN para desbloquear"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Código PIN incorrecto!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear, prima Menu e, em seguida, 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emergência"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"Regressar à chamada"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Correcto!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Lamentamos, tente novamente"</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Lamentamos, tente novamente"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"A carregar (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Carregado."</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"O cartão SIM está bloqueado."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"A desbloquear cartão SIM..."</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Efectuou incorrectamente o seu padrão de desbloqueio <xliff:g id="NUMBER_0">%d</xliff:g> vezes. "\n\n"Tente novamente dentro de <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Introduziu incorrectamente a palavra-passe <xliff:g id="NUMBER_0">%d</xliff:g> vezes. "\n\n"Tente novamente dentro de <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Introduziu incorrectamente o PIN <xliff:g id="NUMBER_0">%d</xliff:g> vezes. "\n\n"Tente novamente dentro de <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Efectuou incorrectamente o seu padrão de desbloqueio <xliff:g id="NUMBER_0">%d</xliff:g> vezes. Após outras <xliff:g id="NUMBER_1">%d</xliff:g> tentativas sem sucesso, ser-lhe-á pedido para desbloquear o telefone utilizando o seu início de sessão no Google."\n\n" Tente novamente dentro de <xliff:g id="NUMBER_2">%d</xliff:g> segundos."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Tente novamente dentro de <xliff:g id="NUMBER">%d</xliff:g> segundos."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Esqueceu-se do padrão?"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Limpar"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Sem notificações"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Em curso"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Notificações"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"A carregar..."</string>
- <string name="battery_low_title" msgid="7923774589611311406">"Ligue o carregador"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"A bateria está a ficar fraca:"</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"Restam <xliff:g id="NUMBER">%d%%</xliff:g> ou menos."</string>
- <string name="battery_low_why" msgid="7279169609518386372">"Utilização da bateria"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"O teste de fábrica falhou"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"A acção FACTORY_TEST apenas é suportada para pacotes instalados em /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Não foi localizado qualquer pacote que forneça a acção FACTORY_TEST."</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"Seleccionar tudo"</string>
- <string name="selectText" msgid="3889149123626888637">"Seleccionar texto"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"Parar selecção de texto"</string>
<string name="cut" msgid="3092569408438626261">"Cortar"</string>
- <string name="cutAll" msgid="2436383270024931639">"Cortar tudo"</string>
<string name="copy" msgid="2681946229533511987">"Copiar"</string>
- <string name="copyAll" msgid="2590829068100113057">"Copiar tudo"</string>
<string name="paste" msgid="5629880836805036433">"Colar"</string>
<string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"Adicionar \"<xliff:g id="WORD">%s</xliff:g>\" ao dicionário"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"Editar texto"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"Pouco espaço livre"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"O espaço de armazenamento do telefone está a ficar reduzido."</string>
<string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"OK"</string>
<string name="no" msgid="5141531044935541497">"Cancelar"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"Atenção"</string>
+ <string name="loading" msgid="1760724998928255250">"A carregar..."</string>
<string name="capital_on" msgid="1544682755514494298">"Activado"</string>
<string name="capital_off" msgid="6815870386972805832">"Desactivar"</string>
<string name="whichApplication" msgid="4533185947064773386">"Concluir acção utilizando"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"Forçar fecho"</string>
<string name="report" msgid="4060218260984795706">"Relatório"</string>
<string name="wait" msgid="7147118217226317732">"Esperar"</string>
+ <string name="smv_application" msgid="295583804361236288">"A aplicação <xliff:g id="APPLICATION">%1$s</xliff:g> (processo <xliff:g id="PROCESS">%2$s</xliff:g>) violou a política StrictMode auto-imposta."</string>
+ <string name="smv_process" msgid="5120397012047462446">"O processo <xliff:g id="PROCESS">%1$s</xliff:g> violou a política StrictMode auto-imposta."</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> em execução"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Seleccione para mudar para a aplicação"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Mudar aplicações?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Já está em execução uma outra aplicação que terá de ser parada para que possa iniciar uma nova."</string>
+ <string name="old_app_action" msgid="493129172238566282">"Regressar a <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="old_app_description" msgid="942967900237208466">"Não iniciar a nova aplicação."</string>
+ <string name="new_app_action" msgid="5472756926945440706">"Iniciar <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"Parar a aplicação antiga sem guardar."</string>
<string name="sendText" msgid="5132506121645618310">"Seleccionar uma acção para texto"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Volume da campainha"</string>
<string name="volume_music" msgid="5421651157138628171">"Volume de multimédia"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"VPN L2TP/IPSec baseada em chave pré- partilhada"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"VPN L2TP/IPSec baseada em certificado"</string>
<string name="upload_file" msgid="2897957172366730416">"Escolher ficheiro"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"Não foi seleccionado nenhum ficheiro"</string>
<string name="reset" msgid="2448168080964209908">"Repor"</string>
<string name="submit" msgid="1602335572089911941">"Enviar"</string>
- <string name="description_star" msgid="2654319874908576133">"favorito"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Modo automóvel activado"</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"Seleccionar para sair do modo automóvel."</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"Ligação ponto a ponto ou hotspot activos"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"Tocar para configurar"</string>
+ <string name="back_button_label" msgid="2300470004503343439">"Anterior"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"Seguinte"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"Utilização elevada de dados móveis"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"Toque para saber mais sobre a utilização de dados móveis"</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"Limite de dados móveis excedido"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 12f77bc..a6c49d8 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Correio de voz"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"Problema de conexão ou código MMI inválido."</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"A operação é limitada somente a números de chamadas fixas."</string>
<string name="serviceEnabled" msgid="8147278346414714315">"O serviço foi ativado."</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"O serviço foi ativado para:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"O serviço foi desativado."</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"Acessar o cartão SD."</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"desativar ou modificar a barra de status"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"Permite que o aplicativo desative a barra de status ou adicione e remova ícones do sistema."</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"barra de status"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"Permite que o aplicativo seja a barra de status."</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"expandir/recolher barra de status"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Permite que o aplicativo expanda ou recolha a barra de status."</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"interceptar chamadas enviadas"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Permite que o aplicativo modifique as configurações globais de áudio como volume e roteamento."</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"gravar áudio"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"Permite que um aplicativo acesse o caminho de gravação do áudio."</string>
- <string name="permlab_camera" msgid="8059288807274039014">"tirar fotos"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"Permite que o aplicativo tire fotos com a câmera. Isso permite que o aplicativo colete imagens vistas pela câmera a qualquer momento."</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"tirar fotos e gravar vídeos"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"Permite que o aplicativo tire fotos e grave vídeos com a câmera. Isso permite que o aplicativo colete imagens vistas pela câmera a qualquer momento."</string>
<string name="permlab_brick" msgid="8337817093326370537">"desativar permanentemente o telefone"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"Permite que o aplicativo desative o telefone inteiro permanentemente. Isso é muito perigoso."</string>
<string name="permlab_reboot" msgid="2898560872462638242">"forçar reinicialização do telefone"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"Permite que o aplicativo controle o vibrador."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"controlar lanterna"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"Permite que o aplicativo controle a lanterna."</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"acessar dispositivos USB"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"Permitir que o aplicativo acesse dispositivos USB."</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"testar hardware"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"Permite que o aplicativo controle diversos periféricos para teste do hardware."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"chamar diretamente os números de telefone"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"Controla o bloqueio do aparelho, exigindo que a senha seja digitada novamente."</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"Apagar todos os dados"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"Execute uma redefinição de fábrica, excluindo todos os seus dados sem qualquer confirmação."</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Residencial"</item>
<item msgid="869923650527136615">"Celular"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"Comercial"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Outros"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Personalizado"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"por meio de <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Digite o código PIN"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Digite a senha para desbloquear"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Digite o PIN para desbloquear"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Código PIN incorreto!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear, pressione Menu e, em seguida, 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emergência"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"Retornar à chamada"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Correto!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Tente novamente"</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Tente novamente"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Carregando (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Carregado."</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"O cartão SIM está bloqueado."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Desbloqueando o cartão SIM…"</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Você desenhou incorretamente o seu padrão de desbloqueio <xliff:g id="NUMBER_0">%d</xliff:g> vezes. "\n\n"Tente novamente em <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Você inseriu incorretamente a sua senha <xliff:g id="NUMBER_0">%d</xliff:g> vezes. "\n\n"Tente novamente em <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Você digitou incorretamente o seu PIN <xliff:g id="NUMBER_0">%d</xliff:g> vezes. "\n\n"Tente novamente em <xliff:g id="NUMBER_1">%d</xliff:g> segundos."</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Você desenhou o seu padrão de desbloqueio incorretamente <xliff:g id="NUMBER_0">%d</xliff:g> vezes. Mais <xliff:g id="NUMBER_1">%d</xliff:g> tentativas incorretas e você receberá uma solicitação para desbloquear o seu telefone usando o seu login do Google."\n\n" Tente novamente em <xliff:g id="NUMBER_2">%d</xliff:g> segundos."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Tente novamente em <xliff:g id="NUMBER">%d</xliff:g> segundos."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Esqueceu o padrão?"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Limpar"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Sem notificações"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Em andamento"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Notificações"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"Carregando..."</string>
- <string name="battery_low_title" msgid="7923774589611311406">"Conecte o carregador"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"A bateria está ficando baixa:"</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> ou menos restante(s)."</string>
- <string name="battery_low_why" msgid="7279169609518386372">"Uso da bateria"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Falha no teste de fábrica"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"A ação FACTORY_TEST é suportada apenas para pacotes instalados em /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Nenhum pacote que forneça a ação FACTORY_TEST foi encontrado."</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"Selecionar tudo"</string>
- <string name="selectText" msgid="3889149123626888637">"Selecionar texto"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"Parar seleção de texto"</string>
<string name="cut" msgid="3092569408438626261">"Recortar"</string>
- <string name="cutAll" msgid="2436383270024931639">"Recortar tudo"</string>
<string name="copy" msgid="2681946229533511987">"Copiar"</string>
- <string name="copyAll" msgid="2590829068100113057">"Copiar tudo"</string>
<string name="paste" msgid="5629880836805036433">"Colar"</string>
<string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"Adicionar \"<xliff:g id="WORD">%s</xliff:g>\" ao dicionário"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"Editar texto"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"Pouco espaço"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"O espaço de armazenamento do telefone está ficando baixo."</string>
<string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"OK"</string>
<string name="no" msgid="5141531044935541497">"Cancelar"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"Atenção"</string>
+ <string name="loading" msgid="1760724998928255250">"Carregando..."</string>
<string name="capital_on" msgid="1544682755514494298">"ATIVADO"</string>
<string name="capital_off" msgid="6815870386972805832">"DESATIVADO"</string>
<string name="whichApplication" msgid="4533185947064773386">"Complete a ação usando"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"Forçar fechamento"</string>
<string name="report" msgid="4060218260984795706">"Informar"</string>
<string name="wait" msgid="7147118217226317732">"Aguardar"</string>
+ <string name="smv_application" msgid="295583804361236288">"O aplicativo <xliff:g id="APPLICATION">%1$s</xliff:g> (processo <xliff:g id="PROCESS">%2$s</xliff:g>) violou a política StrictMode imposta automaticamente."</string>
+ <string name="smv_process" msgid="5120397012047462446">"O processo <xliff:g id="PROCESS">%1$s</xliff:g> violou a política StrictMode imposta automaticamente."</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> em execução"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Selecionar para alternar para o aplicativo"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Alternar aplicativos?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Outro aplicativo já está em execução e deve ser interrompido antes que você inicie um novo aplicativo."</string>
+ <string name="old_app_action" msgid="493129172238566282">"Voltar para <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="old_app_description" msgid="942967900237208466">"Não inicie o novo aplicativo."</string>
+ <string name="new_app_action" msgid="5472756926945440706">"Iniciar <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"Pare o aplicativo antigo sem salvar."</string>
<string name="sendText" msgid="5132506121645618310">"Selecione uma ação para o texto"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Volume da campainha"</string>
<string name="volume_music" msgid="5421651157138628171">"Volume da mídia"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"VPN L2TP/IPSec com base em chave pré-compartilhada"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"VPN L2TP/IPSec com base em certificado"</string>
<string name="upload_file" msgid="2897957172366730416">"Escolher arquivo"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"Nenhum arquivo escolhido"</string>
<string name="reset" msgid="2448168080964209908">"Redefinir"</string>
<string name="submit" msgid="1602335572089911941">"Enviar"</string>
- <string name="description_star" msgid="2654319874908576133">"favorito"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Modo de carro ativado"</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"Selecione para sair do modo de carro."</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"Vínculo ou ponto de acesso ativo"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"Toque para configurar"</string>
+ <string name="back_button_label" msgid="2300470004503343439">"Voltar"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"Avançar"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"Alto uso de dados do celular"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"Toque para saber mais sobre uso de dados do celular"</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"Limite de dados do celular excedido"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index bd41d60..fcb7eb7 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Голосовая почта"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"Неполадки подключения или неверный код MMI."</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"Операция возможна только для разрешенных номеров."</string>
<string name="serviceEnabled" msgid="8147278346414714315">"Служба включена."</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"Служба подключена для:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"Служба отключена."</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"Доступ к SD-карте."</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"отключать или изменять строку состояния"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"Позволяет приложению отключать строку состояния или добавлять/удалять системные значки."</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"строка состояния"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"Позволяет приложению быть строкой состояния."</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"разворачивать/сворачивать строку состояния"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Позволяет приложению разворачивать или сворачивать строку состояния."</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"перехватывать исходящие вызовы"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Позволяет приложению изменять глобальные аудионастройки, такие как громкость и маршрутизацию."</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"записывать аудио"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"Позволяет приложению получать доступ к пути аудиозаписи."</string>
- <string name="permlab_camera" msgid="8059288807274039014">"снимать фотографии"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"Позволяет приложению делать снимки с помощью камеры. Это разрешение позволяет приложению в любое время собирать изображения, видимые через объектив камеры."</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"снимать фото и видео"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"Позволяет приложению делать снимки и видео с помощью камеры. Это дает приложению возможность в любое время получать изображения с объектива камеры."</string>
<string name="permlab_brick" msgid="8337817093326370537">"отключать телефон"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"Позволяет данному приложению отключить телефон навсегда. Это очень опасно."</string>
<string name="permlab_reboot" msgid="2898560872462638242">"принудительно перезагружать телефон"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"Позволяет приложению управлять виброзвонком."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"управлять вспышкой"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"Позволяет приложению управлять вспышкой."</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"доступ к USB-устройствам"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"Позволяет приложению получать доступ к USB-устройствам."</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"проверять аппаратное обеспечение"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"Позволяет приложению управлять различными периферийными устройствами для проверки аппаратного обеспечения."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"посылать прямые вызовы на номера телефонов"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"Управление блокировкой устройства, требующей ввода пароля."</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"Удалить все данные"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"Выполнить сброс к начальным настройкам с удалением всех данных без запроса подтверждения."</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Домашний"</item>
<item msgid="869923650527136615">"Мобильный"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"Работа"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Другое"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Создать свой ярлык"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"с помощью <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> с помощью <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Введите PIN-код"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Введите пароль для разблокировки"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Введите PIN-код для разблокировки"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Неверный PIN-код!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Для разблокировки нажмите \"Меню\", а затем 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Экстренная служба"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"Вернуться к вызову"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Правильно!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Повторите попытку"</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Повторите попытку"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Идет зарядка (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Заряжена."</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM-карта заблокирована."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Разблокировка SIM-карты…"</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Количество неудачных попыток ввода графического ключа разблокировки: <xliff:g id="NUMBER_0">%d</xliff:g>. "\n\n"Повторите попытку через <xliff:g id="NUMBER_1">%d</xliff:g> с."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Количество неудачных попыток ввода пароля: <xliff:g id="NUMBER_0">%d</xliff:g>. "\n\n"Повторите попытку через <xliff:g id="NUMBER_1">%d</xliff:g> с."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Количество неудачных попыток ввода PIN-кода: <xliff:g id="NUMBER_0">%d</xliff:g>. "\n\n"Повторите попытку через <xliff:g id="NUMBER_1">%d</xliff:g> с."</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Количество неудачных попыток ввода графического ключа разблокировки: <xliff:g id="NUMBER_0">%d</xliff:g>. После <xliff:g id="NUMBER_1">%d</xliff:g> неудачных попыток вам будет предложено разблокировать телефон с помощью учетных данных Google.\n "\n\n" Повторите попытку через <xliff:g id="NUMBER_2">%d</xliff:g> с."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Повторите попытку через <xliff:g id="NUMBER">%d</xliff:g> с."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Забыли графический ключ?"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Очистить"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Нет уведомлений"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Текущие"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Уведомления"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"Идет зарядка..."</string>
- <string name="battery_low_title" msgid="7923774589611311406">"Подключите зарядное устройство"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"Батарея разряжена:"</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"Осталось <xliff:g id="NUMBER">%d%%</xliff:g> или меньше."</string>
- <string name="battery_low_why" msgid="7279169609518386372">"Расход заряда батареи"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Не удалось провести стандартный тест"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Действие FACTORY_TEST поддерживается только для пакетов, установленных в /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Пакет, обеспечивающий действие FACTORY_TEST, не найден."</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"Выбрать все"</string>
- <string name="selectText" msgid="3889149123626888637">"Выбрать текст"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"Остановить выделение текста"</string>
<string name="cut" msgid="3092569408438626261">"Вырезать"</string>
- <string name="cutAll" msgid="2436383270024931639">"Вырезать все"</string>
<string name="copy" msgid="2681946229533511987">"Копировать"</string>
- <string name="copyAll" msgid="2590829068100113057">"Копировать все"</string>
<string name="paste" msgid="5629880836805036433">"Вставить"</string>
<string name="copyUrl" msgid="2538211579596067402">"Копировать URL"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"Способ ввода"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"Добавить \"<xliff:g id="WORD">%s</xliff:g>\" в словарь"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"Изменить текст"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"Недостаточно места"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"Заканчивается место в памяти телефона."</string>
<string name="ok" msgid="5970060430562524910">"ОК"</string>
@@ -726,10 +727,11 @@
<string name="yes" msgid="5362982303337969312">"ОК"</string>
<string name="no" msgid="5141531044935541497">"Отмена"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"Внимание"</string>
+ <string name="loading" msgid="1760724998928255250">"Загрузка..."</string>
<string name="capital_on" msgid="1544682755514494298">"ВКЛ"</string>
<string name="capital_off" msgid="6815870386972805832">"ВЫКЛ"</string>
- <string name="whichApplication" msgid="4533185947064773386">"Завершить действие с помощью"</string>
- <string name="alwaysUse" msgid="4583018368000610438">"Использовать по умолчанию для этого действия."</string>
+ <string name="whichApplication" msgid="4533185947064773386">"Что использовать?"</string>
+ <string name="alwaysUse" msgid="4583018368000610438">"По умолчанию для этого действия"</string>
<string name="clearDefaultHintMsg" msgid="4815455344600932173">"Удалить настройки по умолчанию: главный экран > \"Настройки\" > \"Приложения\" > \"Управление приложениями\"."</string>
<string name="chooseActivity" msgid="1009246475582238425">"Выберите действие"</string>
<string name="noApplications" msgid="1691104391758345586">"Это действие не может выполнять ни одно приложение."</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"Закрыть"</string>
<string name="report" msgid="4060218260984795706">"Отчет"</string>
<string name="wait" msgid="7147118217226317732">"Подождать"</string>
+ <string name="smv_application" msgid="295583804361236288">"Приложение <xliff:g id="APPLICATION">%1$s</xliff:g> (процесс <xliff:g id="PROCESS">%2$s</xliff:g>) нарушило собственную политику StrictMode."</string>
+ <string name="smv_process" msgid="5120397012047462446">"Процесс <xliff:g id="PROCESS">%1$s</xliff:g> нарушил собственную политику StrictMode."</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"Приложение <xliff:g id="APP">%1$s</xliff:g> запущено"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Нажмите, чтобы переключиться в приложение"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Переключить приложения?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Выполняется другое приложение, которое должно быть остановлено прежде, чем запускать новое."</string>
+ <string name="old_app_action" msgid="493129172238566282">"Вернуться к приложению <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="old_app_description" msgid="942967900237208466">"Не запускать новое приложение."</string>
+ <string name="new_app_action" msgid="5472756926945440706">"Запустить приложение <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"Остановить старое приложение без сохранения изменений."</string>
<string name="sendText" msgid="5132506121645618310">"Выберите действие для текста"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Громкость звонка"</string>
<string name="volume_music" msgid="5421651157138628171">"Громкость мультимедиа"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPN (на основе предв. общ. ключа)"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"L2TP/IPSec VPN (на основе сертификата)"</string>
<string name="upload_file" msgid="2897957172366730416">"Выбрать файл"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"Не выбран файл"</string>
<string name="reset" msgid="2448168080964209908">"Сбросить"</string>
<string name="submit" msgid="1602335572089911941">"Отправить"</string>
- <string name="description_star" msgid="2654319874908576133">"избранное"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Режим громкой связи включен"</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"Выберите для выхода из режима громкой связи."</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"USB-модем или точка доступа Wi-Fi активны"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"Нажмите для настройки"</string>
+ <string name="back_button_label" msgid="2300470004503343439">"Назад"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"Далее"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"Активная передача данных"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"Нажмите, чтобы узнать больше о мобильной передаче данных"</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"Превышен лимит на мобильные данные"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index b7cbc52..8e623e8 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Röstbrevlåda"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"Anslutningsproblem eller ogiltig MMI-kod."</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"Endast fasta nummer kan användas."</string>
<string name="serviceEnabled" msgid="8147278346414714315">"Tjänsten har aktiverats."</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"Tjänsten har aktiverats för:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"Tjänsten har inaktiverats."</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"Få åtkomst till SD-kortet."</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"inaktivera eller ändra statusfält"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"Tillåter att programmet inaktiverar statusfältet eller lägger till och tar bort systemikoner."</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"statusfält"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"Tillåter att programmet visas i statusfältet."</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"expandera/komprimera statusfält"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Tillåter att program expanderar eller komprimerar statusfältet."</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"spärra utgående samtal"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Tillåter att ett program ändrar globala ljudinställningar, till exempel volym och routning."</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"spela in ljud"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"Tillåter att program får åtkomst till sökvägen för ljudinspelning."</string>
- <string name="permlab_camera" msgid="8059288807274039014">"ta bilder"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"Tillåter att program tar kort med kameran. Då kan programmet när som helst samla bilderna som visas i kameran."</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"ta bilder och spela in videoklipp"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"Tillåter att program tar kort och spelar in video med kameran. Då kan programmet när som helst fotografera eller spela in det som visas i kameran."</string>
<string name="permlab_brick" msgid="8337817093326370537">"inaktivera telefonen permanent"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"Tillåter att programmet inaktiverar hela telefonen permanent. Detta är mycket farligt."</string>
<string name="permlab_reboot" msgid="2898560872462638242">"tvinga omstart av telefon"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"Tillåter att programmet styr vibratorn."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"styra lampa"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"Tillåter att programmet styr lampan."</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"åtkomst till USB-enheter"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"Tillåter att programmet använder USB-enheter."</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"testa maskinvara"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"Tillåter att ett program styr kringutrustning i syfte att testa maskinvara."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"ringa telefonnummer direkt"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"Kontrollera när enheten låses, vilket kräver att du anger lösenordet igen."</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"Radera alla data"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"Återställ fabriksinställningarna och ta bort alla data utan någon bekräftelse."</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Hem"</item>
<item msgid="869923650527136615">"Mobil"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"Arbete"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Övrigt"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Anpassad"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"via <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g> via <xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"Ange PIN-kod"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Ange lösenord för att låsa upp"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Ange PIN-kod för att låsa upp"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Fel PIN-kod!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Tryck på Menu och sedan på 0 om du vill låsa upp."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Nödsamtalsnummer"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"Återgå till samtalet"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Korrekt!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Försök igen"</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Försök igen"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Laddar (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Laddad."</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM-kortet är låst."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Låser upp SIM-kort…"</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Du har ritat ditt grafiska lösenord fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. "\n\n"Försök igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Du har angett ditt lösenord fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. "\n\n"Försök igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Du har angett din PIN-kod fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. "\n\n"Försök igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Du har ritat ditt grafiska lösenord fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter <xliff:g id="NUMBER_1">%d</xliff:g> försök till kommer du att uppmanas att låsa upp telefonen med din Google-inloggning."\n\n" Försök igen om <xliff:g id="NUMBER_2">%d</xliff:g> sekunder."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Försök igen om <xliff:g id="NUMBER">%d</xliff:g> sekunder."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Glömt ditt grafiska lösenord?"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Ta bort"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Inga aviseringar"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Pågående"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Meddelanden"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"Laddar…"</string>
- <string name="battery_low_title" msgid="7923774589611311406">"Anslut laddaren"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"Batteriet håller på att ta slut:"</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> eller mindre kvar."</string>
- <string name="battery_low_why" msgid="7279169609518386372">"Batteriförbrukning"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Det gick fel vid fabrikstestet"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"Åtgärden FACTORY_TEST stöds endast för paket som har installerats i /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"Vi hittade inget paket som erbjuder åtgärden FACTORY_TEST."</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"Välj alla"</string>
- <string name="selectText" msgid="3889149123626888637">"Markera text"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"Sluta välja text"</string>
<string name="cut" msgid="3092569408438626261">"Klipp ut"</string>
- <string name="cutAll" msgid="2436383270024931639">"Klipp ut alla"</string>
<string name="copy" msgid="2681946229533511987">"Kopiera"</string>
- <string name="copyAll" msgid="2590829068100113057">"Kopiera alla"</string>
<string name="paste" msgid="5629880836805036433">"Klistra in"</string>
<string name="copyUrl" msgid="2538211579596067402">"Kopiera webbadress"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"Indatametod"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"Lägg till \"<xliff:g id="WORD">%s</xliff:g>\" i ordlistan"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"Redigera text"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"Dåligt med utrymme"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"Telefonens lagringsutrymme håller på att ta slut."</string>
<string name="ok" msgid="5970060430562524910">"OK"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"OK"</string>
<string name="no" msgid="5141531044935541497">"Avbryt"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"Obs!"</string>
+ <string name="loading" msgid="1760724998928255250">"Läser in..."</string>
<string name="capital_on" msgid="1544682755514494298">"PÅ"</string>
<string name="capital_off" msgid="6815870386972805832">"AV"</string>
<string name="whichApplication" msgid="4533185947064773386">"Slutför åtgärd genom att använda"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"Tvinga fram en stängning"</string>
<string name="report" msgid="4060218260984795706">"Rapportera"</string>
<string name="wait" msgid="7147118217226317732">"Vänta"</string>
+ <string name="smv_application" msgid="295583804361236288">"Programmet <xliff:g id="APPLICATION">%1$s</xliff:g> (processen <xliff:g id="PROCESS">%2$s</xliff:g>) har brutit mot sin egen StrictMode-policy."</string>
+ <string name="smv_process" msgid="5120397012047462446">"Processen <xliff:g id="PROCESS">%1$s</xliff:g> har brutit mot sin egen StrictMode-policy."</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> körs"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Välj om du vill växla till programmet"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Vill du byta program?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Ett annat program som körs måste avslutas innan du kan starta ett nytt."</string>
+ <string name="old_app_action" msgid="493129172238566282">"Gå tillbaka till <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="old_app_description" msgid="942967900237208466">"Starta inte det nya programmet."</string>
+ <string name="new_app_action" msgid="5472756926945440706">"Starta <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"Avbryt det gamla programmet utan att spara."</string>
<string name="sendText" msgid="5132506121645618310">"Välj en åtgärd för text"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Ringvolym"</string>
<string name="volume_music" msgid="5421651157138628171">"Mediavolym"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"I förväg delad L2TP/IPSec VPN-nyckel"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"Certifikatsbaserad L2TP/IPSec VPN"</string>
<string name="upload_file" msgid="2897957172366730416">"Välj fil"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"Ingen fil har valts"</string>
<string name="reset" msgid="2448168080964209908">"Återställ"</string>
<string name="submit" msgid="1602335572089911941">"Skicka"</string>
- <string name="description_star" msgid="2654319874908576133">"favorit"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Billäge aktiverat"</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"Välj om du vill avsluta billäge."</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"Internetdelning eller surfpunkt aktiverad"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"Tryck om du vill konfigurera"</string>
+ <string name="back_button_label" msgid="2300470004503343439">"Tillbaka"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"Nästa"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"Hög mobildataanvändning"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"Tryck om du vill veta mer om mobildataanvändning"</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"Gränsen för mobildata har överskridits"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index cb1e399..b207db9 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Sesli Mesaj"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"Bağlantı sorunu veya geçersiz MMI kodu."</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"İşlem sadece sabit arama numaralarıyla sınırlandırılmıştır."</string>
<string name="serviceEnabled" msgid="8147278346414714315">"Hizmet etkindi."</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"Hizmet şunun için etkinleştirildi:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"Hizmet devre dışı bırakıldı."</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"SD karta erişin."</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"durum çubuğunu devre dışı bırak veya değiştir"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"Uygulamanın durum çubuğunu devre dışı bırakmasına veya sistem simgeleri ekleyip kaldırmasına izin verir."</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"durum çubuğu"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"Uygulamanın durum çubuğu olmasına izin verir."</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"durum çubuğunu genişlet/daralt"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Uygulamanın, durum çubuğunu genişletip daraltmasına izin verir."</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"giden aramalarda araya gir"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Uygulamaların, ses düzeyi ve yönlendirme gibi genel ses ayarlarını değiştirmesine izin verir."</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"ses kaydet"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"Uygulamanın, ses kayıt yoluna erişmesine izin verir."</string>
- <string name="permlab_camera" msgid="8059288807274039014">"resim çek"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"Uygulamaların kamera ile resim çekmesine izin verir. Bu işlev herhangi bir zamanda kameranın görmekte olduğu görüntüyü uygulamaların toplamasına izin verir."</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"resim çekme ve görüntü kaydetme"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"Uygulamanın kamera ile resim çekmesine ve görüntü almasına izin verir. Bu işlev herhangi bir zamanda uygulamanın kamera görüntülerini yakalamasına olanak tanır."</string>
<string name="permlab_brick" msgid="8337817093326370537">"telefonu tamamen devre dışı bırak"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"Uygulamaların tüm telefonu kalıcı olarak devre dışı bırakmasına izin verir. Bu çok tehlikelidir."</string>
<string name="permlab_reboot" msgid="2898560872462638242">"telefonu yeniden başlamaya zorla"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"Uygulamanın titreşimi denetlemesine izin verir."</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"flaşı denetle"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"Uygulamaların flaş ışığını denetlemesine izin verir."</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"USB cihazlarına erişme"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"Uygulamaların USB cihazlarına erişimine izin verir"</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"donanımı test et"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"Uygulamanın donanım testi için çeşitli çevre birimlerini denetlemesine izin verir."</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"telefon numaralarına doğrudan çağrı yap"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"Cihaz kilitlendiğinde, şifresini yeniden girmenizi gerektiren denetim."</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"Tüm verileri sil"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"Tüm verilerinizi onay olmadan silmek için fabrika ayarlarına sıfırlayın."</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"Ev"</item>
<item msgid="869923650527136615">"Mobil"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"İş"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"Diğer"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"Özel"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"<xliff:g id="SOURCE">%1$s</xliff:g> aracılığıyla"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="SOURCE">%2$s</xliff:g> ile <xliff:g id="DATE">%1$s</xliff:g> tarihinde"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"PIN kodunu gir"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"Kilidi açmak için şifreyi girin"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"Kilidi açmak için PIN\'i girin"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"Yanlış PIN kodu!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"Kilidi açmak için önce Menü\'ye, sonra 0\'a basın."</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Acil durum numarası"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"Çağrıya dön"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Doğru!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"Üzgünüz, lütfen yeniden deneyin"</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"Maalesef, tekrar deneyin"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"Şarj oluyor (<xliff:g id="PERCENT">%%</xliff:g><xliff:g id="NUMBER">%d</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"Şarj oldu."</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM kart kilitli."</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"SIM kart kilidi açılıyor…"</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Kilit açma deseninizi <xliff:g id="NUMBER_0">%d</xliff:g> kez yanlış çizdiniz. "\n\n"Lütfen <xliff:g id="NUMBER_1">%d</xliff:g> saniye içinde yeniden deneyin."</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Şifrenizi <xliff:g id="NUMBER_0">%d</xliff:g> kez yanlış girdiniz. "\n\n"Lütfen <xliff:g id="NUMBER_1">%d</xliff:g> saniye içinde yeniden deneyin."</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"PIN\'inizi <xliff:g id="NUMBER_0">%d</xliff:g> kez yanlış girdiniz. "\n\n"Lütfen <xliff:g id="NUMBER_1">%d</xliff:g> saniye içinde yeniden deneyin."</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Kilit açma deseninizi <xliff:g id="NUMBER_0">%d</xliff:g> kez yanlış çizdiniz. <xliff:g id="NUMBER_1">%d</xliff:g> başarısız denemeden sonra telefonunuzu Google oturum açma bilgilerinizi kullanarak açmanız istenir."\n\n" Lütfen <xliff:g id="NUMBER_2">%d</xliff:g> saniye içinde yeniden deneyin."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"<xliff:g id="NUMBER">%d</xliff:g> saniye içinde yeniden deneyin."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"Deseni unuttunuz mu?"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Temizle"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Bildirim yok"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Sürüyor"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Bildirimler"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"Şarj oluyor…"</string>
- <string name="battery_low_title" msgid="7923774589611311406">"Lütfen şarj cihazını takın"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"Pil tükeniyor:"</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> veya daha az kaldı."</string>
- <string name="battery_low_why" msgid="7279169609518386372">"Pil kullanımı"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"Fabrika testi yapılamadı"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST işlemi yalnızca /system/app dizinine yüklenmiş paketler için desteklenir."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"FACTORY_TEST işlemini sağlayan hiçbir paket bulunamadı."</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"Tümünü seç"</string>
- <string name="selectText" msgid="3889149123626888637">"Metin seç"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"Metin seçmeyi durdur"</string>
<string name="cut" msgid="3092569408438626261">"Kes"</string>
- <string name="cutAll" msgid="2436383270024931639">"Tümünü kes"</string>
<string name="copy" msgid="2681946229533511987">"Kopyala"</string>
- <string name="copyAll" msgid="2590829068100113057">"Tümünü kopyala"</string>
<string name="paste" msgid="5629880836805036433">"Yapıştır"</string>
<string name="copyUrl" msgid="2538211579596067402">"URL\'yi kopyala"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"Giriş yöntemi"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"\"<xliff:g id="WORD">%s</xliff:g>\" kelimesini sözlüğe ekle"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"Metin düzenle"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"Yer az"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"Telefonun depolama alanı azalıyor."</string>
<string name="ok" msgid="5970060430562524910">"Tamam"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"Tamam"</string>
<string name="no" msgid="5141531044935541497">"İptal"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"Dikkat"</string>
+ <string name="loading" msgid="1760724998928255250">"Yükleniyor..."</string>
<string name="capital_on" msgid="1544682755514494298">"AÇIK"</string>
<string name="capital_off" msgid="6815870386972805832">"KAPALI"</string>
<string name="whichApplication" msgid="4533185947064773386">"İşlemi şunu kullanarak tamamla"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"Kapanmaya zorla"</string>
<string name="report" msgid="4060218260984795706">"Rapor"</string>
<string name="wait" msgid="7147118217226317732">"Bekle"</string>
+ <string name="smv_application" msgid="295583804361236288">"<xliff:g id="APPLICATION">%1$s</xliff:g> uygulaması (<xliff:g id="PROCESS">%2$s</xliff:g> işlemi) kendiliğinden zorunlu StrictMode politikasını ihlal etti."</string>
+ <string name="smv_process" msgid="5120397012047462446">"<xliff:g id="PROCESS">%1$s</xliff:g> işlemi kendiliğinden zorunlu StrictMode politikasını ihlal etti."</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> çalışıyor"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Uygulama değiştirmeyi seçin"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Uygulamaların arasında geçiş yapılsın mı?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Başka bir uygulama zaten çalışıyor. Yeni bir uygulama başlatmadan bu uygulama durdurulmalıdır."</string>
+ <string name="old_app_action" msgid="493129172238566282">"<xliff:g id="OLD_APP">%1$s</xliff:g> öğesine geri dön"</string>
+ <string name="old_app_description" msgid="942967900237208466">"Yeni uygulamayı başlatmayın."</string>
+ <string name="new_app_action" msgid="5472756926945440706">"<xliff:g id="OLD_APP">%1$s</xliff:g> uygulamasını başlat"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"Eski uygulamayı kaydetmeden durdurun."</string>
<string name="sendText" msgid="5132506121645618310">"Metin için bir işlem seçin"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"Zil sesi düzeyi"</string>
<string name="volume_music" msgid="5421651157138628171">"Medya ses düzeyi"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec VPN temelli önceden paylaşılmış anahtar"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"L2TP/IPSec VPN temelli sertifika"</string>
<string name="upload_file" msgid="2897957172366730416">"Dosya seç"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"Seçili dosya yok"</string>
<string name="reset" msgid="2448168080964209908">"Sıfırla"</string>
<string name="submit" msgid="1602335572089911941">"Gönder"</string>
- <string name="description_star" msgid="2654319874908576133">"favori"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"Araba modu etkin"</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"Araba modundan çıkmak için seçin."</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"Doğrudan bağlantı veya ortak erişim noktası etkin"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"Yapılandırmak için dokunun"</string>
+ <string name="back_button_label" msgid="2300470004503343439">"Geri"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"İleri"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"Yüksek düzeyde mobil veri kullanımı"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"Mobil veri kullanımı hakkında daha fazla bilgi edinmek için dokunun"</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"Mobil veri limiti aşıldı"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 834a84c..4d78b86 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"语音信箱"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"出现连接问题或 MMI 码无效。"</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"只能对固定拨号号码执行此类操作。"</string>
<string name="serviceEnabled" msgid="8147278346414714315">"已启用服务。"</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"已针对以下内容启用了服务:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"已停用服务。"</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"访问 SD 卡。"</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"停用或修改状态栏"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"允许应用程序停用状态栏或者增删系统图标。"</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"状态栏"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"允许以状态栏形式显示应用程序。"</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"展开/收拢状态栏"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"允许应用程序展开或收拢状态栏。"</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"拦截外拨电话"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"允许应用程序修改整个系统的音频设置,如音量和路由。"</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"录音"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"允许应用程序访问录音路径。"</string>
- <string name="permlab_camera" msgid="8059288807274039014">"拍照"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"允许应用程序使用相机拍照,这样应用程序可随时收集进入相机镜头的图像。"</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"拍摄照片和视频"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"允许应用程序使用相机拍摄照片和视频,这样应用程序可随时收集进入相机镜头中看到的图片。"</string>
<string name="permlab_brick" msgid="8337817093326370537">"永久停用手机"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"允许应用程序永久停用整个手机,这非常危险。"</string>
<string name="permlab_reboot" msgid="2898560872462638242">"强行重新启动手机"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"允许应用程序控制振动器。"</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"控制闪光灯"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"允许应用程序控制闪光灯。"</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"访问 USB 设备"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"允许应用程序访问 USB 设备。"</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"测试硬件"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"允许应用程序控制各外围设备以进行硬件测试。"</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"直接拨打电话号码"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"控制何时锁定设备,这需要您重新输入密码。"</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"清除所有数据"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"恢复出厂设置,这会在不提示确认的情况下删除您的所有数据。"</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"住宅"</item>
<item msgid="869923650527136615">"手机"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"公司"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"其他"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"自定义"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"通过 <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"时间:<xliff:g id="DATE">%1$s</xliff:g>,方式:<xliff:g id="SOURCE">%2$s</xliff:g>"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"输入 PIN 码"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"输入密码进行解锁"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"输入 PIN 进行解锁"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PIN 码不正确!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"要解锁,请先按 MENU 再按 0。"</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"急救或报警电话"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"返回通话"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"正确!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"很抱歉,请重试"</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"很抱歉,请重试"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"正在充电 (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"已充满。"</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM 卡被锁定"</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"正在解锁 SIM 卡..."</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"您已经 <xliff:g id="NUMBER_0">%d</xliff:g> 次绘错了自己的解锁图案。"\n\n"请在 <xliff:g id="NUMBER_1">%d</xliff:g> 秒后重试。"</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"您已经 <xliff:g id="NUMBER_0">%d</xliff:g> 次错误地输入了密码。"\n\n"请在 <xliff:g id="NUMBER_1">%d</xliff:g> 秒后重试。"</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"您已经 <xliff:g id="NUMBER_0">%d</xliff:g> 次错误地输入了 PIN。"\n\n"请在 <xliff:g id="NUMBER_1">%d</xliff:g> 秒后重试。"</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"您已经 <xliff:g id="NUMBER_0">%d</xliff:g> 次绘错了自己的解锁图案。如果再尝试 <xliff:g id="NUMBER_1">%d</xliff:g> 次后仍不成功,系统会要求您使用自己的 Google 登录信息解锁手机。"\n\n"请在 <xliff:g id="NUMBER_2">%d</xliff:g> 秒后重试。"</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"<xliff:g id="NUMBER">%d</xliff:g> 秒后重试。"</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"忘记了图案?"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="AMPM">%P</xliff:g><xliff:g id="HOUR">%-l</xliff:g>点"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="AMPM">%p</xliff:g><xliff:g id="HOUR">%-l</xliff:g>点"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"清除"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"无通知"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"正在进行的"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"通知"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"正在充电..."</string>
- <string name="battery_low_title" msgid="7923774589611311406">"请连接充电器"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"电量所剩不多:"</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"电量剩余 <xliff:g id="NUMBER">%d%%</xliff:g> 或更少。"</string>
- <string name="battery_low_why" msgid="7279169609518386372">"电量使用情况"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"出厂测试失败"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"只有在 /system/app 中安装的包支持 FACTORY_TEST 操作。"</string>
<string name="factorytest_no_action" msgid="872991874799998561">"未发现支持 FACTORY_TEST 操作的包。"</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"全选"</string>
- <string name="selectText" msgid="3889149123626888637">"选择文字"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"停止选择文字"</string>
<string name="cut" msgid="3092569408438626261">"剪切"</string>
- <string name="cutAll" msgid="2436383270024931639">"全部剪切"</string>
<string name="copy" msgid="2681946229533511987">"复制"</string>
- <string name="copyAll" msgid="2590829068100113057">"全部复制"</string>
<string name="paste" msgid="5629880836805036433">"粘贴"</string>
<string name="copyUrl" msgid="2538211579596067402">"复制网址"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"输入法"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"将“<xliff:g id="WORD">%s</xliff:g>”添加到词典"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"编辑文字"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"存储空间不足"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"手机内存空间所剩不多了。"</string>
<string name="ok" msgid="5970060430562524910">"确定"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"确定"</string>
<string name="no" msgid="5141531044935541497">"取消"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"注意"</string>
+ <string name="loading" msgid="1760724998928255250">"正在载入..."</string>
<string name="capital_on" msgid="1544682755514494298">"打开"</string>
<string name="capital_off" msgid="6815870386972805832">"关闭"</string>
<string name="whichApplication" msgid="4533185947064773386">"使用以下方式发送"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"强行关闭"</string>
<string name="report" msgid="4060218260984795706">"报告"</string>
<string name="wait" msgid="7147118217226317732">"等待"</string>
+ <string name="smv_application" msgid="295583804361236288">"应用程序<xliff:g id="APPLICATION">%1$s</xliff:g>(<xliff:g id="PROCESS">%2$s</xliff:g> 进程)违反了自我强制执行的严格模式 (StrictMode) 政策。"</string>
+ <string name="smv_process" msgid="5120397012047462446">"进程 <xliff:g id="PROCESS">%1$s</xliff:g> 违反了自我强制执行的严格模式 (StrictMode) 政策"</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g>正在运行"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"选择以切换到该应用程序"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"要切换应用程序吗?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"已有一个应用程序正在运行,要启动新的应用程序,您必须先停止该应用程序。"</string>
+ <string name="old_app_action" msgid="493129172238566282">"返回至<xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="old_app_description" msgid="942967900237208466">"不要启动新的应用程序。"</string>
+ <string name="new_app_action" msgid="5472756926945440706">"启动<xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"停止旧的应用程序,但不保存。"</string>
<string name="sendText" msgid="5132506121645618310">"选择要对文字执行的操作"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"铃声音量"</string>
<string name="volume_music" msgid="5421651157138628171">"媒体音量"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"基于预共享密钥的 L2TP/IPSec VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"基于证书的 L2TP/IPSec VPN"</string>
<string name="upload_file" msgid="2897957172366730416">"选择文件"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"未选定任何文件"</string>
<string name="reset" msgid="2448168080964209908">"重置"</string>
<string name="submit" msgid="1602335572089911941">"提交"</string>
- <string name="description_star" msgid="2654319874908576133">"收藏"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"已启用车载模式"</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"选择退出车载模式"</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"USB 绑定或热点已启用"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"触摸可进行配置"</string>
+ <string name="back_button_label" msgid="2300470004503343439">"上一步"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"下一步"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"手机流量过多"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"轻触以了解有关手机流量详情"</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"已超出手机数据上限"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 549f571..4ab9907 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -34,6 +34,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"語音留言"</string>
<string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
<string name="mmiError" msgid="5154499457739052907">"連線發生問題或錯誤的 MMI 碼。"</string>
+ <string name="mmiFdnError" msgid="5224398216385316471">"僅允許在固定撥號時使用此操作。"</string>
<string name="serviceEnabled" msgid="8147278346414714315">"服務已啟用。"</string>
<string name="serviceEnabledFor" msgid="6856228140453471041">"已啟用服務:"</string>
<string name="serviceDisabled" msgid="1937553226592516411">"服務已停用。"</string>
@@ -171,6 +172,8 @@
<string name="permgroupdesc_storage" msgid="9203302214915355774">"存取 SD 卡。"</string>
<string name="permlab_statusBar" msgid="7417192629601890791">"停用或變更狀態列"</string>
<string name="permdesc_statusBar" msgid="1365473595331989732">"允許應用程式停用狀態列或新增、移除系統圖示。"</string>
+ <string name="permlab_statusBarService" msgid="7247281911387931485">"狀態列"</string>
+ <string name="permdesc_statusBarService" msgid="4097605867643520920">"允許應用程式成為狀態列。"</string>
<string name="permlab_expandStatusBar" msgid="1148198785937489264">"展開/收攏狀態列"</string>
<string name="permdesc_expandStatusBar" msgid="7088604400110768665">"允許應用程式展開或收攏狀態列。"</string>
<string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"攔截撥出電話"</string>
@@ -311,8 +314,8 @@
<string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"允許應用程式編輯全域音訊設定,例如音量與路由。"</string>
<string name="permlab_recordAudio" msgid="3876049771427466323">"錄製音訊"</string>
<string name="permdesc_recordAudio" msgid="6493228261176552356">"允許應用程式存取音訊錄製路徑。"</string>
- <string name="permlab_camera" msgid="8059288807274039014">"照相"</string>
- <string name="permdesc_camera" msgid="9013476258810982546">"允許應用程式使用相機拍照。此功能可讓應用程式隨時透過相機拍攝照片。"</string>
+ <string name="permlab_camera" msgid="3616391919559751192">"拍照和錄影"</string>
+ <string name="permdesc_camera" msgid="6004878235852154239">"允許應用程式使用相機拍照和錄影。此功能可讓應用程式隨時透過相機收集圖片。"</string>
<string name="permlab_brick" msgid="8337817093326370537">"永久停用電話"</string>
<string name="permdesc_brick" msgid="5569526552607599221">"允許應用程式永久停用手機。此項操作非常危險。"</string>
<string name="permlab_reboot" msgid="2898560872462638242">"強制重開機"</string>
@@ -335,6 +338,8 @@
<string name="permdesc_vibrate" msgid="2886677177257789187">"允許應用程式控制震動。"</string>
<string name="permlab_flashlight" msgid="2155920810121984215">"控制閃光燈"</string>
<string name="permdesc_flashlight" msgid="6433045942283802309">"允許應用程式控制閃光燈。"</string>
+ <string name="permlab_accessUsb" msgid="7362327818655760496">"存取 USB 裝置"</string>
+ <string name="permdesc_accessUsb" msgid="2414271762914049292">"允許應用程式存取 USB 裝置。"</string>
<string name="permlab_hardware_test" msgid="4148290860400659146">"測試硬體"</string>
<string name="permdesc_hardware_test" msgid="3668894686500081699">"允許應用程式控制各種週邊設備,以供測試用。"</string>
<string name="permlab_callPhone" msgid="3925836347681847954">"直接撥打電話號碼"</string>
@@ -431,6 +436,10 @@
<string name="policydesc_forceLock" msgid="2819868664946089740">"裝置鎖定時可取得控制,但必須重新輸入密碼。"</string>
<string name="policylab_wipeData" msgid="3910545446758639713">"清除所有資料"</string>
<string name="policydesc_wipeData" msgid="2314060933796396205">"重設為原廠設定 (系統會刪除所有資料,且不會向您進行確認)。"</string>
+ <!-- no translation found for policylab_setGlobalProxy (2784828293747791446) -->
+ <skip />
+ <!-- no translation found for policydesc_setGlobalProxy (6387497466660154931) -->
+ <skip />
<string-array name="phoneTypes">
<item msgid="8901098336658710359">"住家電話"</item>
<item msgid="869923650527136615">"行動電話"</item>
@@ -524,10 +533,9 @@
<string name="orgTypeWork" msgid="29268870505363872">"公司"</string>
<string name="orgTypeOther" msgid="3951781131570124082">"其他"</string>
<string name="orgTypeCustom" msgid="225523415372088322">"自訂"</string>
- <string name="contact_status_update_attribution" msgid="5112589886094402795">"透過 <xliff:g id="SOURCE">%1$s</xliff:g>"</string>
- <string name="contact_status_update_attribution_with_date" msgid="5945386376369979909">"<xliff:g id="DATE">%1$s</xliff:g>透過「<xliff:g id="SOURCE">%2$s</xliff:g>」"</string>
<string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"輸入 PIN 碼"</string>
<string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"輸入密碼即可解鎖"</string>
+ <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"輸入 PIN 即可解鎖"</string>
<string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PIN 碼錯誤!"</string>
<string name="keyguard_label_text" msgid="861796461028298424">"如要解鎖,請按 Menu 鍵,然後按 0。"</string>
<string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"緊急電話號碼"</string>
@@ -540,6 +548,7 @@
<string name="lockscreen_return_to_call" msgid="5244259785500040021">"返回通話"</string>
<string name="lockscreen_pattern_correct" msgid="9039008650362261237">"正確!"</string>
<string name="lockscreen_pattern_wrong" msgid="4817583279053112312">"很抱歉,請再試一次"</string>
+ <string name="lockscreen_password_wrong" msgid="6237443657358168819">"很抱歉,請再試一次"</string>
<string name="lockscreen_plugged_in" msgid="613343852842944435">"正在充電 (<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>)"</string>
<string name="lockscreen_charged" msgid="4938930459620989972">"充電完成。"</string>
<string name="lockscreen_battery_short" msgid="3617549178603354656">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
@@ -554,6 +563,8 @@
<string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM 卡已鎖定。"</string>
<string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"解鎖 SIM 卡中..."</string>
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"畫出解鎖圖形已錯誤 <xliff:g id="NUMBER_0">%d</xliff:g> 次。"\n\n" 請在 <xliff:g id="NUMBER_1">%d</xliff:g> 秒後再嘗試。"</string>
+ <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"您已輸入 <xliff:g id="NUMBER_0">%d</xliff:g> 次不正確的密碼。"\n\n"請於 <xliff:g id="NUMBER_1">%d</xliff:g> 秒後再試一次。"</string>
+ <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"您已輸入 <xliff:g id="NUMBER_0">%d</xliff:g> 次不正確的 PIN。"\n\n"請於 <xliff:g id="NUMBER_1">%d</xliff:g> 秒內再試一次。"</string>
<string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"畫出解鎖圖形已錯誤 <xliff:g id="NUMBER_0">%d</xliff:g> 次。再錯誤 <xliff:g id="NUMBER_1">%d</xliff:g> 次後,系統會要求使用 Google 登入來解鎖。"\n\n" 請在 <xliff:g id="NUMBER_2">%d</xliff:g> 秒後再試一次。"</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"<xliff:g id="NUMBER">%d</xliff:g> 秒後再試一次。"</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"忘記解鎖圖形?"</string>
@@ -573,16 +584,6 @@
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string>
<string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"清除"</string>
- <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"沒有通知"</string>
- <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"進行中"</string>
- <string name="status_bar_latest_events_title" msgid="6594767438577593172">"通知"</string>
- <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
- <string name="battery_status_charging" msgid="756617993998772213">"充電中"</string>
- <string name="battery_low_title" msgid="7923774589611311406">"請連接充電器"</string>
- <string name="battery_low_subtitle" msgid="7388781709819722764">"電池電量即將不足:"</string>
- <string name="battery_low_percent_format" msgid="696154104579022959">"還剩 <xliff:g id="NUMBER">%d%%</xliff:g> 以下。"</string>
- <string name="battery_low_why" msgid="7279169609518386372">"電池使用狀況"</string>
<string name="factorytest_failed" msgid="5410270329114212041">"出廠測試失敗"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"只有安裝在 /system/app 裡的程式才能支援 FACTORY_TEST 操作。"</string>
<string name="factorytest_no_action" msgid="872991874799998561">"找不到提供 FACTORY_TEST 的程式。"</string>
@@ -708,17 +709,17 @@
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"全部選取"</string>
- <string name="selectText" msgid="3889149123626888637">"選取文字"</string>
- <string name="stopSelectingText" msgid="4157931463872320996">"停止選取文字"</string>
<string name="cut" msgid="3092569408438626261">"剪下"</string>
- <string name="cutAll" msgid="2436383270024931639">"全部剪下"</string>
<string name="copy" msgid="2681946229533511987">"複製"</string>
- <string name="copyAll" msgid="2590829068100113057">"全部複製"</string>
<string name="paste" msgid="5629880836805036433">"貼上"</string>
<string name="copyUrl" msgid="2538211579596067402">"複製網址"</string>
+ <!-- no translation found for selectTextMode (6738556348861347240) -->
+ <skip />
+ <!-- no translation found for textSelectionCABTitle (5236850394370820357) -->
+ <skip />
<string name="inputMethod" msgid="1653630062304567879">"輸入方式"</string>
- <string name="addToDictionary" msgid="8793624991686948709">"將「<xliff:g id="WORD">%s</xliff:g>」新增至字典"</string>
- <string name="editTextMenuTitle" msgid="1672989176958581452">"編輯文字"</string>
+ <!-- no translation found for editTextMenuTitle (4909135564941815494) -->
+ <skip />
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"儲存空間即將不足"</string>
<string name="low_internal_storage_view_text" msgid="635106544616378836">"手機儲存空間即將不足。"</string>
<string name="ok" msgid="5970060430562524910">"確定"</string>
@@ -726,6 +727,7 @@
<string name="yes" msgid="5362982303337969312">"確定"</string>
<string name="no" msgid="5141531044935541497">"取消"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"注意"</string>
+ <string name="loading" msgid="1760724998928255250">"載入中..."</string>
<string name="capital_on" msgid="1544682755514494298">"開啟"</string>
<string name="capital_off" msgid="6815870386972805832">"關閉"</string>
<string name="whichApplication" msgid="4533185947064773386">"完成操作需使用"</string>
@@ -744,6 +746,16 @@
<string name="force_close" msgid="3653416315450806396">"強制關閉"</string>
<string name="report" msgid="4060218260984795706">"回報"</string>
<string name="wait" msgid="7147118217226317732">"等待"</string>
+ <string name="smv_application" msgid="295583804361236288">"應用程式 <xliff:g id="APPLICATION">%1$s</xliff:g> (處理程序 <xliff:g id="PROCESS">%2$s</xliff:g>) 已違反其自行實施的 StrictMode 政策。"</string>
+ <string name="smv_process" msgid="5120397012047462446">"處理程序 <xliff:g id="PROCESS">%1$s</xliff:g> 已違反其自行實施的 StrictMode 政策。"</string>
+ <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> 執行中"</string>
+ <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"選取切換應用程式"</string>
+ <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"切換應用程式?"</string>
+ <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"其他應用程式已在執行中,您必須停止執行,才能啟動新的應用程式。"</string>
+ <string name="old_app_action" msgid="493129172238566282">"返回 <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="old_app_description" msgid="942967900237208466">"請勿啟動新的應用程式。"</string>
+ <string name="new_app_action" msgid="5472756926945440706">"啟動 <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+ <string name="new_app_description" msgid="6830398339826789493">"停止舊的應用程式,且不儲存。"</string>
<string name="sendText" msgid="5132506121645618310">"訊息傳送方式"</string>
<string name="volume_ringtone" msgid="6885421406845734650">"鈴聲音量"</string>
<string name="volume_music" msgid="5421651157138628171">"媒體音量"</string>
@@ -850,13 +862,15 @@
<string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"採用預先共用金鑰的 L2TP/IPSec VPN"</string>
<string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"採用憑證的 L2TP/IPSec VPN"</string>
<string name="upload_file" msgid="2897957172366730416">"選擇檔案"</string>
+ <string name="no_file_chosen" msgid="6363648562170759465">"沒有選擇檔案"</string>
<string name="reset" msgid="2448168080964209908">"重設"</string>
<string name="submit" msgid="1602335572089911941">"提交"</string>
- <string name="description_star" msgid="2654319874908576133">"我的最愛"</string>
<string name="car_mode_disable_notification_title" msgid="3164768212003864316">"已啟用車用模式"</string>
<string name="car_mode_disable_notification_message" msgid="668663626721675614">"選取結束車用模式。"</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"數據連線或無線基地台已啟用"</string>
<string name="tethered_notification_message" msgid="3067108323903048927">"輕觸以設定"</string>
+ <string name="back_button_label" msgid="2300470004503343439">"上一個"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"下一個"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"高行動資料用量"</string>
<string name="throttle_warning_notification_message" msgid="2609734763845705708">"輕觸即可瞭解更多有關行動資料用量的詳細資訊"</string>
<string name="throttled_notification_title" msgid="6269541897729781332">"已達行動資料上限"</string>
diff --git a/core/tests/coretests/src/android/util/JsonReaderTest.java b/core/tests/coretests/src/android/util/JsonReaderTest.java
new file mode 100644
index 0000000..b0cfb7b
--- /dev/null
+++ b/core/tests/coretests/src/android/util/JsonReaderTest.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2010 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 android.util;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+public final class JsonReaderTest extends TestCase {
+
+ public void testReadArray() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[true, true]"));
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ assertEquals(true, reader.nextBoolean());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testReadObject() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader(
+ "{\"a\": \"android\", \"b\": \"banana\"}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ assertEquals("android", reader.nextString());
+ assertEquals("b", reader.nextName());
+ assertEquals("banana", reader.nextString());
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testSkipObject() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader(
+ "{\"a\": { \"c\": [], \"d\": [true, true, {}] }, \"b\": \"banana\"}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ reader.skipValue();
+ assertEquals("b", reader.nextName());
+ reader.skipValue();
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testHelloWorld() throws IOException {
+ String json = "{\n" +
+ " \"hello\": true,\n" +
+ " \"foo\": [\"world\"]\n" +
+ "}";
+ JsonReader reader = new JsonReader(new StringReader(json));
+ reader.beginObject();
+ assertEquals("hello", reader.nextName());
+ assertEquals(true, reader.nextBoolean());
+ assertEquals("foo", reader.nextName());
+ reader.beginArray();
+ assertEquals("world", reader.nextString());
+ reader.endArray();
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testNulls() {
+ try {
+ new JsonReader(null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testEmptyString() throws IOException {
+ try {
+ new JsonReader(new StringReader("")).beginArray();
+ } catch (IOException expected) {
+ }
+ try {
+ new JsonReader(new StringReader("")).beginObject();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testNoTopLevelObject() throws IOException {
+ try {
+ new JsonReader(new StringReader("true")).nextBoolean();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testCharacterUnescaping() throws IOException {
+ String json = "[\"a\","
+ + "\"a\\\"\","
+ + "\"\\\"\","
+ + "\":\","
+ + "\",\","
+ + "\"\\b\","
+ + "\"\\f\","
+ + "\"\\n\","
+ + "\"\\r\","
+ + "\"\\t\","
+ + "\" \","
+ + "\"\\\\\","
+ + "\"{\","
+ + "\"}\","
+ + "\"[\","
+ + "\"]\","
+ + "\"\\u0000\","
+ + "\"\\u0019\","
+ + "\"\\u20AC\""
+ + "]";
+ JsonReader reader = new JsonReader(new StringReader(json));
+ reader.beginArray();
+ assertEquals("a", reader.nextString());
+ assertEquals("a\"", reader.nextString());
+ assertEquals("\"", reader.nextString());
+ assertEquals(":", reader.nextString());
+ assertEquals(",", reader.nextString());
+ assertEquals("\b", reader.nextString());
+ assertEquals("\f", reader.nextString());
+ assertEquals("\n", reader.nextString());
+ assertEquals("\r", reader.nextString());
+ assertEquals("\t", reader.nextString());
+ assertEquals(" ", reader.nextString());
+ assertEquals("\\", reader.nextString());
+ assertEquals("{", reader.nextString());
+ assertEquals("}", reader.nextString());
+ assertEquals("[", reader.nextString());
+ assertEquals("]", reader.nextString());
+ assertEquals("\0", reader.nextString());
+ assertEquals("\u0019", reader.nextString());
+ assertEquals("\u20AC", reader.nextString());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testIntegersWithFractionalPartSpecified() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[1.0,1.0,1.0]"));
+ reader.beginArray();
+ assertEquals(1.0, reader.nextDouble());
+ assertEquals(1, reader.nextInt());
+ assertEquals(1L, reader.nextLong());
+ }
+
+ public void testDoubles() throws IOException {
+ String json = "[-0.0,"
+ + "1.0,"
+ + "1.7976931348623157E308,"
+ + "4.9E-324,"
+ + "0.0,"
+ + "-0.5,"
+ + "2.2250738585072014E-308,"
+ + "3.141592653589793,"
+ + "2.718281828459045]";
+ JsonReader reader = new JsonReader(new StringReader(json));
+ reader.beginArray();
+ assertEquals(-0.0, reader.nextDouble());
+ assertEquals(1.0, reader.nextDouble());
+ assertEquals(1.7976931348623157E308, reader.nextDouble());
+ assertEquals(4.9E-324, reader.nextDouble());
+ assertEquals(0.0, reader.nextDouble());
+ assertEquals(-0.5, reader.nextDouble());
+ assertEquals(2.2250738585072014E-308, reader.nextDouble());
+ assertEquals(3.141592653589793, reader.nextDouble());
+ assertEquals(2.718281828459045, reader.nextDouble());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testNonFiniteDoubles() throws IOException {
+ String json = "[NaN]";
+ JsonReader reader = new JsonReader(new StringReader(json));
+ reader.beginArray();
+ try {
+ reader.nextDouble();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ }
+
+ public void testLongs() throws IOException {
+ String json = "[0,0,0,"
+ + "1,1,1,"
+ + "-1,-1,-1,"
+ + "-9223372036854775808,"
+ + "9223372036854775807]";
+ JsonReader reader = new JsonReader(new StringReader(json));
+ reader.beginArray();
+ assertEquals(0L, reader.nextLong());
+ assertEquals(0, reader.nextInt());
+ assertEquals(0.0, reader.nextDouble());
+ assertEquals(1L, reader.nextLong());
+ assertEquals(1, reader.nextInt());
+ assertEquals(1.0, reader.nextDouble());
+ assertEquals(-1L, reader.nextLong());
+ assertEquals(-1, reader.nextInt());
+ assertEquals(-1.0, reader.nextDouble());
+ try {
+ reader.nextInt();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ assertEquals(Long.MIN_VALUE, reader.nextLong());
+ try {
+ reader.nextInt();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ assertEquals(Long.MAX_VALUE, reader.nextLong());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ /**
+ * This test fails because there's no double for 9223372036854775806, and
+ * our long parsing uses Double.parseDouble() for fractional values.
+ */
+ public void testHighPrecisionLong() throws IOException {
+ String json = "[9223372036854775806.000]";
+ JsonReader reader = new JsonReader(new StringReader(json));
+ reader.beginArray();
+ assertEquals(9223372036854775806L, reader.nextLong());
+ reader.endArray();
+ }
+
+ public void testNumberWithOctalPrefix() throws IOException {
+ String json = "[01]";
+ JsonReader reader = new JsonReader(new StringReader(json));
+ reader.beginArray();
+ try {
+ reader.nextInt();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ try {
+ reader.nextLong();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ try {
+ reader.nextDouble();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ assertEquals("01", reader.nextString());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testBooleans() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[true,false]"));
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ assertEquals(false, reader.nextBoolean());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testMixedCaseLiterals() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[True,TruE,False,FALSE,NULL,nulL]"));
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ assertEquals(true, reader.nextBoolean());
+ assertEquals(false, reader.nextBoolean());
+ assertEquals(false, reader.nextBoolean());
+ reader.nextNull();
+ reader.nextNull();
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testMissingValue() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\":}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ try {
+ reader.nextString();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testPrematureEndOfInput() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\":true,"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ assertEquals(true, reader.nextBoolean());
+ try {
+ reader.nextName();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testPrematurelyClosed() throws IOException {
+ try {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\":[]}"));
+ reader.beginObject();
+ reader.close();
+ reader.nextName();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+
+ try {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\":[]}"));
+ reader.close();
+ reader.beginObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+
+ try {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\":true}"));
+ reader.beginObject();
+ reader.nextName();
+ reader.peek();
+ reader.close();
+ reader.nextBoolean();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testNextFailuresDoNotAdvance() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\":true}"));
+ reader.beginObject();
+ try {
+ reader.nextString();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ assertEquals("a", reader.nextName());
+ try {
+ reader.nextName();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.beginArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.endArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.beginObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.endObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ assertEquals(true, reader.nextBoolean());
+ try {
+ reader.nextString();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.nextName();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.beginArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.endArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ reader.close();
+ }
+
+ public void testStringNullIsNotNull() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[\"null\"]"));
+ reader.beginArray();
+ try {
+ reader.nextNull();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testNullLiteralIsNotAString() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[null]"));
+ reader.beginArray();
+ try {
+ reader.nextString();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/util/JsonWriterTest.java b/core/tests/coretests/src/android/util/JsonWriterTest.java
new file mode 100644
index 0000000..0bf7e04
--- /dev/null
+++ b/core/tests/coretests/src/android/util/JsonWriterTest.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2010 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 android.util;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+public final class JsonWriterTest extends TestCase {
+
+ public void testWrongTopLevelType() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ try {
+ jsonWriter.value("a");
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testTwoNames() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.name("a");
+ try {
+ jsonWriter.name("a");
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testNameWithoutValue() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.name("a");
+ try {
+ jsonWriter.endObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testValueWithoutName() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ try {
+ jsonWriter.value(true);
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testMultipleTopLevelValues() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray().endArray();
+ try {
+ jsonWriter.beginArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testBadNestingObject() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.beginObject();
+ try {
+ jsonWriter.endArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testBadNestingArray() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.beginArray();
+ try {
+ jsonWriter.endObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testNullName() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ try {
+ jsonWriter.name(null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testNullStringValue() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.name("a");
+ jsonWriter.value(null);
+ jsonWriter.endObject();
+ assertEquals("{\"a\":null}", stringWriter.toString());
+ }
+
+ public void testNonFiniteDoubles() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ try {
+ jsonWriter.value(Double.NaN);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ jsonWriter.value(Double.NEGATIVE_INFINITY);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ jsonWriter.value(Double.POSITIVE_INFINITY);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testDoubles() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.value(-0.0);
+ jsonWriter.value(1.0);
+ jsonWriter.value(Double.MAX_VALUE);
+ jsonWriter.value(Double.MIN_VALUE);
+ jsonWriter.value(0.0);
+ jsonWriter.value(-0.5);
+ jsonWriter.value(Double.MIN_NORMAL);
+ jsonWriter.value(Math.PI);
+ jsonWriter.value(Math.E);
+ jsonWriter.endArray();
+ jsonWriter.close();
+ assertEquals("[-0.0,"
+ + "1.0,"
+ + "1.7976931348623157E308,"
+ + "4.9E-324,"
+ + "0.0,"
+ + "-0.5,"
+ + "2.2250738585072014E-308,"
+ + "3.141592653589793,"
+ + "2.718281828459045]", stringWriter.toString());
+ }
+
+ public void testLongs() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.value(0);
+ jsonWriter.value(1);
+ jsonWriter.value(-1);
+ jsonWriter.value(Long.MIN_VALUE);
+ jsonWriter.value(Long.MAX_VALUE);
+ jsonWriter.endArray();
+ jsonWriter.close();
+ assertEquals("[0,"
+ + "1,"
+ + "-1,"
+ + "-9223372036854775808,"
+ + "9223372036854775807]", stringWriter.toString());
+ }
+
+ public void testBooleans() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.value(true);
+ jsonWriter.value(false);
+ jsonWriter.endArray();
+ assertEquals("[true,false]", stringWriter.toString());
+ }
+
+ public void testNulls() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.nullValue();
+ jsonWriter.endArray();
+ assertEquals("[null]", stringWriter.toString());
+ }
+
+ public void testStrings() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.value("a");
+ jsonWriter.value("a\"");
+ jsonWriter.value("\"");
+ jsonWriter.value(":");
+ jsonWriter.value(",");
+ jsonWriter.value("\b");
+ jsonWriter.value("\f");
+ jsonWriter.value("\n");
+ jsonWriter.value("\r");
+ jsonWriter.value("\t");
+ jsonWriter.value(" ");
+ jsonWriter.value("\\");
+ jsonWriter.value("{");
+ jsonWriter.value("}");
+ jsonWriter.value("[");
+ jsonWriter.value("]");
+ jsonWriter.value("\0");
+ jsonWriter.value("\u0019");
+ jsonWriter.endArray();
+ assertEquals("[\"a\","
+ + "\"a\\\"\","
+ + "\"\\\"\","
+ + "\":\","
+ + "\",\","
+ + "\"\\b\","
+ + "\"\\f\","
+ + "\"\\n\","
+ + "\"\\r\","
+ + "\"\\t\","
+ + "\" \","
+ + "\"\\\\\","
+ + "\"{\","
+ + "\"}\","
+ + "\"[\","
+ + "\"]\","
+ + "\"\\u0000\","
+ + "\"\\u0019\"]", stringWriter.toString());
+ }
+
+ public void testEmptyArray() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.endArray();
+ assertEquals("[]", stringWriter.toString());
+ }
+
+ public void testEmptyObject() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.endObject();
+ assertEquals("{}", stringWriter.toString());
+ }
+
+ public void testObjectsInArrays() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.beginObject();
+ jsonWriter.name("a").value(5);
+ jsonWriter.name("b").value(false);
+ jsonWriter.endObject();
+ jsonWriter.beginObject();
+ jsonWriter.name("c").value(6);
+ jsonWriter.name("d").value(true);
+ jsonWriter.endObject();
+ jsonWriter.endArray();
+ assertEquals("[{\"a\":5,\"b\":false},"
+ + "{\"c\":6,\"d\":true}]", stringWriter.toString());
+ }
+
+ public void testArraysInObjects() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.name("a");
+ jsonWriter.beginArray();
+ jsonWriter.value(5);
+ jsonWriter.value(false);
+ jsonWriter.endArray();
+ jsonWriter.name("b");
+ jsonWriter.beginArray();
+ jsonWriter.value(6);
+ jsonWriter.value(true);
+ jsonWriter.endArray();
+ jsonWriter.endObject();
+ assertEquals("{\"a\":[5,false],"
+ + "\"b\":[6,true]}", stringWriter.toString());
+ }
+
+ public void testDeepNestingArrays() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ for (int i = 0; i < 20; i++) {
+ jsonWriter.beginArray();
+ }
+ for (int i = 0; i < 20; i++) {
+ jsonWriter.endArray();
+ }
+ assertEquals("[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]", stringWriter.toString());
+ }
+
+ public void testDeepNestingObjects() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ for (int i = 0; i < 20; i++) {
+ jsonWriter.name("a");
+ jsonWriter.beginObject();
+ }
+ for (int i = 0; i < 20; i++) {
+ jsonWriter.endObject();
+ }
+ jsonWriter.endObject();
+ assertEquals("{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":"
+ + "{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{"
+ + "}}}}}}}}}}}}}}}}}}}}}", stringWriter.toString());
+ }
+
+ public void testRepeatedName() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.name("a").value(true);
+ jsonWriter.name("a").value(false);
+ jsonWriter.endObject();
+ // JsonWriter doesn't attempt to detect duplicate names
+ assertEquals("{\"a\":true,\"a\":false}", stringWriter.toString());
+ }
+
+ public void testPrettyPrintObject() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.setIndentSpaces(3);
+
+ jsonWriter.beginObject();
+ jsonWriter.name("a").value(true);
+ jsonWriter.name("b").value(false);
+ jsonWriter.name("c").value(5.0);
+ jsonWriter.name("e").nullValue();
+ jsonWriter.name("f").beginArray();
+ jsonWriter.value(6.0);
+ jsonWriter.value(7.0);
+ jsonWriter.endArray();
+ jsonWriter.name("g").beginObject();
+ jsonWriter.name("h").value(8.0);
+ jsonWriter.name("i").value(9.0);
+ jsonWriter.endObject();
+ jsonWriter.endObject();
+
+ String expected = "{\n"
+ + " \"a\": true,\n"
+ + " \"b\": false,\n"
+ + " \"c\": 5.0,\n"
+ + " \"e\": null,\n"
+ + " \"f\": [\n"
+ + " 6.0,\n"
+ + " 7.0\n"
+ + " ],\n"
+ + " \"g\": {\n"
+ + " \"h\": 8.0,\n"
+ + " \"i\": 9.0\n"
+ + " }\n"
+ + "}";
+ assertEquals(expected, stringWriter.toString());
+ }
+
+ public void testPrettyPrintArray() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.setIndentSpaces(3);
+
+ jsonWriter.beginArray();
+ jsonWriter.value(true);
+ jsonWriter.value(false);
+ jsonWriter.value(5.0);
+ jsonWriter.nullValue();
+ jsonWriter.beginObject();
+ jsonWriter.name("a").value(6.0);
+ jsonWriter.name("b").value(7.0);
+ jsonWriter.endObject();
+ jsonWriter.beginArray();
+ jsonWriter.value(8.0);
+ jsonWriter.value(9.0);
+ jsonWriter.endArray();
+ jsonWriter.endArray();
+
+ String expected = "[\n"
+ + " true,\n"
+ + " false,\n"
+ + " 5.0,\n"
+ + " null,\n"
+ + " {\n"
+ + " \"a\": 6.0,\n"
+ + " \"b\": 7.0\n"
+ + " },\n"
+ + " [\n"
+ + " 8.0,\n"
+ + " 9.0\n"
+ + " ]\n"
+ + "]";
+ assertEquals(expected, stringWriter.toString());
+ }
+}
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index 281823a..cb2c6a2 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -593,5 +593,8 @@
private static native void native_transform(int nPath, int matrix);
private static native void finalizer(int nPath);
- private final int mNativePath;
+ /**
+ * @hide
+ */
+ public final int mNativePath;
}
diff --git a/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java b/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
index f4cf15c..b469d2a 100644
--- a/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
+++ b/graphics/java/android/graphics/drawable/shapes/RoundRectShape.java
@@ -57,13 +57,11 @@
*/
public RoundRectShape(float[] outerRadii, RectF inset,
float[] innerRadii) {
- if (outerRadii.length < 8) {
- throw new ArrayIndexOutOfBoundsException(
- "outer radii must have >= 8 values");
+ if (outerRadii != null && outerRadii.length < 8) {
+ throw new ArrayIndexOutOfBoundsException("outer radii must have >= 8 values");
}
if (innerRadii != null && innerRadii.length < 8) {
- throw new ArrayIndexOutOfBoundsException(
- "inner radii must have >= 8 values");
+ throw new ArrayIndexOutOfBoundsException("inner radii must have >= 8 values");
}
mOuterRadii = outerRadii;
mInset = inset;
@@ -97,8 +95,7 @@
r.right - mInset.right, r.bottom - mInset.bottom);
if (mInnerRect.width() < w && mInnerRect.height() < h) {
if (mInnerRadii != null) {
- mPath.addRoundRect(mInnerRect, mInnerRadii,
- Path.Direction.CCW);
+ mPath.addRoundRect(mInnerRect, mInnerRadii, Path.Direction.CCW);
} else {
mPath.addRect(mInnerRect, Path.Direction.CCW);
}
@@ -109,8 +106,8 @@
@Override
public RoundRectShape clone() throws CloneNotSupportedException {
RoundRectShape shape = (RoundRectShape) super.clone();
- shape.mOuterRadii = mOuterRadii.clone();
- shape.mInnerRadii = mInnerRadii.clone();
+ shape.mOuterRadii = mOuterRadii != null ? mOuterRadii.clone() : null;
+ shape.mInnerRadii = mInnerRadii != null ? mInnerRadii.clone() : null;
shape.mInset = new RectF(mInset);
shape.mInnerRect = new RectF(mInnerRect);
shape.mPath = new Path(mPath);
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 8f28612..0444964 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -9,6 +9,7 @@
OpenGLRenderer.cpp \
Patch.cpp \
PatchCache.cpp \
+ PathCache.cpp \
Program.cpp \
ProgramCache.cpp \
SkiaColorFilter.cpp \
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index feb45ec..dbea114 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -62,6 +62,27 @@
}
}
+void Font::measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y, Rect *bounds) {
+ int nPenX = x + glyph->mBitmapLeft;
+ int nPenY = y + glyph->mBitmapTop;
+
+ int width = (int) glyph->mBitmapWidth;
+ int height = (int) glyph->mBitmapHeight;
+
+ if(bounds->bottom > nPenY) {
+ bounds->bottom = nPenY;
+ }
+ if(bounds->left > nPenX) {
+ bounds->left = nPenX;
+ }
+ if(bounds->right < nPenX + width) {
+ bounds->right = nPenX + width;
+ }
+ if(bounds->top < nPenY + height) {
+ bounds->top = nPenY + height;
+ }
+}
+
void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y) {
int nPenX = x + glyph->mBitmapLeft;
int nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
@@ -88,22 +109,17 @@
uint32_t endX = glyph->mStartX + glyph->mBitmapWidth;
uint32_t endY = glyph->mStartY + glyph->mBitmapHeight;
- if(nPenX < 0 || nPenY < 0) {
- LOGE("Cannot render into a bitmap, some of the glyph is below zero");
- return;
- }
-
- if(nPenX + glyph->mBitmapWidth >= bitmapW || nPenY + glyph->mBitmapHeight >= bitmapH) {
- LOGE("Cannot render into a bitmap, dimentions too small");
- return;
- }
-
uint32_t cacheWidth = mState->getCacheWidth();
const uint8_t* cacheBuffer = mState->getTextTextureData();
- uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
+ uint32_t cacheX = 0, cacheY = 0;
+ int32_t bX = 0, bY = 0;
for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) {
for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) {
+ if(bX < 0 || bY < 0 || bX >= (int32_t)bitmapW || bY >= (int32_t)bitmapH) {
+ LOGE("Skipping invalid index");
+ continue;
+ }
uint8_t tempCol = cacheBuffer[cacheY * cacheWidth + cacheX];
bitmap[bY * bitmapW + bX] = tempCol;
}
@@ -127,7 +143,33 @@
}
void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
- int numGlyphs, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) {
+ int numGlyphs, int x, int y,
+ uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) {
+ if(bitmap != NULL && bitmapW > 0 && bitmapH > 0) {
+ renderUTF(paint, text, start, len, numGlyphs, x, y, BITMAP,
+ bitmap, bitmapW, bitmapH, NULL);
+ }
+ else {
+ renderUTF(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER,
+ NULL, 0, 0, NULL);
+ }
+
+}
+
+void Font::measureUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ int numGlyphs, Rect *bounds) {
+ if(bounds == NULL) {
+ LOGE("No return rectangle provided to measure text");
+ return;
+ }
+ bounds->set(1e6, -1e6, -1e6, 1e6);
+ renderUTF(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds);
+}
+
+void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ int numGlyphs, int x, int y, RenderMode mode,
+ uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
+ Rect *bounds) {
if (numGlyphs == 0 || text == NULL || len == 0) {
return;
}
@@ -152,11 +194,16 @@
// If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
if (cachedGlyph->mIsValid) {
- if(bitmap != NULL) {
- drawCachedGlyph(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH);
- }
- else {
+ switch(mode) {
+ case FRAMEBUFFER:
drawCachedGlyph(cachedGlyph, penX, penY);
+ break;
+ case BITMAP:
+ drawCachedGlyph(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH);
+ break;
+ case MEASURE:
+ measureCachedGlyph(cachedGlyph, penX, penY, bounds);
+ break;
}
}
@@ -569,6 +616,32 @@
precacheLatin(paint);
}
}
+FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
+ uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius) {
+
+ Rect bounds;
+ mCurrentFont->measureUTF(paint, text, startIndex, len, numGlyphs, &bounds);
+ uint32_t paddedWidth = (uint32_t)(bounds.right - bounds.left) + 2*radius;
+ uint32_t paddedHeight = (uint32_t)(bounds.top - bounds.bottom) + 2*radius;
+ uint8_t* dataBuffer = new uint8_t[paddedWidth * paddedHeight];
+ for(uint32_t i = 0; i < paddedWidth * paddedHeight; i ++) {
+ dataBuffer[i] = 0;
+ }
+ int penX = radius - bounds.left;
+ int penY = radius - bounds.bottom;
+
+ mCurrentFont->renderUTF(paint, text, startIndex, len, numGlyphs, penX, penY,
+ dataBuffer, paddedWidth, paddedHeight);
+ blurImage(dataBuffer, paddedWidth, paddedHeight, radius);
+
+ DropShadow image;
+ image.width = paddedWidth;
+ image.height = paddedHeight;
+ image.image = dataBuffer;
+ image.penX = penX;
+ image.penY = penY;
+ return image;
+}
void FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text,
uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y) {
@@ -598,11 +671,11 @@
// and sigma varies with radius.
// Based on some experimental radius values and sigma's
// we approximately fit sigma = f(radius) as
- // sigma = radius * 0.4 + 0.6
+ // sigma = radius * 0.3 + 0.6
// The larger the radius gets, the more our gaussian blur
// will resemble a box blur since with large sigma
// the gaussian curve begins to lose its shape
- float sigma = 0.4f * (float)radius + 0.6f;
+ float sigma = 0.3f * (float)radius + 0.6f;
// Now compute the coefficints
// We will store some redundant values to save some math during
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index 4cd902e..6346ded 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -45,6 +45,7 @@
/**
* Renders the specified string of text.
+ * If bitmap is specified, it will be used as the render target
*/
void renderUTF(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
int numGlyphs, int x, int y,
@@ -57,6 +58,20 @@
protected:
friend class FontRenderer;
+ enum RenderMode {
+ FRAMEBUFFER,
+ BITMAP,
+ MEASURE,
+ };
+
+ void renderUTF(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+ int numGlyphs, int x, int y, RenderMode mode,
+ uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
+ Rect *bounds);
+
+ void measureUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ int numGlyphs, Rect *bounds);
+
struct CachedGlyphInfo {
// Has the cache been invalidated?
bool mIsValid;
@@ -89,6 +104,7 @@
CachedGlyphInfo* cacheGlyph(SkPaint* paint, int32_t glyph);
void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo *glyph);
+ void measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y, Rect *bounds);
void drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y);
void drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y,
uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH);
@@ -112,6 +128,19 @@
void renderText(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
uint32_t len, int numGlyphs, int x, int y);
+ struct DropShadow {
+ uint32_t width;
+ uint32_t height;
+ uint8_t* image;
+ int32_t penX;
+ int32_t penY;
+ };
+
+ // After renderDropShadow returns, the called owns the memory in DropShadow.image
+ // and is responsible for releasing it when it's done with it
+ DropShadow renderDropShadow(SkPaint* paint, const char *text, uint32_t startIndex,
+ uint32_t len, int numGlyphs, uint32_t radius);
+
GLuint getTexture() {
checkInit();
return mTextureId;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index d694039..db8c863 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -37,7 +37,8 @@
///////////////////////////////////////////////////////////////////////////////
#define DEFAULT_TEXTURE_CACHE_SIZE 20.0f
-#define DEFAULT_LAYER_CACHE_SIZE 10.0f
+#define DEFAULT_LAYER_CACHE_SIZE 6.0f
+#define DEFAULT_PATH_CACHE_SIZE 6.0f
#define DEFAULT_PATCH_CACHE_SIZE 100
#define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
@@ -105,6 +106,7 @@
mTextureCache(MB(DEFAULT_TEXTURE_CACHE_SIZE)),
mLayerCache(MB(DEFAULT_LAYER_CACHE_SIZE)),
mGradientCache(MB(DEFAULT_GRADIENT_CACHE_SIZE)),
+ mPathCache(MB(DEFAULT_PATH_CACHE_SIZE)),
mPatchCache(DEFAULT_PATCH_CACHE_SIZE) {
LOGD("Create OpenGLRenderer");
@@ -125,11 +127,18 @@
if (property_get(PROPERTY_GRADIENT_CACHE_SIZE, property, NULL) > 0) {
LOGD(" Setting gradient cache size to %sMB", property);
- mLayerCache.setMaxSize(MB(atof(property)));
+ mGradientCache.setMaxSize(MB(atof(property)));
} else {
LOGD(" Using default gradient cache size of %.2fMB", DEFAULT_GRADIENT_CACHE_SIZE);
}
+ if (property_get(PROPERTY_PATH_CACHE_SIZE, property, NULL) > 0) {
+ LOGD(" Setting path cache size to %sMB", property);
+ mPathCache.setMaxSize(MB(atof(property)));
+ } else {
+ LOGD(" Using default path cache size of %.2fMB", DEFAULT_PATH_CACHE_SIZE);
+ }
+
mCurrentProgram = NULL;
mShader = NULL;
mColorFilter = NULL;
@@ -427,6 +436,8 @@
}
const Texture* texture = mTextureCache.get(bitmap);
+ const AutoTexture autoCleanup(texture);
+
drawTextureRect(left, top, right, bottom, texture, paint);
}
@@ -440,6 +451,8 @@
}
const Texture* texture = mTextureCache.get(bitmap);
+ const AutoTexture autoCleanup(texture);
+
drawTextureRect(r.left, r.top, r.right, r.bottom, texture, paint);
}
@@ -452,6 +465,7 @@
}
const Texture* texture = mTextureCache.get(bitmap);
+ const AutoTexture autoCleanup(texture);
const float width = texture->width;
const float height = texture->height;
@@ -475,6 +489,7 @@
}
const Texture* texture = mTextureCache.get(bitmap);
+ const AutoTexture autoCleanup(texture);
int alpha;
SkXfermode::Mode mode;
@@ -597,6 +612,74 @@
glDisableVertexAttribArray(texCoordsSlot);
}
+void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) {
+ GLuint textureUnit = 0;
+ glActiveTexture(gTextureUnits[textureUnit]);
+
+ const PathTexture* texture = mPathCache.get(path, paint);
+ const AutoTexture autoCleanup(texture);
+
+ int alpha;
+ SkXfermode::Mode mode;
+ getAlphaAndMode(paint, &alpha, &mode);
+
+ uint32_t color = paint->getColor();
+ const GLfloat a = alpha / 255.0f;
+ const GLfloat r = a * ((color >> 16) & 0xFF) / 255.0f;
+ const GLfloat g = a * ((color >> 8) & 0xFF) / 255.0f;
+ const GLfloat b = a * ((color ) & 0xFF) / 255.0f;
+
+ // Describe the required shaders
+ ProgramDescription description;
+ description.hasTexture = true;
+ description.hasAlpha8Texture = true;
+ if (mShader) {
+ mShader->describe(description, mExtensions);
+ }
+ if (mColorFilter) {
+ mColorFilter->describe(description, mExtensions);
+ }
+
+ // Build and use the appropriate shader
+ useProgram(mProgramCache.get(description));
+
+ // Setup the blending mode
+ chooseBlending(true, mode);
+ bindTexture(texture->id, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, textureUnit);
+ glUniform1i(mCurrentProgram->getUniform("sampler"), textureUnit);
+
+ int texCoordsSlot = mCurrentProgram->getAttrib("texCoords");
+ glEnableVertexAttribArray(texCoordsSlot);
+
+ // Setup attributes
+ glVertexAttribPointer(mCurrentProgram->position, 2, GL_FLOAT, GL_FALSE,
+ gMeshStride, &mMeshVertices[0].position[0]);
+ glVertexAttribPointer(texCoordsSlot, 2, GL_FLOAT, GL_FALSE,
+ gMeshStride, &mMeshVertices[0].texture[0]);
+
+ // Setup uniforms
+ mModelView.loadTranslate(texture->left - texture->offset,
+ texture->top - texture->offset, 0.0f);
+ mModelView.scale(texture->width, texture->height, 1.0f);
+ mCurrentProgram->set(mOrthoMatrix, mModelView, mSnapshot->transform);
+
+ glUniform4f(mCurrentProgram->color, r, g, b, a);
+
+ textureUnit++;
+ // Setup attributes and uniforms required by the shaders
+ if (mShader) {
+ mShader->setupProgram(mCurrentProgram, mModelView, *mSnapshot, &textureUnit);
+ }
+ if (mColorFilter) {
+ mColorFilter->setupProgram(mCurrentProgram);
+ }
+
+ // Draw the mesh
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
+
+ glDisableVertexAttribArray(texCoordsSlot);
+}
+
///////////////////////////////////////////////////////////////////////////////
// Shaders
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index d2a291f..76783e9 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -44,6 +44,7 @@
#include "ProgramCache.h"
#include "SkiaShader.h"
#include "SkiaColorFilter.h"
+#include "PathCache.h"
namespace android {
namespace uirenderer {
@@ -92,6 +93,7 @@
float right, float bottom, const SkPaint* paint);
void drawColor(int color, SkXfermode::Mode mode);
void drawRect(float left, float top, float right, float bottom, const SkPaint* paint);
+ void drawPath(SkPath* path, SkPaint* paint);
void resetShader();
void setupShader(SkiaShader* shader);
@@ -307,6 +309,7 @@
LayerCache mLayerCache;
GradientCache mGradientCache;
ProgramCache mProgramCache;
+ PathCache mPathCache;
PatchCache mPatchCache;
}; // class OpenGLRenderer
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
new file mode 100644
index 0000000..fa6ea25
--- /dev/null
+++ b/libs/hwui/PathCache.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <GLES2/gl2.h>
+
+#include <SkCanvas.h>
+#include <SkRect.h>
+
+#include "PathCache.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+PathCache::PathCache(uint32_t maxByteSize):
+ mCache(GenerationCache<PathCacheEntry, PathTexture*>::kUnlimitedCapacity),
+ mSize(0), mMaxSize(maxByteSize) {
+ mCache.setOnEntryRemovedListener(this);
+}
+
+PathCache::~PathCache() {
+ mCache.clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t PathCache::getSize() {
+ return mSize;
+}
+
+uint32_t PathCache::getMaxSize() {
+ return mMaxSize;
+}
+
+void PathCache::setMaxSize(uint32_t maxSize) {
+ mMaxSize = maxSize;
+ while (mSize > mMaxSize) {
+ mCache.removeOldest();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Callbacks
+///////////////////////////////////////////////////////////////////////////////
+
+void PathCache::operator()(PathCacheEntry& path, PathTexture*& texture) {
+ const uint32_t size = texture->width * texture->height;
+ mSize -= size;
+
+ if (texture) {
+ glDeleteTextures(1, &texture->id);
+ delete texture;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+PathTexture* PathCache::get(SkPath* path, SkPaint* paint) {
+ PathCacheEntry entry(path, paint);
+ PathTexture* texture = mCache.get(entry);
+
+ if (!texture) {
+ texture = addTexture(entry, path, paint);
+ } else if (path->getGenerationID() != texture->generation) {
+ mCache.remove(entry);
+ texture = addTexture(entry, path, paint);
+ }
+
+ return texture;
+}
+
+PathTexture* PathCache::addTexture(const PathCacheEntry& entry,
+ const SkPath *path, const SkPaint* paint) {
+ const SkRect& bounds = path->getBounds();
+ const float offset = entry.strokeWidth * 1.5f;
+ const uint32_t width = uint32_t(bounds.width() + offset * 2.0 + 0.5);
+ const uint32_t height = uint32_t(bounds.height() + offset * 2.0 + 0.5);
+
+ const uint32_t size = width * height;
+ // Don't even try to cache a bitmap that's bigger than the cache
+ if (size < mMaxSize) {
+ while (mSize + size > mMaxSize) {
+ mCache.removeOldest();
+ }
+ }
+
+ PathTexture* texture = new PathTexture;
+ texture->left = bounds.fLeft;
+ texture->top = bounds.fTop;
+ texture->offset = offset;
+ texture->width = width;
+ texture->height = height;
+ texture->generation = path->getGenerationID();
+
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kA8_Config, width, height);
+ bitmap.allocPixels();
+ bitmap.eraseColor(0);
+
+ SkCanvas canvas(bitmap);
+ canvas.translate(-bounds.fLeft + offset, -bounds.fTop + offset);
+ canvas.drawPath(*path, *paint);
+
+ generateTexture(bitmap, texture);
+
+ if (size < mMaxSize) {
+ mSize += size;
+ mCache.put(entry, texture);
+ } else {
+ texture->cleanup = true;
+ }
+
+ return texture;
+}
+
+void PathCache::clear() {
+ mCache.clear();
+}
+
+void PathCache::generateTexture(SkBitmap& bitmap, Texture* texture) {
+ SkAutoLockPixels alp(bitmap);
+ if (!bitmap.readyToDraw()) {
+ LOGE("Cannot generate texture from bitmap");
+ return;
+ }
+
+ glGenTextures(1, &texture->id);
+
+ glBindTexture(GL_TEXTURE_2D, texture->id);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap.bytesPerPixel());
+
+ texture->blend = true;
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, bitmap.rowBytesAsPixels(), texture->height, 0,
+ GL_ALPHA, GL_UNSIGNED_BYTE, bitmap.getPixels());
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
new file mode 100644
index 0000000..206fb49
--- /dev/null
+++ b/libs/hwui/PathCache.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#ifndef ANDROID_UI_PATH_CACHE_H
+#define ANDROID_UI_PATH_CACHE_H
+
+#include <SkBitmap.h>
+#include <SkPaint.h>
+#include <SkPath.h>
+
+#include "Texture.h"
+#include "GenerationCache.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Describe a path in the path cache.
+ */
+struct PathCacheEntry {
+ PathCacheEntry() {
+ path = NULL;
+ join = SkPaint::kDefault_Join;
+ cap = SkPaint::kDefault_Cap;
+ style = SkPaint::kFill_Style;
+ miter = 4.0f;
+ strokeWidth = 1.0f;
+ }
+
+ PathCacheEntry(const PathCacheEntry& entry):
+ path(entry.path), join(entry.join), cap(entry.cap),
+ style(entry.style), miter(entry.miter),
+ strokeWidth(entry.strokeWidth) {
+ }
+
+ PathCacheEntry(SkPath* path, SkPaint* paint) {
+ this->path = path;
+ join = paint->getStrokeJoin();
+ cap = paint->getStrokeCap();
+ miter = paint->getStrokeMiter();
+ strokeWidth = paint->getStrokeWidth();
+ style = paint->getStyle();
+ }
+
+ SkPath* path;
+ SkPaint::Join join;
+ SkPaint::Cap cap;
+ SkPaint::Style style;
+ float miter;
+ float strokeWidth;
+
+ bool operator<(const PathCacheEntry& rhs) const {
+ return memcmp(this, &rhs, sizeof(PathCacheEntry)) < 0;
+ }
+}; // struct PathCacheEntry
+
+/**
+ * Alpha texture used to represent a path.
+ */
+struct PathTexture: public Texture {
+ PathTexture(): Texture() {
+ }
+
+ /**
+ * Left coordinate of the path bounds.
+ */
+ float left;
+ /**
+ * Top coordinate of the path bounds.
+ */
+ float top;
+ /**
+ * Offset to draw the path at the correct origin.
+ */
+ float offset;
+}; // struct PathTexture
+
+/**
+ * A simple LRU path cache. The cache has a maximum size expressed in bytes.
+ * Any texture added to the cache causing the cache to grow beyond the maximum
+ * allowed size will also cause the oldest texture to be kicked out.
+ */
+class PathCache: public OnEntryRemoved<PathCacheEntry, PathTexture*> {
+public:
+ PathCache(uint32_t maxByteSize);
+ ~PathCache();
+
+ /**
+ * Used as a callback when an entry is removed from the cache.
+ * Do not invoke directly.
+ */
+ void operator()(PathCacheEntry& path, PathTexture*& texture);
+
+ /**
+ * Returns the texture associated with the specified path. If the texture
+ * cannot be found in the cache, a new texture is generated.
+ */
+ PathTexture* get(SkPath* path, SkPaint* paint);
+ /**
+ * Clears the cache. This causes all textures to be deleted.
+ */
+ void clear();
+
+ /**
+ * Sets the maximum size of the cache in bytes.
+ */
+ void setMaxSize(uint32_t maxSize);
+ /**
+ * Returns the maximum size of the cache in bytes.
+ */
+ uint32_t getMaxSize();
+ /**
+ * Returns the current size of the cache in bytes.
+ */
+ uint32_t getSize();
+
+private:
+ /**
+ * Generates the texture from a bitmap into the specified texture structure.
+ */
+ void generateTexture(SkBitmap& bitmap, Texture* texture);
+
+ PathTexture* addTexture(const PathCacheEntry& entry, const SkPath *path, const SkPaint* paint);
+
+ GenerationCache<PathCacheEntry, PathTexture*> mCache;
+
+ uint32_t mSize;
+ uint32_t mMaxSize;
+}; // class PathCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_PATH_CACHE_H
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 3205258..8a97b4c 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -90,7 +90,7 @@
};
const char* gFS_Main =
"\nvoid main(void) {\n"
- " vec4 fragColor;\n";
+ " lowp vec4 fragColor;\n";
const char* gFS_Main_FetchColor =
" fragColor = color;\n";
const char* gFS_Main_FetchTexture =
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 4f20a95..915b0be 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -26,10 +26,10 @@
#define PROPERTY_TEXTURE_CACHE_SIZE "ro.hwui.texture_cache_size"
#define PROPERTY_LAYER_CACHE_SIZE "ro.hwui.layer_cache_size"
#define PROPERTY_GRADIENT_CACHE_SIZE "ro.hwui.gradient_cache_size"
+#define PROPERTY_PATH_CACHE_SIZE "ro.hwui.path_cache_size"
// These properties are defined in pixels
#define PROPERTY_TEXT_CACHE_WIDTH "ro.hwui.text_cache_width"
#define PROPERTY_TEXT_CACHE_HEIGHT "ro.hwui.text_cache_height"
#endif // ANDROID_UI_PROPERTIES_H
-
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index fe4b54d..ffdb348 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -133,9 +133,9 @@
}
SkiaLinearGradientShader::~SkiaLinearGradientShader() {
- delete mBounds;
- delete mColors;
- delete mPositions;
+ delete[] mBounds;
+ delete[] mColors;
+ delete[] mPositions;
}
void SkiaLinearGradientShader::describe(ProgramDescription& description,
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index d37013d..90f548b 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -26,6 +26,10 @@
* Represents an OpenGL texture.
*/
struct Texture {
+ Texture() {
+ cleanup = false;
+ }
+
/**
* Name of the texture.
*/
@@ -46,8 +50,26 @@
* Height of the backing bitmap.
*/
uint32_t height;
+ /**
+ * Indicates whether this texture should be cleaned up after use.
+ */
+ bool cleanup;
}; // struct Texture
+class AutoTexture {
+public:
+ AutoTexture(const Texture* texture): mTexture(texture) { }
+ ~AutoTexture() {
+ if (mTexture && mTexture->cleanup) {
+ glDeleteTextures(1, &mTexture->id);
+ delete mTexture;
+ }
+ }
+
+private:
+ const Texture* mTexture;
+}; // class AutoTexture
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 4975edb..59903b7 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -93,11 +93,13 @@
if (size < mMaxSize) {
mSize += size;
mCache.put(bitmap, texture);
+ } else {
+ texture->cleanup = true;
}
} else if (bitmap->getGenerationID() != texture->generation) {
generateTexture(bitmap, texture, true);
}
- // TODO: Do something to destroy the texture object if it's too big for the cache
+
return texture;
}
diff --git a/libs/rs/rsMatrix.cpp b/libs/rs/rsMatrix.cpp
index 2f21405..94eef13 100644
--- a/libs/rs/rsMatrix.cpp
+++ b/libs/rs/rsMatrix.cpp
@@ -73,7 +73,7 @@
s = sinf(rot);
const float len = sqrtf(x*x + y*y + z*z);
- if (!(len != 1)) {
+ if (len != 1) {
const float recipLen = 1.f / len;
x *= recipLen;
y *= recipLen;
diff --git a/libs/rs/rsProgramFragment.cpp b/libs/rs/rsProgramFragment.cpp
index 056863c..a045043 100644
--- a/libs/rs/rsProgramFragment.cpp
+++ b/libs/rs/rsProgramFragment.cpp
@@ -169,7 +169,7 @@
void ProgramFragment::createShader()
{
mShader.setTo("precision mediump float;\n");
- mShader.append("varying vec4 varColor;\n");
+ mShader.append("varying lowp vec4 varColor;\n");
mShader.append("varying vec4 varTex0;\n");
mShader.append("uniform vec4 uni_Color;\n");
@@ -199,9 +199,9 @@
mShader.append("void main() {\n");
if (mVaryingColor) {
- mShader.append(" vec4 col = varColor;\n");
+ mShader.append(" lowp vec4 col = varColor;\n");
} else {
- mShader.append(" vec4 col = uni_Color;\n");
+ mShader.append(" lowp vec4 col = uni_Color;\n");
}
if (mTextureEnableMask) {
diff --git a/libs/rs/scriptc/rs_core.rsh b/libs/rs/scriptc/rs_core.rsh
index 01b039f..93e009a 100644
--- a/libs/rs/scriptc/rs_core.rsh
+++ b/libs/rs/scriptc/rs_core.rsh
@@ -260,7 +260,7 @@
s = sin(rot);
const float len = x*x + y*y + z*z;
- if (!(len != 1)) {
+ if (len != 1) {
const float recipLen = 1.f / sqrt(len);
x *= recipLen;
y *= recipLen;
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index 805f86b..5756e53 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -38,8 +38,8 @@
#include <surfaceflinger/ISurface.h>
#include <utils/Errors.h>
#include <sys/types.h>
-#include <unistd.h>
#include <ctype.h>
+#include <unistd.h>
#include "ARTPWriter.h"
@@ -906,7 +906,7 @@
}
status_t StagefrightRecorder::setupCameraSource() {
- if(!mCaptureTimeLapse) {
+ if (!mCaptureTimeLapse) {
// Dont clip for time lapse capture as encoder will have enough
// time to encode because of slow capture rate of time lapse.
clipVideoBitRate();
@@ -929,9 +929,10 @@
// Set the actual video recording frame size
CameraParameters params(mCamera->getParameters());
- // dont change the preview size for time lapse as mVideoWidth, mVideoHeight
- // may correspond to HD resolution not supported by video camera.
- if (!mCaptureTimeLapse) {
+ // dont change the preview size when using still camera for time lapse
+ // as mVideoWidth, mVideoHeight may correspond to HD resolution not
+ // supported by the video camera.
+ if (!(mCaptureTimeLapse && mUseStillCameraForTimeLapse)) {
params.setPreviewSize(mVideoWidth, mVideoHeight);
}
@@ -947,7 +948,7 @@
// Check on video frame size
int frameWidth = 0, frameHeight = 0;
newCameraParams.getPreviewSize(&frameWidth, &frameHeight);
- if (!mCaptureTimeLapse &&
+ if (!(mCaptureTimeLapse && mUseStillCameraForTimeLapse) &&
(frameWidth < 0 || frameWidth != mVideoWidth ||
frameHeight < 0 || frameHeight != mVideoHeight)) {
LOGE("Failed to set the video frame size to %dx%d",
diff --git a/packages/SystemUI/res/layout/status_bar_latest_event.xml b/packages/SystemUI/res/layout/status_bar_latest_event.xml
new file mode 100644
index 0000000..88d9739
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_latest_event.xml
@@ -0,0 +1,24 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="65sp"
+ android:orientation="vertical"
+ >
+
+ <com.android.systemui.statusbar.LatestItemView android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="64sp"
+ android:background="@android:drawable/status_bar_item_background"
+ android:focusable="true"
+ android:clickable="true"
+ android:paddingRight="6sp"
+ >
+ </com.android.systemui.statusbar.LatestItemView>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1sp"
+ android:background="@android:drawable/divider_horizontal_bright"
+ />
+
+</LinearLayout>
+
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 2299852..73fa93c 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -2412,8 +2412,17 @@
}
} else {
mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
- if (mActionBar != null && mActionBar.getTitle() == null) {
- mActionBar.setWindowTitle(mTitle);
+ if (mActionBar != null) {
+ if (mActionBar.getTitle() == null) {
+ mActionBar.setWindowTitle(mTitle);
+ }
+ // Post the panel invalidate for later; avoid application onCreateOptionsMenu
+ // being called in the middle of onCreate or similar.
+ mDecor.post(new Runnable() {
+ public void run() {
+ invalidatePanelMenu(FEATURE_ACTION_BAR);
+ }
+ });
}
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2412b7d..d44ce97 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -17,6 +17,7 @@
package com.android.server;
import com.android.server.am.ActivityManagerService;
+import com.android.server.sip.SipService;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.SamplingProfilerIntegration;
@@ -428,6 +429,13 @@
} catch (Throwable e) {
Slog.e(TAG, "Failure starting SamplingProfiler Service", e);
}
+
+ try {
+ Slog.i(TAG, "Sip Service");
+ ServiceManager.addService("sip", new SipService(context));
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failure starting SIP Service", e);
+ }
}
// make sure the ADB_ENABLED setting value matches the secure property value
diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java
index f8c9a61..ea6aa94 100755
--- a/services/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/java/com/android/server/location/GpsLocationProvider.java
@@ -177,7 +177,7 @@
// flags to trigger NTP or XTRA data download when network becomes available
// initialized to true so we do NTP and XTRA when the network comes up after booting
private boolean mInjectNtpTimePending = true;
- private boolean mDownloadXtraDataPending = true;
+ private boolean mDownloadXtraDataPending = false;
// true if GPS is navigating
private boolean mNavigating;
@@ -495,7 +495,7 @@
}
private void handleDownloadXtraData() {
- if (!mDownloadXtraDataPending) {
+ if (!mNetworkAvailable) {
// try again when network is up
mDownloadXtraDataPending = true;
return;
diff --git a/services/java/com/android/server/sip/SipHelper.java b/services/java/com/android/server/sip/SipHelper.java
new file mode 100644
index 0000000..83eeb84
--- /dev/null
+++ b/services/java/com/android/server/sip/SipHelper.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2010 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.sip;
+
+import gov.nist.javax.sip.SipStackExt;
+import gov.nist.javax.sip.clientauthutils.AccountManager;
+import gov.nist.javax.sip.clientauthutils.AuthenticationHelper;
+
+import android.net.sip.SessionDescription;
+import android.net.sip.SipProfile;
+import android.util.Log;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.EventObject;
+import java.util.List;
+import javax.sip.ClientTransaction;
+import javax.sip.Dialog;
+import javax.sip.DialogTerminatedEvent;
+import javax.sip.InvalidArgumentException;
+import javax.sip.ListeningPoint;
+import javax.sip.PeerUnavailableException;
+import javax.sip.RequestEvent;
+import javax.sip.ResponseEvent;
+import javax.sip.ServerTransaction;
+import javax.sip.SipException;
+import javax.sip.SipFactory;
+import javax.sip.SipProvider;
+import javax.sip.SipStack;
+import javax.sip.Transaction;
+import javax.sip.TransactionAlreadyExistsException;
+import javax.sip.TransactionTerminatedEvent;
+import javax.sip.TransactionUnavailableException;
+import javax.sip.TransactionState;
+import javax.sip.address.Address;
+import javax.sip.address.AddressFactory;
+import javax.sip.address.SipURI;
+import javax.sip.header.CSeqHeader;
+import javax.sip.header.CallIdHeader;
+import javax.sip.header.ContactHeader;
+import javax.sip.header.FromHeader;
+import javax.sip.header.Header;
+import javax.sip.header.HeaderFactory;
+import javax.sip.header.MaxForwardsHeader;
+import javax.sip.header.ToHeader;
+import javax.sip.header.ViaHeader;
+import javax.sip.message.Message;
+import javax.sip.message.MessageFactory;
+import javax.sip.message.Request;
+import javax.sip.message.Response;
+
+/**
+ * Helper class for holding SIP stack related classes and for various low-level
+ * SIP tasks like sending messages.
+ */
+class SipHelper {
+ private static final String TAG = SipHelper.class.getSimpleName();
+
+ private SipStack mSipStack;
+ private SipProvider mSipProvider;
+ private AddressFactory mAddressFactory;
+ private HeaderFactory mHeaderFactory;
+ private MessageFactory mMessageFactory;
+
+ public SipHelper(SipStack sipStack, SipProvider sipProvider)
+ throws PeerUnavailableException {
+ mSipStack = sipStack;
+ mSipProvider = sipProvider;
+
+ SipFactory sipFactory = SipFactory.getInstance();
+ mAddressFactory = sipFactory.createAddressFactory();
+ mHeaderFactory = sipFactory.createHeaderFactory();
+ mMessageFactory = sipFactory.createMessageFactory();
+ }
+
+ private FromHeader createFromHeader(SipProfile profile, String tag)
+ throws ParseException {
+ return mHeaderFactory.createFromHeader(profile.getSipAddress(), tag);
+ }
+
+ private ToHeader createToHeader(SipProfile profile) throws ParseException {
+ return createToHeader(profile, null);
+ }
+
+ private ToHeader createToHeader(SipProfile profile, String tag)
+ throws ParseException {
+ return mHeaderFactory.createToHeader(profile.getSipAddress(), tag);
+ }
+
+ private CallIdHeader createCallIdHeader() {
+ return mSipProvider.getNewCallId();
+ }
+
+ private CSeqHeader createCSeqHeader(String method)
+ throws ParseException, InvalidArgumentException {
+ long sequence = (long) (Math.random() * 10000);
+ return mHeaderFactory.createCSeqHeader(sequence, method);
+ }
+
+ private MaxForwardsHeader createMaxForwardsHeader()
+ throws InvalidArgumentException {
+ return mHeaderFactory.createMaxForwardsHeader(70);
+ }
+
+ private MaxForwardsHeader createMaxForwardsHeader(int max)
+ throws InvalidArgumentException {
+ return mHeaderFactory.createMaxForwardsHeader(max);
+ }
+
+ private ListeningPoint getListeningPoint() throws SipException {
+ ListeningPoint lp = mSipProvider.getListeningPoint(ListeningPoint.UDP);
+ if (lp == null) lp = mSipProvider.getListeningPoint(ListeningPoint.TCP);
+ if (lp == null) {
+ ListeningPoint[] lps = mSipProvider.getListeningPoints();
+ if ((lps != null) && (lps.length > 0)) lp = lps[0];
+ }
+ if (lp == null) {
+ throw new SipException("no listening point is available");
+ }
+ return lp;
+ }
+
+ private List<ViaHeader> createViaHeaders()
+ throws ParseException, SipException {
+ List<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(1);
+ ListeningPoint lp = getListeningPoint();
+ ViaHeader viaHeader = mHeaderFactory.createViaHeader(lp.getIPAddress(),
+ lp.getPort(), lp.getTransport(), null);
+ viaHeader.setRPort();
+ viaHeaders.add(viaHeader);
+ return viaHeaders;
+ }
+
+ private ContactHeader createContactHeader(SipProfile profile)
+ throws ParseException, SipException {
+ ListeningPoint lp = getListeningPoint();
+ SipURI contactURI =
+ createSipUri(profile.getUserName(), profile.getProtocol(), lp);
+
+ Address contactAddress = mAddressFactory.createAddress(contactURI);
+ contactAddress.setDisplayName(profile.getDisplayName());
+
+ return mHeaderFactory.createContactHeader(contactAddress);
+ }
+
+ private ContactHeader createWildcardContactHeader() {
+ ContactHeader contactHeader = mHeaderFactory.createContactHeader();
+ contactHeader.setWildCard();
+ return contactHeader;
+ }
+
+ private SipURI createSipUri(String username, String transport,
+ ListeningPoint lp) throws ParseException {
+ SipURI uri = mAddressFactory.createSipURI(username, lp.getIPAddress());
+ try {
+ uri.setPort(lp.getPort());
+ uri.setTransportParam(transport);
+ } catch (InvalidArgumentException e) {
+ throw new RuntimeException(e);
+ }
+ return uri;
+ }
+
+ public ClientTransaction sendKeepAlive(SipProfile userProfile, String tag)
+ throws SipException {
+ try {
+ Request request = createRequest(Request.OPTIONS, userProfile, tag);
+
+ ClientTransaction clientTransaction =
+ mSipProvider.getNewClientTransaction(request);
+ clientTransaction.sendRequest();
+ return clientTransaction;
+ } catch (Exception e) {
+ throw new SipException("sendKeepAlive()", e);
+ }
+ }
+
+ public ClientTransaction sendRegister(SipProfile userProfile, String tag,
+ int expiry) throws SipException {
+ try {
+ Request request = createRequest(Request.REGISTER, userProfile, tag);
+ if (expiry == 0) {
+ // remove all previous registrations by wildcard
+ // rfc3261#section-10.2.2
+ request.addHeader(createWildcardContactHeader());
+ } else {
+ request.addHeader(createContactHeader(userProfile));
+ }
+ request.addHeader(mHeaderFactory.createExpiresHeader(expiry));
+
+ ClientTransaction clientTransaction =
+ mSipProvider.getNewClientTransaction(request);
+ clientTransaction.sendRequest();
+ return clientTransaction;
+ } catch (ParseException e) {
+ throw new SipException("sendRegister()", e);
+ }
+ }
+
+ private Request createRequest(String requestType, SipProfile userProfile,
+ String tag) throws ParseException, SipException {
+ FromHeader fromHeader = createFromHeader(userProfile, tag);
+ ToHeader toHeader = createToHeader(userProfile);
+ SipURI requestURI = mAddressFactory.createSipURI("sip:"
+ + userProfile.getSipDomain());
+ List<ViaHeader> viaHeaders = createViaHeaders();
+ CallIdHeader callIdHeader = createCallIdHeader();
+ CSeqHeader cSeqHeader = createCSeqHeader(requestType);
+ MaxForwardsHeader maxForwards = createMaxForwardsHeader();
+ Request request = mMessageFactory.createRequest(requestURI,
+ requestType, callIdHeader, cSeqHeader, fromHeader,
+ toHeader, viaHeaders, maxForwards);
+ Header userAgentHeader = mHeaderFactory.createHeader("User-Agent",
+ "SIPAUA/0.1.001");
+ request.addHeader(userAgentHeader);
+ return request;
+ }
+
+ public ClientTransaction handleChallenge(ResponseEvent responseEvent,
+ AccountManager accountManager) throws SipException {
+ AuthenticationHelper authenticationHelper =
+ ((SipStackExt) mSipStack).getAuthenticationHelper(
+ accountManager, mHeaderFactory);
+ ClientTransaction tid = responseEvent.getClientTransaction();
+ ClientTransaction ct = authenticationHelper.handleChallenge(
+ responseEvent.getResponse(), tid, mSipProvider, 5);
+ ct.sendRequest();
+ return ct;
+ }
+
+ public ClientTransaction sendInvite(SipProfile caller, SipProfile callee,
+ SessionDescription sessionDescription, String tag)
+ throws SipException {
+ try {
+ FromHeader fromHeader = createFromHeader(caller, tag);
+ ToHeader toHeader = createToHeader(callee);
+ SipURI requestURI = callee.getUri();
+ List<ViaHeader> viaHeaders = createViaHeaders();
+ CallIdHeader callIdHeader = createCallIdHeader();
+ CSeqHeader cSeqHeader = createCSeqHeader(Request.INVITE);
+ MaxForwardsHeader maxForwards = createMaxForwardsHeader();
+
+ Request request = mMessageFactory.createRequest(requestURI,
+ Request.INVITE, callIdHeader, cSeqHeader, fromHeader,
+ toHeader, viaHeaders, maxForwards);
+
+ request.addHeader(createContactHeader(caller));
+ request.setContent(sessionDescription.getContent(),
+ mHeaderFactory.createContentTypeHeader(
+ "application", sessionDescription.getType()));
+
+ ClientTransaction clientTransaction =
+ mSipProvider.getNewClientTransaction(request);
+ clientTransaction.sendRequest();
+ return clientTransaction;
+ } catch (ParseException e) {
+ throw new SipException("sendInvite()", e);
+ }
+ }
+
+ public ClientTransaction sendReinvite(Dialog dialog,
+ SessionDescription sessionDescription) throws SipException {
+ try {
+ Request request = dialog.createRequest(Request.INVITE);
+ request.setContent(sessionDescription.getContent(),
+ mHeaderFactory.createContentTypeHeader(
+ "application", sessionDescription.getType()));
+
+ ClientTransaction clientTransaction =
+ mSipProvider.getNewClientTransaction(request);
+ dialog.sendRequest(clientTransaction);
+ return clientTransaction;
+ } catch (ParseException e) {
+ throw new SipException("sendReinvite()", e);
+ }
+ }
+
+ private ServerTransaction getServerTransaction(RequestEvent event)
+ throws SipException {
+ ServerTransaction transaction = event.getServerTransaction();
+ if (transaction == null) {
+ Request request = event.getRequest();
+ return mSipProvider.getNewServerTransaction(request);
+ } else {
+ return transaction;
+ }
+ }
+
+ /**
+ * @param event the INVITE request event
+ */
+ public ServerTransaction sendRinging(RequestEvent event, String tag)
+ throws SipException {
+ try {
+ Request request = event.getRequest();
+ ServerTransaction transaction = getServerTransaction(event);
+
+ Response response = mMessageFactory.createResponse(Response.RINGING,
+ request);
+
+ ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME);
+ toHeader.setTag(tag);
+ response.addHeader(toHeader);
+ transaction.sendResponse(response);
+ return transaction;
+ } catch (ParseException e) {
+ throw new SipException("sendRinging()", e);
+ }
+ }
+
+ /**
+ * @param event the INVITE request event
+ */
+ public ServerTransaction sendInviteOk(RequestEvent event,
+ SipProfile localProfile, SessionDescription sessionDescription,
+ ServerTransaction inviteTransaction)
+ throws SipException {
+ try {
+ Request request = event.getRequest();
+ Response response = mMessageFactory.createResponse(Response.OK,
+ request);
+ response.addHeader(createContactHeader(localProfile));
+ response.setContent(sessionDescription.getContent(),
+ mHeaderFactory.createContentTypeHeader(
+ "application", sessionDescription.getType()));
+
+ if (inviteTransaction == null) {
+ inviteTransaction = getServerTransaction(event);
+ }
+ if (inviteTransaction.getState() != TransactionState.COMPLETED) {
+ inviteTransaction.sendResponse(response);
+ }
+
+ return inviteTransaction;
+ } catch (ParseException e) {
+ throw new SipException("sendInviteOk()", e);
+ }
+ }
+
+ public void sendInviteBusyHere(RequestEvent event,
+ ServerTransaction inviteTransaction) throws SipException {
+ try {
+ Request request = event.getRequest();
+ Response response = mMessageFactory.createResponse(
+ Response.BUSY_HERE, request);
+
+ if (inviteTransaction.getState() != TransactionState.COMPLETED) {
+ inviteTransaction.sendResponse(response);
+ }
+ } catch (ParseException e) {
+ throw new SipException("sendInviteBusyHere()", e);
+ }
+ }
+
+ /**
+ * @param event the INVITE ACK request event
+ */
+ public void sendInviteAck(ResponseEvent event, Dialog dialog)
+ throws SipException {
+ Response response = event.getResponse();
+ long cseq = ((CSeqHeader) response.getHeader(CSeqHeader.NAME))
+ .getSeqNumber();
+ dialog.sendAck(dialog.createAck(cseq));
+ }
+
+ public void sendBye(Dialog dialog) throws SipException {
+ Request byeRequest = dialog.createRequest(Request.BYE);
+ Log.d(TAG, "send BYE: " + byeRequest);
+ dialog.sendRequest(mSipProvider.getNewClientTransaction(byeRequest));
+ }
+
+ public void sendCancel(ClientTransaction inviteTransaction)
+ throws SipException {
+ Request cancelRequest = inviteTransaction.createCancel();
+ mSipProvider.getNewClientTransaction(cancelRequest).sendRequest();
+ }
+
+ public void sendResponse(RequestEvent event, int responseCode)
+ throws SipException {
+ try {
+ getServerTransaction(event).sendResponse(
+ mMessageFactory.createResponse(
+ responseCode, event.getRequest()));
+ } catch (ParseException e) {
+ throw new SipException("sendResponse()", e);
+ }
+ }
+
+ public void sendInviteRequestTerminated(Request inviteRequest,
+ ServerTransaction inviteTransaction) throws SipException {
+ try {
+ inviteTransaction.sendResponse(mMessageFactory.createResponse(
+ Response.REQUEST_TERMINATED, inviteRequest));
+ } catch (ParseException e) {
+ throw new SipException("sendInviteRequestTerminated()", e);
+ }
+ }
+
+ public static String getCallId(EventObject event) {
+ if (event == null) return null;
+ if (event instanceof RequestEvent) {
+ return getCallId(((RequestEvent) event).getRequest());
+ } else if (event instanceof ResponseEvent) {
+ return getCallId(((ResponseEvent) event).getResponse());
+ } else if (event instanceof DialogTerminatedEvent) {
+ Dialog dialog = ((DialogTerminatedEvent) event).getDialog();
+ return getCallId(((DialogTerminatedEvent) event).getDialog());
+ } else if (event instanceof TransactionTerminatedEvent) {
+ TransactionTerminatedEvent e = (TransactionTerminatedEvent) event;
+ return getCallId(e.isServerTransaction()
+ ? e.getServerTransaction()
+ : e.getClientTransaction());
+ } else {
+ Object source = event.getSource();
+ if (source instanceof Transaction) {
+ return getCallId(((Transaction) source));
+ } else if (source instanceof Dialog) {
+ return getCallId((Dialog) source);
+ }
+ }
+ return "";
+ }
+
+ public static String getCallId(Transaction transaction) {
+ return ((transaction != null) ? getCallId(transaction.getRequest())
+ : "");
+ }
+
+ private static String getCallId(Message message) {
+ CallIdHeader callIdHeader =
+ (CallIdHeader) message.getHeader(CallIdHeader.NAME);
+ return callIdHeader.getCallId();
+ }
+
+ private static String getCallId(Dialog dialog) {
+ return dialog.getCallId().getCallId();
+ }
+}
diff --git a/services/java/com/android/server/sip/SipService.java b/services/java/com/android/server/sip/SipService.java
new file mode 100644
index 0000000..1142136
--- /dev/null
+++ b/services/java/com/android/server/sip/SipService.java
@@ -0,0 +1,1091 @@
+/*
+ * Copyright (C) 2010, 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.sip;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.sip.ISipService;
+import android.net.sip.ISipSession;
+import android.net.sip.ISipSessionListener;
+import android.net.sip.SipManager;
+import android.net.sip.SipProfile;
+import android.net.sip.SipSessionAdapter;
+import android.net.sip.SipSessionState;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.TreeSet;
+import javax.sip.SipException;
+
+/**
+ */
+public final class SipService extends ISipService.Stub {
+ private static final String TAG = "SipService";
+ private static final int EXPIRY_TIME = 3600;
+ private static final int SHORT_EXPIRY_TIME = 10;
+ private static final int MIN_EXPIRY_TIME = 60;
+
+ private Context mContext;
+ private String mLocalIp;
+ private String mNetworkType;
+ private boolean mConnected;
+ private WakeupTimer mTimer;
+ private WifiManager.WifiLock mWifiLock;
+
+ // SipProfile URI --> group
+ private Map<String, SipSessionGroupExt> mSipGroups =
+ new HashMap<String, SipSessionGroupExt>();
+
+ // session ID --> session
+ private Map<String, ISipSession> mPendingSessions =
+ new HashMap<String, ISipSession>();
+
+ private ConnectivityReceiver mConnectivityReceiver;
+
+ public SipService(Context context) {
+ Log.v(TAG, " service started!");
+ mContext = context;
+ mConnectivityReceiver = new ConnectivityReceiver();
+ context.registerReceiver(mConnectivityReceiver,
+ new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
+
+ mTimer = new WakeupTimer(context);
+ }
+
+ public synchronized SipProfile[] getListOfProfiles() {
+ SipProfile[] profiles = new SipProfile[mSipGroups.size()];
+ int i = 0;
+ for (SipSessionGroupExt group : mSipGroups.values()) {
+ profiles[i++] = group.getLocalProfile();
+ }
+ return profiles;
+ }
+
+ public void open(SipProfile localProfile) {
+ if (localProfile.getAutoRegistration()) {
+ openToReceiveCalls(localProfile);
+ } else {
+ openToMakeCalls(localProfile);
+ }
+ }
+
+ private void openToMakeCalls(SipProfile localProfile) {
+ try {
+ createGroup(localProfile);
+ } catch (SipException e) {
+ Log.e(TAG, "openToMakeCalls()", e);
+ // TODO: how to send the exception back
+ }
+ }
+
+ private void openToReceiveCalls(SipProfile localProfile) {
+ open3(localProfile, SipManager.SIP_INCOMING_CALL_ACTION, null);
+ }
+
+ public synchronized void open3(SipProfile localProfile,
+ String incomingCallBroadcastAction, ISipSessionListener listener) {
+ if (TextUtils.isEmpty(incomingCallBroadcastAction)) {
+ throw new RuntimeException(
+ "empty broadcast action for incoming call");
+ }
+ Log.v(TAG, "open3: " + localProfile.getUriString() + ": "
+ + incomingCallBroadcastAction + ": " + listener);
+ try {
+ SipSessionGroupExt group = createGroup(localProfile,
+ incomingCallBroadcastAction, listener);
+ if (localProfile.getAutoRegistration()) {
+ group.openToReceiveCalls();
+ if (isWifiOn()) grabWifiLock();
+ }
+ } catch (SipException e) {
+ Log.e(TAG, "openToReceiveCalls()", e);
+ // TODO: how to send the exception back
+ }
+ }
+
+ public synchronized void close(String localProfileUri) {
+ SipSessionGroupExt group = mSipGroups.remove(localProfileUri);
+ if (group != null) {
+ notifyProfileRemoved(group.getLocalProfile());
+ group.closeToNotReceiveCalls();
+ if (isWifiOn() && !anyOpened()) releaseWifiLock();
+ }
+ }
+
+ public synchronized boolean isOpened(String localProfileUri) {
+ SipSessionGroupExt group = mSipGroups.get(localProfileUri);
+ return ((group != null) ? group.isOpened() : false);
+ }
+
+ public synchronized boolean isRegistered(String localProfileUri) {
+ SipSessionGroupExt group = mSipGroups.get(localProfileUri);
+ return ((group != null) ? group.isRegistered() : false);
+ }
+
+ public synchronized void setRegistrationListener(String localProfileUri,
+ ISipSessionListener listener) {
+ SipSessionGroupExt group = mSipGroups.get(localProfileUri);
+ if (group != null) group.setListener(listener);
+ }
+
+ public synchronized ISipSession createSession(SipProfile localProfile,
+ ISipSessionListener listener) {
+ if (!mConnected) return null;
+ try {
+ SipSessionGroupExt group = createGroup(localProfile);
+ return group.createSession(listener);
+ } catch (SipException e) {
+ Log.w(TAG, "createSession()", e);
+ return null;
+ }
+ }
+
+ public synchronized ISipSession getPendingSession(String callId) {
+ if (callId == null) return null;
+ return mPendingSessions.get(callId);
+ }
+
+ private String determineLocalIp() {
+ try {
+ DatagramSocket s = new DatagramSocket();
+ s.connect(InetAddress.getByName("192.168.1.1"), 80);
+ return s.getLocalAddress().getHostAddress();
+ } catch (IOException e) {
+ Log.w(TAG, "determineLocalIp()", e);
+ // dont do anything; there should be a connectivity change going
+ return null;
+ }
+ }
+
+ private SipSessionGroupExt createGroup(SipProfile localProfile)
+ throws SipException {
+ String key = localProfile.getUriString();
+ SipSessionGroupExt group = mSipGroups.get(key);
+ if (group == null) {
+ group = new SipSessionGroupExt(localProfile, null, null);
+ mSipGroups.put(key, group);
+ notifyProfileAdded(localProfile);
+ }
+ return group;
+ }
+
+ private SipSessionGroupExt createGroup(SipProfile localProfile,
+ String incomingCallBroadcastAction, ISipSessionListener listener)
+ throws SipException {
+ String key = localProfile.getUriString();
+ SipSessionGroupExt group = mSipGroups.get(key);
+ if (group != null) {
+ group.setIncomingCallBroadcastAction(
+ incomingCallBroadcastAction);
+ group.setListener(listener);
+ } else {
+ group = new SipSessionGroupExt(localProfile,
+ incomingCallBroadcastAction, listener);
+ mSipGroups.put(key, group);
+ notifyProfileAdded(localProfile);
+ }
+ return group;
+ }
+
+ private void notifyProfileAdded(SipProfile localProfile) {
+ Log.d(TAG, "notify: profile added: " + localProfile);
+ Intent intent = new Intent(SipManager.SIP_ADD_PHONE_ACTION);
+ intent.putExtra(SipManager.LOCAL_URI_KEY, localProfile.getUriString());
+ mContext.sendBroadcast(intent);
+ }
+
+ private void notifyProfileRemoved(SipProfile localProfile) {
+ Log.d(TAG, "notify: profile removed: " + localProfile);
+ Intent intent = new Intent(SipManager.SIP_REMOVE_PHONE_ACTION);
+ intent.putExtra(SipManager.LOCAL_URI_KEY, localProfile.getUriString());
+ mContext.sendBroadcast(intent);
+ }
+
+ private boolean anyOpened() {
+ for (SipSessionGroupExt group : mSipGroups.values()) {
+ if (group.isOpened()) return true;
+ }
+ return false;
+ }
+
+ private void grabWifiLock() {
+ if (mWifiLock == null) {
+ Log.v(TAG, "acquire wifi lock");
+ mWifiLock = ((WifiManager)
+ mContext.getSystemService(Context.WIFI_SERVICE))
+ .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
+ mWifiLock.acquire();
+ }
+ }
+
+ private void releaseWifiLock() {
+ if (mWifiLock != null) {
+ Log.v(TAG, "release wifi lock");
+ mWifiLock.release();
+ mWifiLock = null;
+ }
+ }
+
+ private boolean isWifiOn() {
+ return "WIFI".equalsIgnoreCase(mNetworkType);
+ //return (mConnected && "WIFI".equalsIgnoreCase(mNetworkType));
+ }
+
+ private synchronized void onConnectivityChanged(
+ String type, boolean connected) {
+ Log.v(TAG, "onConnectivityChanged(): "
+ + mNetworkType + (mConnected? " CONNECTED" : " DISCONNECTED")
+ + " --> " + type + (connected? " CONNECTED" : " DISCONNECTED"));
+
+ boolean sameType = type.equals(mNetworkType);
+ if (!sameType && !connected) return;
+
+ boolean wasWifi = "WIFI".equalsIgnoreCase(mNetworkType);
+ boolean isWifi = "WIFI".equalsIgnoreCase(type);
+ boolean wifiOff = (isWifi && !connected) || (wasWifi && !sameType);
+ boolean wifiOn = isWifi && connected;
+ if (wifiOff) {
+ releaseWifiLock();
+ } else if (wifiOn) {
+ if (anyOpened()) grabWifiLock();
+ }
+
+ try {
+ boolean wasConnected = mConnected;
+ mNetworkType = type;
+ mConnected = connected;
+
+ if (wasConnected) {
+ mLocalIp = null;
+ for (SipSessionGroupExt group : mSipGroups.values()) {
+ group.onConnectivityChanged(false);
+ }
+ }
+
+ if (connected) {
+ mLocalIp = determineLocalIp();
+ for (SipSessionGroupExt group : mSipGroups.values()) {
+ group.onConnectivityChanged(true);
+ }
+ }
+
+ } catch (SipException e) {
+ Log.e(TAG, "onConnectivityChanged()", e);
+ }
+ }
+
+ private synchronized void addPendingSession(ISipSession session) {
+ try {
+ mPendingSessions.put(session.getCallId(), session);
+ } catch (RemoteException e) {
+ // should not happen with a local call
+ Log.e(TAG, "addPendingSession()", e);
+ }
+ }
+
+ private class SipSessionGroupExt extends SipSessionAdapter {
+ private SipSessionGroup mSipGroup;
+ private String mIncomingCallBroadcastAction;
+ private boolean mOpened;
+
+ private AutoRegistrationProcess mAutoRegistration =
+ new AutoRegistrationProcess();
+
+ public SipSessionGroupExt(SipProfile localProfile,
+ String incomingCallBroadcastAction,
+ ISipSessionListener listener) throws SipException {
+ String password = localProfile.getPassword();
+ SipProfile p = duplicate(localProfile);
+ mSipGroup = createSipSessionGroup(mLocalIp, p, password);
+ mIncomingCallBroadcastAction = incomingCallBroadcastAction;
+ mAutoRegistration.setListener(listener);
+ }
+
+ public SipProfile getLocalProfile() {
+ return mSipGroup.getLocalProfile();
+ }
+
+ // network connectivity is tricky because network can be disconnected
+ // at any instant so need to deal with exceptions carefully even when
+ // you think you are connected
+ private SipSessionGroup createSipSessionGroup(String localIp,
+ SipProfile localProfile, String password) throws SipException {
+ try {
+ return new SipSessionGroup(localIp, localProfile, password);
+ } catch (IOException e) {
+ // network disconnected
+ Log.w(TAG, "createSipSessionGroup(): network disconnected?");
+ if (localIp != null) {
+ return createSipSessionGroup(null, localProfile, password);
+ } else {
+ // recursive
+ Log.wtf(TAG, "impossible!");
+ throw new RuntimeException("createSipSessionGroup");
+ }
+ }
+ }
+
+ private SipProfile duplicate(SipProfile p) {
+ try {
+ return new SipProfile.Builder(p.getUserName(), p.getSipDomain())
+ .setProfileName(p.getProfileName())
+ .setPassword("*")
+ .setPort(p.getPort())
+ .setProtocol(p.getProtocol())
+ .setOutboundProxy(p.getProxyAddress())
+ .setSendKeepAlive(p.getSendKeepAlive())
+ .setAutoRegistration(p.getAutoRegistration())
+ .setDisplayName(p.getDisplayName())
+ .build();
+ } catch (Exception e) {
+ Log.wtf(TAG, "duplicate()", e);
+ throw new RuntimeException("duplicate profile", e);
+ }
+ }
+
+ public void setListener(ISipSessionListener listener) {
+ mAutoRegistration.setListener(listener);
+ }
+
+ public void setIncomingCallBroadcastAction(String action) {
+ mIncomingCallBroadcastAction = action;
+ }
+
+ public void openToReceiveCalls() throws SipException {
+ mOpened = true;
+ if (mConnected) {
+ mSipGroup.openToReceiveCalls(this);
+ mAutoRegistration.start(mSipGroup);
+ }
+ Log.v(TAG, " openToReceiveCalls: " + getUri() + ": "
+ + mIncomingCallBroadcastAction);
+ }
+
+ public void onConnectivityChanged(boolean connected)
+ throws SipException {
+ if (connected) {
+ resetGroup(mLocalIp);
+ if (mOpened) openToReceiveCalls();
+ } else {
+ // close mSipGroup but remember mOpened
+ Log.v(TAG, " close auto reg temporarily: " + getUri() + ": "
+ + mIncomingCallBroadcastAction);
+ mSipGroup.close();
+ mAutoRegistration.stop();
+ }
+ }
+
+ private void resetGroup(String localIp) throws SipException {
+ try {
+ mSipGroup.reset(localIp);
+ } catch (IOException e) {
+ // network disconnected
+ Log.w(TAG, "resetGroup(): network disconnected?");
+ if (localIp != null) {
+ resetGroup(null); // reset w/o local IP
+ } else {
+ // recursive
+ Log.wtf(TAG, "impossible!");
+ throw new RuntimeException("resetGroup");
+ }
+ }
+ }
+
+ public void closeToNotReceiveCalls() {
+ mOpened = false;
+ mSipGroup.closeToNotReceiveCalls();
+ mAutoRegistration.stop();
+ Log.v(TAG, " close: " + getUri() + ": "
+ + mIncomingCallBroadcastAction);
+ }
+
+ public ISipSession createSession(ISipSessionListener listener) {
+ return mSipGroup.createSession(listener);
+ }
+
+ @Override
+ public void onRinging(ISipSession session, SipProfile caller,
+ byte[] sessionDescription) {
+ synchronized (SipService.this) {
+ try {
+ if (!isRegistered()) {
+ session.endCall();
+ return;
+ }
+
+ // send out incoming call broadcast
+ Log.d(TAG, " ringing~~ " + getUri() + ": " + caller.getUri()
+ + ": " + session.getCallId());
+ addPendingSession(session);
+ Intent intent = SipManager.createIncomingCallBroadcast(
+ mIncomingCallBroadcastAction, session.getCallId(),
+ sessionDescription);
+ Log.d(TAG, " send out intent: " + intent);
+ mContext.sendBroadcast(intent);
+ } catch (RemoteException e) {
+ // should never happen with a local call
+ Log.e(TAG, "processCall()", e);
+ }
+ }
+ }
+
+ @Override
+ public void onError(ISipSession session, String errorClass,
+ String message) {
+ Log.v(TAG, "sip session error: " + errorClass + ": " + message);
+ }
+
+ public boolean isOpened() {
+ return mOpened;
+ }
+
+ public boolean isRegistered() {
+ return mAutoRegistration.isRegistered();
+ }
+
+ private String getUri() {
+ return mSipGroup.getLocalProfileUri();
+ }
+ }
+
+ private class KeepAliveProcess implements Runnable {
+ private static final String TAG = "\\KEEPALIVE/";
+ private static final int INTERVAL = 15;
+ private SipSessionGroup.SipSessionImpl mSession;
+
+ public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) {
+ mSession = session;
+ }
+
+ public void start() {
+ mTimer.set(INTERVAL * 1000, this);
+ }
+
+ public void run() {
+ synchronized (SipService.this) {
+ SipSessionGroup.SipSessionImpl session = mSession.duplicate();
+ Log.d(TAG, " ~~~ keepalive");
+ mTimer.cancel(this);
+ session.sendKeepAlive();
+ if (session.isReRegisterRequired()) {
+ mSession.register(EXPIRY_TIME);
+ } else {
+ mTimer.set(INTERVAL * 1000, this);
+ }
+ }
+ }
+
+ public void stop() {
+ mTimer.cancel(this);
+ }
+ }
+
+ private class AutoRegistrationProcess extends SipSessionAdapter
+ implements Runnable {
+ private SipSessionGroup.SipSessionImpl mSession;
+ private SipSessionListenerProxy mProxy = new SipSessionListenerProxy();
+ private KeepAliveProcess mKeepAliveProcess;
+ private int mBackoff = 1;
+ private boolean mRegistered;
+ private long mExpiryTime;
+
+ private String getAction() {
+ return toString();
+ }
+
+ public void start(SipSessionGroup group) {
+ if (mSession == null) {
+ mBackoff = 1;
+ mSession = (SipSessionGroup.SipSessionImpl)
+ group.createSession(this);
+ // return right away if no active network connection.
+ if (mSession == null) return;
+
+ // start unregistration to clear up old registration at server
+ // TODO: when rfc5626 is deployed, use reg-id and sip.instance
+ // in registration to avoid adding duplicate entries to server
+ mSession.unregister();
+ Log.v(TAG, "start AutoRegistrationProcess for "
+ + mSession.getLocalProfile().getUriString());
+ }
+ }
+
+ public void stop() {
+ if (mSession == null) return;
+ if (mConnected) mSession.unregister();
+ mTimer.cancel(this);
+ if (mKeepAliveProcess != null) {
+ mKeepAliveProcess.stop();
+ mKeepAliveProcess = null;
+ }
+ mSession = null;
+ mRegistered = false;
+ }
+
+ private boolean isStopped() {
+ return (mSession == null);
+ }
+
+ public void setListener(ISipSessionListener listener) {
+ Log.v(TAG, "setListener(): " + listener);
+ mProxy.setListener(listener);
+ if (mSession == null) return;
+
+ try {
+ if ((mSession != null) && SipSessionState.REGISTERING.equals(
+ mSession.getState())) {
+ mProxy.onRegistering(mSession);
+ } else if (mRegistered) {
+ int duration = (int)
+ (mExpiryTime - SystemClock.elapsedRealtime());
+ mProxy.onRegistrationDone(mSession, duration);
+ }
+ } catch (Throwable t) {
+ Log.w(TAG, "setListener(): " + t);
+ }
+ }
+
+ public boolean isRegistered() {
+ return mRegistered;
+ }
+
+ public void run() {
+ Log.v(TAG, " ~~~ registering");
+ synchronized (SipService.this) {
+ if (mConnected && !isStopped()) mSession.register(EXPIRY_TIME);
+ }
+ }
+
+ private boolean isBehindNAT(String address) {
+ try {
+ byte[] d = InetAddress.getByName(address).getAddress();
+ if ((d[0] == 10) ||
+ (((0x000000FF & ((int)d[0])) == 172) &&
+ ((0x000000F0 & ((int)d[1])) == 16)) ||
+ (((0x000000FF & ((int)d[0])) == 192) &&
+ ((0x000000FF & ((int)d[1])) == 168))) {
+ return true;
+ }
+ } catch (UnknownHostException e) {
+ Log.e(TAG, "isBehindAT()" + address, e);
+ }
+ return false;
+ }
+
+ private void restart(int duration) {
+ Log.v(TAG, "Refresh registration " + duration + "s later.");
+ mTimer.cancel(this);
+ mTimer.set(duration * 1000, this);
+ }
+
+ private int backoffDuration() {
+ int duration = SHORT_EXPIRY_TIME * mBackoff;
+ if (duration > 3600) {
+ duration = 3600;
+ } else {
+ mBackoff *= 2;
+ }
+ return duration;
+ }
+
+ @Override
+ public void onRegistering(ISipSession session) {
+ Log.v(TAG, "onRegistering(): " + session + ": " + mSession);
+ synchronized (SipService.this) {
+ if (!isStopped() && (session != mSession)) return;
+ mRegistered = false;
+ try {
+ mProxy.onRegistering(session);
+ } catch (Throwable t) {
+ Log.w(TAG, "onRegistering()", t);
+ }
+ }
+ }
+
+ @Override
+ public void onRegistrationDone(ISipSession session, int duration) {
+ Log.v(TAG, "onRegistrationDone(): " + session + ": " + mSession);
+ synchronized (SipService.this) {
+ if (!isStopped() && (session != mSession)) return;
+ try {
+ mProxy.onRegistrationDone(session, duration);
+ } catch (Throwable t) {
+ Log.w(TAG, "onRegistrationDone()", t);
+ }
+ if (isStopped()) return;
+
+ if (duration > 0) {
+ mSession.clearReRegisterRequired();
+ mExpiryTime = SystemClock.elapsedRealtime()
+ + (duration * 1000);
+
+ if (!mRegistered) {
+ mRegistered = true;
+ // allow some overlap to avoid call drop during renew
+ duration -= MIN_EXPIRY_TIME;
+ if (duration < MIN_EXPIRY_TIME) {
+ duration = MIN_EXPIRY_TIME;
+ }
+ restart(duration);
+
+ if (isBehindNAT(mLocalIp) ||
+ mSession.getLocalProfile().getSendKeepAlive()) {
+ if (mKeepAliveProcess == null) {
+ mKeepAliveProcess =
+ new KeepAliveProcess(mSession);
+ }
+ mKeepAliveProcess.start();
+ }
+ }
+ } else {
+ mRegistered = false;
+ mExpiryTime = -1L;
+ Log.v(TAG, "Refresh registration immediately");
+ run();
+ }
+ }
+ }
+
+ @Override
+ public void onRegistrationFailed(ISipSession session, String className,
+ String message) {
+ Log.v(TAG, "onRegistrationFailed(): " + session + ": " + mSession
+ + ": " + className + ": " + message);
+ synchronized (SipService.this) {
+ if (!isStopped() && (session != mSession)) return;
+ try {
+ mProxy.onRegistrationFailed(session, className, message);
+ } catch (Throwable t) {
+ Log.w(TAG, "onRegistrationFailed(): " + t);
+ }
+
+ if (!isStopped()) onError();
+ }
+ }
+
+ @Override
+ public void onRegistrationTimeout(ISipSession session) {
+ Log.v(TAG, "onRegistrationTimeout(): " + session + ": " + mSession);
+ synchronized (SipService.this) {
+ if (!isStopped() && (session != mSession)) return;
+ try {
+ mProxy.onRegistrationTimeout(session);
+ } catch (Throwable t) {
+ Log.w(TAG, "onRegistrationTimeout(): " + t);
+ }
+
+ if (!isStopped()) {
+ mRegistered = false;
+ onError();
+ }
+ }
+ }
+
+ private void onError() {
+ mRegistered = false;
+ restart(backoffDuration());
+ if (mKeepAliveProcess != null) {
+ mKeepAliveProcess.stop();
+ mKeepAliveProcess = null;
+ }
+ }
+ }
+
+ private class ConnectivityReceiver extends BroadcastReceiver {
+ private Timer mTimer = new Timer();
+ private MyTimerTask mTask;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ Bundle b = intent.getExtras();
+ if (b != null) {
+ NetworkInfo netInfo = (NetworkInfo)
+ b.get(ConnectivityManager.EXTRA_NETWORK_INFO);
+ String type = netInfo.getTypeName();
+ NetworkInfo.State state = netInfo.getState();
+ if (state == NetworkInfo.State.CONNECTED) {
+ Log.v(TAG, "Connectivity alert: CONNECTED " + type);
+ onChanged(type, true);
+ } else if (state == NetworkInfo.State.DISCONNECTED) {
+ Log.v(TAG, "Connectivity alert: DISCONNECTED " + type);
+ onChanged(type, false);
+ } else {
+ Log.d(TAG, "Connectivity alert not processed: " + state
+ + " " + type);
+ }
+ }
+ }
+ }
+
+ private void onChanged(String type, boolean connected) {
+ synchronized (SipService.this) {
+ // When turning on WIFI, it needs some time for network
+ // connectivity to get stabile so we defer good news (because
+ // we want to skip the interim ones) but deliver bad news
+ // immediately
+ if (connected) {
+ if (mTask != null) mTask.cancel();
+ mTask = new MyTimerTask(type, connected);
+ mTimer.schedule(mTask, 3 * 1000L);
+ // TODO: hold wakup lock so that we can finish change before
+ // the device goes to sleep
+ } else {
+ if ((mTask != null) && mTask.mNetworkType.equals(type)) {
+ mTask.cancel();
+ }
+ onConnectivityChanged(type, false);
+ }
+ }
+ }
+
+ private class MyTimerTask extends TimerTask {
+ private boolean mConnected;
+ private String mNetworkType;
+
+ public MyTimerTask(String type, boolean connected) {
+ mNetworkType = type;
+ mConnected = connected;
+ }
+
+ @Override
+ public void run() {
+ synchronized (SipService.this) {
+ if (mTask != this) {
+ Log.w(TAG, " unexpected task: " + mNetworkType
+ + (mConnected ? " CONNECTED" : "DISCONNECTED"));
+ return;
+ }
+ mTask = null;
+ Log.v(TAG, " deliver change for " + mNetworkType
+ + (mConnected ? " CONNECTED" : "DISCONNECTED"));
+ onConnectivityChanged(mNetworkType, mConnected);
+ }
+ }
+ }
+ }
+
+ // TODO: clean up pending SipSession(s) periodically
+
+
+ /**
+ * Timer that can schedule events to occur even when the device is in sleep.
+ * Only used internally in this package.
+ */
+ class WakeupTimer extends BroadcastReceiver {
+ private static final String TAG = "_SIP.WkTimer_";
+ private static final String TRIGGER_TIME = "TriggerTime";
+
+ private Context mContext;
+ private AlarmManager mAlarmManager;
+
+ // runnable --> time to execute in SystemClock
+ private TreeSet<MyEvent> mEventQueue =
+ new TreeSet<MyEvent>(new MyEventComparator());
+
+ private PendingIntent mPendingIntent;
+
+ public WakeupTimer(Context context) {
+ mContext = context;
+ mAlarmManager = (AlarmManager)
+ context.getSystemService(Context.ALARM_SERVICE);
+
+ IntentFilter filter = new IntentFilter(getAction());
+ context.registerReceiver(this, filter);
+ }
+
+ /**
+ * Stops the timer. No event can be scheduled after this method is called.
+ */
+ public synchronized void stop() {
+ mContext.unregisterReceiver(this);
+ if (mPendingIntent != null) {
+ mAlarmManager.cancel(mPendingIntent);
+ mPendingIntent = null;
+ }
+ mEventQueue.clear();
+ mEventQueue = null;
+ }
+
+ private synchronized boolean stopped() {
+ if (mEventQueue == null) {
+ Log.w(TAG, "Timer stopped");
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void cancelAlarm() {
+ mAlarmManager.cancel(mPendingIntent);
+ mPendingIntent = null;
+ }
+
+ private void recalculatePeriods() {
+ if (mEventQueue.isEmpty()) return;
+
+ MyEvent firstEvent = mEventQueue.first();
+ int minPeriod = firstEvent.mMaxPeriod;
+ long minTriggerTime = firstEvent.mTriggerTime;
+ for (MyEvent e : mEventQueue) {
+ e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod;
+ int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod
+ - minTriggerTime);
+ interval = interval / minPeriod * minPeriod;
+ e.mTriggerTime = minTriggerTime + interval;
+ }
+ TreeSet<MyEvent> newQueue = new TreeSet<MyEvent>(
+ mEventQueue.comparator());
+ newQueue.addAll((Collection<MyEvent>) mEventQueue);
+ mEventQueue.clear();
+ mEventQueue = newQueue;
+ Log.v(TAG, "queue re-calculated");
+ printQueue();
+ }
+
+ // Determines the period and the trigger time of the new event and insert it
+ // to the queue.
+ private void insertEvent(MyEvent event) {
+ long now = SystemClock.elapsedRealtime();
+ if (mEventQueue.isEmpty()) {
+ event.mTriggerTime = now + event.mPeriod;
+ mEventQueue.add(event);
+ return;
+ }
+ MyEvent firstEvent = mEventQueue.first();
+ int minPeriod = firstEvent.mPeriod;
+ if (minPeriod <= event.mMaxPeriod) {
+ event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod;
+ int interval = event.mMaxPeriod;
+ interval -= (int) (firstEvent.mTriggerTime - now);
+ interval = interval / minPeriod * minPeriod;
+ event.mTriggerTime = firstEvent.mTriggerTime + interval;
+ mEventQueue.add(event);
+ } else {
+ long triggerTime = now + event.mPeriod;
+ if (firstEvent.mTriggerTime < triggerTime) {
+ event.mTriggerTime = firstEvent.mTriggerTime;
+ event.mLastTriggerTime -= event.mPeriod;
+ } else {
+ event.mTriggerTime = triggerTime;
+ }
+ mEventQueue.add(event);
+ recalculatePeriods();
+ }
+ }
+
+ /**
+ * Sets a periodic timer.
+ *
+ * @param period the timer period; in milli-second
+ * @param callback is called back when the timer goes off; the same callback
+ * can be specified in multiple timer events
+ */
+ public synchronized void set(int period, Runnable callback) {
+ if (stopped()) return;
+
+ long now = SystemClock.elapsedRealtime();
+ MyEvent event = new MyEvent(period, callback, now);
+ insertEvent(event);
+
+ if (mEventQueue.first() == event) {
+ if (mEventQueue.size() > 1) cancelAlarm();
+ scheduleNext();
+ }
+
+ long triggerTime = event.mTriggerTime;
+ Log.v(TAG, " add event " + event + " scheduled at "
+ + showTime(triggerTime) + " at " + showTime(now)
+ + ", #events=" + mEventQueue.size());
+ printQueue();
+ }
+
+ /**
+ * Cancels all the timer events with the specified callback.
+ *
+ * @param callback the callback
+ */
+ public synchronized void cancel(Runnable callback) {
+ if (stopped() || mEventQueue.isEmpty()) return;
+ Log.d(TAG, "cancel:" + callback);
+
+ MyEvent firstEvent = mEventQueue.first();
+ for (Iterator<MyEvent> iter = mEventQueue.iterator();
+ iter.hasNext();) {
+ MyEvent event = iter.next();
+ if (event.mCallback == callback) {
+ iter.remove();
+ Log.d(TAG, " cancel found:" + event);
+ }
+ }
+ if (mEventQueue.isEmpty()) {
+ cancelAlarm();
+ } else if (mEventQueue.first() != firstEvent) {
+ cancelAlarm();
+ firstEvent = mEventQueue.first();
+ firstEvent.mPeriod = firstEvent.mMaxPeriod;
+ firstEvent.mTriggerTime = firstEvent.mLastTriggerTime
+ + firstEvent.mPeriod;
+ recalculatePeriods();
+ scheduleNext();
+ }
+ Log.d(TAG, "after cancel:");
+ printQueue();
+ }
+
+ private void scheduleNext() {
+ if (stopped() || mEventQueue.isEmpty()) return;
+
+ if (mPendingIntent != null) {
+ throw new RuntimeException("pendingIntent is not null!");
+ }
+
+ MyEvent event = mEventQueue.first();
+ Intent intent = new Intent(getAction());
+ intent.putExtra(TRIGGER_TIME, event.mTriggerTime);
+ PendingIntent pendingIntent = mPendingIntent =
+ PendingIntent.getBroadcast(mContext, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ event.mTriggerTime, pendingIntent);
+ }
+
+ @Override
+ public synchronized void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (getAction().equals(action)
+ && intent.getExtras().containsKey(TRIGGER_TIME)) {
+ mPendingIntent = null;
+ long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L);
+ execute(triggerTime);
+ } else {
+ Log.d(TAG, "unrecognized intent: " + intent);
+ }
+ }
+
+ private void printQueue() {
+ int count = 0;
+ for (MyEvent event : mEventQueue) {
+ Log.d(TAG, " " + event + ": scheduled at "
+ + showTime(event.mTriggerTime) + ": last at "
+ + showTime(event.mLastTriggerTime));
+ if (++count >= 5) break;
+ }
+ if (mEventQueue.size() > count) {
+ Log.d(TAG, " .....");
+ } else if (count == 0) {
+ Log.d(TAG, " <empty>");
+ }
+ }
+
+ private void execute(long triggerTime) {
+ Log.d(TAG, "time's up, triggerTime = " + showTime(triggerTime) + ": "
+ + mEventQueue.size());
+ if (stopped() || mEventQueue.isEmpty()) return;
+
+ for (MyEvent event : mEventQueue) {
+ if (event.mTriggerTime != triggerTime) break;
+ Log.d(TAG, "execute " + event);
+
+ event.mLastTriggerTime = event.mTriggerTime;
+ event.mTriggerTime += event.mPeriod;
+
+ // run the callback in a new thread to prevent deadlock
+ new Thread(event.mCallback).start();
+ }
+ Log.d(TAG, "after timeout execution");
+ printQueue();
+ scheduleNext();
+ }
+
+ private String getAction() {
+ return toString();
+ }
+
+ private String showTime(long time) {
+ int ms = (int) (time % 1000);
+ int s = (int) (time / 1000);
+ int m = s / 60;
+ s %= 60;
+ return String.format("%d.%d.%d", m, s, ms);
+ }
+ }
+
+ private static class MyEvent {
+ int mPeriod;
+ int mMaxPeriod;
+ long mTriggerTime;
+ long mLastTriggerTime;
+ Runnable mCallback;
+
+ MyEvent(int period, Runnable callback, long now) {
+ mPeriod = mMaxPeriod = period;
+ mCallback = callback;
+ mLastTriggerTime = now;
+ }
+
+ @Override
+ public String toString() {
+ String s = super.toString();
+ s = s.substring(s.indexOf("@"));
+ return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":"
+ + toString(mCallback);
+ }
+
+ private String toString(Object o) {
+ String s = o.toString();
+ int index = s.indexOf("$");
+ if (index > 0) s = s.substring(index + 1);
+ return s;
+ }
+ }
+
+ private static class MyEventComparator implements Comparator<MyEvent> {
+ public int compare(MyEvent e1, MyEvent e2) {
+ if (e1 == e2) return 0;
+ int diff = e1.mMaxPeriod - e2.mMaxPeriod;
+ if (diff == 0) diff = -1;
+ return diff;
+ }
+
+ public boolean equals(Object that) {
+ return (this == that);
+ }
+ }
+}
diff --git a/services/java/com/android/server/sip/SipSessionGroup.java b/services/java/com/android/server/sip/SipSessionGroup.java
new file mode 100644
index 0000000..db3f536
--- /dev/null
+++ b/services/java/com/android/server/sip/SipSessionGroup.java
@@ -0,0 +1,1081 @@
+/*
+ * Copyright (C) 2010 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.sip;
+
+import gov.nist.javax.sip.clientauthutils.AccountManager;
+import gov.nist.javax.sip.clientauthutils.UserCredentials;
+import gov.nist.javax.sip.header.SIPHeaderNames;
+import gov.nist.javax.sip.header.WWWAuthenticate;
+
+import android.net.sip.ISipSession;
+import android.net.sip.ISipSessionListener;
+import android.net.sip.SessionDescription;
+import android.net.sip.SipProfile;
+import android.net.sip.SipSessionAdapter;
+import android.net.sip.SipSessionState;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.text.ParseException;
+import java.util.Collection;
+import java.util.EventObject;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TooManyListenersException;
+
+import javax.sip.ClientTransaction;
+import javax.sip.Dialog;
+import javax.sip.DialogTerminatedEvent;
+import javax.sip.IOExceptionEvent;
+import javax.sip.InvalidArgumentException;
+import javax.sip.ListeningPoint;
+import javax.sip.RequestEvent;
+import javax.sip.ResponseEvent;
+import javax.sip.ServerTransaction;
+import javax.sip.SipException;
+import javax.sip.SipFactory;
+import javax.sip.SipListener;
+import javax.sip.SipProvider;
+import javax.sip.SipStack;
+import javax.sip.TimeoutEvent;
+import javax.sip.Transaction;
+import javax.sip.TransactionState;
+import javax.sip.TransactionTerminatedEvent;
+import javax.sip.address.Address;
+import javax.sip.address.SipURI;
+import javax.sip.header.CSeqHeader;
+import javax.sip.header.ExpiresHeader;
+import javax.sip.header.FromHeader;
+import javax.sip.header.MinExpiresHeader;
+import javax.sip.header.ViaHeader;
+import javax.sip.message.Message;
+import javax.sip.message.Request;
+import javax.sip.message.Response;
+
+/**
+ * Manages {@link ISipSession}'s for a SIP account.
+ */
+class SipSessionGroup implements SipListener {
+ private static final String TAG = "SipSession";
+ private static final String ANONYMOUS = "anonymous";
+ private static final int EXPIRY_TIME = 3600;
+
+ private static final EventObject DEREGISTER = new EventObject("Deregister");
+ private static final EventObject END_CALL = new EventObject("End call");
+ private static final EventObject HOLD_CALL = new EventObject("Hold call");
+ private static final EventObject CONTINUE_CALL
+ = new EventObject("Continue call");
+
+ private final SipProfile mLocalProfile;
+ private final String mPassword;
+
+ private SipStack mSipStack;
+ private SipHelper mSipHelper;
+ private String mLastNonce;
+ private int mRPort;
+
+ // session that processes INVITE requests
+ private SipSessionImpl mCallReceiverSession;
+ private String mLocalIp;
+
+ // call-id-to-SipSession map
+ private Map<String, SipSessionImpl> mSessionMap =
+ new HashMap<String, SipSessionImpl>();
+
+ /**
+ * @param myself the local profile with password crossed out
+ * @param password the password of the profile
+ * @throws IOException if cannot assign requested address
+ */
+ public SipSessionGroup(String localIp, SipProfile myself, String password)
+ throws SipException, IOException {
+ mLocalProfile = myself;
+ mPassword = password;
+ reset(localIp);
+ }
+
+ void reset(String localIp) throws SipException, IOException {
+ mLocalIp = localIp;
+ if (localIp == null) return;
+
+ SipProfile myself = mLocalProfile;
+ SipFactory sipFactory = SipFactory.getInstance();
+ Properties properties = new Properties();
+ properties.setProperty("javax.sip.STACK_NAME", getStackName());
+ String outboundProxy = myself.getProxyAddress();
+ if (!TextUtils.isEmpty(outboundProxy)) {
+ properties.setProperty("javax.sip.OUTBOUND_PROXY", outboundProxy
+ + ":" + myself.getPort() + "/" + myself.getProtocol());
+ }
+ SipStack stack = mSipStack = sipFactory.createSipStack(properties);
+
+ try {
+ SipProvider provider = stack.createSipProvider(
+ stack.createListeningPoint(localIp, allocateLocalPort(),
+ myself.getProtocol()));
+ provider.addSipListener(this);
+ mSipHelper = new SipHelper(stack, provider);
+ } catch (InvalidArgumentException e) {
+ throw new IOException(e.getMessage());
+ } catch (TooManyListenersException e) {
+ // must never happen
+ throw new SipException("SipSessionGroup constructor", e);
+ }
+ Log.d(TAG, " start stack for " + myself.getUriString());
+ stack.start();
+
+ mLastNonce = null;
+ mCallReceiverSession = null;
+ mSessionMap.clear();
+ }
+
+ public SipProfile getLocalProfile() {
+ return mLocalProfile;
+ }
+
+ public String getLocalProfileUri() {
+ return mLocalProfile.getUriString();
+ }
+
+ private String getStackName() {
+ return "stack" + System.currentTimeMillis();
+ }
+
+ public synchronized void close() {
+ Log.d(TAG, " close stack for " + mLocalProfile.getUriString());
+ mSessionMap.clear();
+ closeToNotReceiveCalls();
+ if (mSipStack != null) {
+ mSipStack.stop();
+ mSipStack = null;
+ mSipHelper = null;
+ }
+ }
+
+ public synchronized boolean isClosed() {
+ return (mSipStack == null);
+ }
+
+ // For internal use, require listener not to block in callbacks.
+ public synchronized void openToReceiveCalls(ISipSessionListener listener) {
+ if (mCallReceiverSession == null) {
+ mCallReceiverSession = new SipSessionCallReceiverImpl(listener);
+ } else {
+ mCallReceiverSession.setListener(listener);
+ }
+ }
+
+ public synchronized void closeToNotReceiveCalls() {
+ mCallReceiverSession = null;
+ }
+
+ public ISipSession createSession(ISipSessionListener listener) {
+ return (isClosed() ? null : new SipSessionImpl(listener));
+ }
+
+ private static int allocateLocalPort() throws SipException {
+ try {
+ DatagramSocket s = new DatagramSocket();
+ int localPort = s.getLocalPort();
+ s.close();
+ return localPort;
+ } catch (IOException e) {
+ throw new SipException("allocateLocalPort()", e);
+ }
+ }
+
+ private synchronized SipSessionImpl getSipSession(EventObject event) {
+ String key = SipHelper.getCallId(event);
+ Log.d(TAG, " sesssion key from event: " + key);
+ Log.d(TAG, " active sessions:");
+ for (String k : mSessionMap.keySet()) {
+ Log.d(TAG, " ..... '" + k + "': " + mSessionMap.get(k));
+ }
+ SipSessionImpl session = mSessionMap.get(key);
+ return ((session != null) ? session : mCallReceiverSession);
+ }
+
+ private synchronized void addSipSession(SipSessionImpl newSession) {
+ removeSipSession(newSession);
+ String key = newSession.getCallId();
+ Log.d(TAG, " +++++ add a session with key: '" + key + "'");
+ mSessionMap.put(key, newSession);
+ for (String k : mSessionMap.keySet()) {
+ Log.d(TAG, " ..... " + k + ": " + mSessionMap.get(k));
+ }
+ }
+
+ private synchronized void removeSipSession(SipSessionImpl session) {
+ if (session == mCallReceiverSession) return;
+ String key = session.getCallId();
+ SipSessionImpl s = mSessionMap.remove(key);
+ // sanity check
+ if ((s != null) && (s != session)) {
+ Log.w(TAG, "session " + session + " is not associated with key '"
+ + key + "'");
+ mSessionMap.put(key, s);
+ for (Map.Entry<String, SipSessionImpl> entry
+ : mSessionMap.entrySet()) {
+ if (entry.getValue() == s) {
+ key = entry.getKey();
+ mSessionMap.remove(key);
+ }
+ }
+ }
+ Log.d(TAG, " remove session " + session + " with key '" + key + "'");
+
+ for (String k : mSessionMap.keySet()) {
+ Log.d(TAG, " ..... " + k + ": " + mSessionMap.get(k));
+ }
+ }
+
+ public void processRequest(RequestEvent event) {
+ process(event);
+ }
+
+ public void processResponse(ResponseEvent event) {
+ process(event);
+ }
+
+ public void processIOException(IOExceptionEvent event) {
+ process(event);
+ }
+
+ public void processTimeout(TimeoutEvent event) {
+ process(event);
+ }
+
+ public void processTransactionTerminated(TransactionTerminatedEvent event) {
+ process(event);
+ }
+
+ public void processDialogTerminated(DialogTerminatedEvent event) {
+ process(event);
+ }
+
+ private synchronized void process(EventObject event) {
+ SipSessionImpl session = getSipSession(event);
+ try {
+ if ((session != null) && session.process(event)) {
+ Log.d(TAG, " ~~~~~ new state: " + session.mState);
+ } else {
+ Log.d(TAG, "event not processed: " + event);
+ }
+ } catch (Throwable e) {
+ Log.e(TAG, "event process error: " + event, e);
+ session.onError(e);
+ }
+ }
+
+ private class SipSessionCallReceiverImpl extends SipSessionImpl {
+ public SipSessionCallReceiverImpl(ISipSessionListener listener) {
+ super(listener);
+ }
+
+ public boolean process(EventObject evt) throws SipException {
+ Log.d(TAG, " ~~~~~ " + this + ": " + mState + ": processing "
+ + log(evt));
+ if (isRequestEvent(Request.INVITE, evt)) {
+ RequestEvent event = (RequestEvent) evt;
+ SipSessionImpl newSession = new SipSessionImpl(mProxy);
+ newSession.mServerTransaction = mSipHelper.sendRinging(event,
+ generateTag());
+ newSession.mDialog = newSession.mServerTransaction.getDialog();
+ newSession.mInviteReceived = event;
+ newSession.mPeerProfile = createPeerProfile(event.getRequest());
+ newSession.mState = SipSessionState.INCOMING_CALL;
+ newSession.mPeerSessionDescription =
+ event.getRequest().getRawContent();
+ addSipSession(newSession);
+ mProxy.onRinging(newSession, newSession.mPeerProfile,
+ newSession.mPeerSessionDescription);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ class SipSessionImpl extends ISipSession.Stub {
+ SipProfile mPeerProfile;
+ SipSessionListenerProxy mProxy = new SipSessionListenerProxy();
+ SipSessionState mState = SipSessionState.READY_TO_CALL;
+ RequestEvent mInviteReceived;
+ Dialog mDialog;
+ ServerTransaction mServerTransaction;
+ ClientTransaction mClientTransaction;
+ byte[] mPeerSessionDescription;
+ boolean mInCall;
+ boolean mReRegisterFlag = false;
+
+ public SipSessionImpl(ISipSessionListener listener) {
+ setListener(listener);
+ }
+
+ SipSessionImpl duplicate() {
+ return new SipSessionImpl(mProxy.getListener());
+ }
+
+ private void reset() {
+ mInCall = false;
+ removeSipSession(this);
+ mPeerProfile = null;
+ mState = SipSessionState.READY_TO_CALL;
+ mInviteReceived = null;
+ mDialog = null;
+ mServerTransaction = null;
+ mClientTransaction = null;
+ mPeerSessionDescription = null;
+ }
+
+ public boolean isInCall() {
+ return mInCall;
+ }
+
+ public String getLocalIp() {
+ return mLocalIp;
+ }
+
+ public SipProfile getLocalProfile() {
+ return mLocalProfile;
+ }
+
+ public SipProfile getPeerProfile() {
+ return mPeerProfile;
+ }
+
+ public String getCallId() {
+ return SipHelper.getCallId(getTransaction());
+ }
+
+ private Transaction getTransaction() {
+ if (mClientTransaction != null) return mClientTransaction;
+ if (mServerTransaction != null) return mServerTransaction;
+ return null;
+ }
+
+ public String getState() {
+ return mState.toString();
+ }
+
+ public void setListener(ISipSessionListener listener) {
+ mProxy.setListener((listener instanceof SipSessionListenerProxy)
+ ? ((SipSessionListenerProxy) listener).getListener()
+ : listener);
+ }
+
+ public void makeCall(SipProfile peerProfile,
+ SessionDescription sessionDescription) {
+ try {
+ processCommand(
+ new MakeCallCommand(peerProfile, sessionDescription));
+ } catch (SipException e) {
+ onError(e);
+ }
+ }
+
+ public void answerCall(SessionDescription sessionDescription) {
+ try {
+ processCommand(
+ new MakeCallCommand(mPeerProfile, sessionDescription));
+ } catch (SipException e) {
+ onError(e);
+ }
+ }
+
+ public void endCall() {
+ try {
+ processCommand(END_CALL);
+ } catch (SipException e) {
+ onError(e);
+ }
+ }
+
+ public void changeCall(SessionDescription sessionDescription) {
+ try {
+ processCommand(
+ new MakeCallCommand(mPeerProfile, sessionDescription));
+ } catch (SipException e) {
+ onError(e);
+ }
+ }
+
+ public void register(int duration) {
+ try {
+ processCommand(new RegisterCommand(duration));
+ } catch (SipException e) {
+ onRegistrationFailed(e);
+ }
+ }
+
+ public void unregister() {
+ try {
+ processCommand(DEREGISTER);
+ } catch (SipException e) {
+ onRegistrationFailed(e);
+ }
+ }
+
+ public boolean isReRegisterRequired() {
+ return mReRegisterFlag;
+ }
+
+ public void clearReRegisterRequired() {
+ mReRegisterFlag = false;
+ }
+
+ public void sendKeepAlive() {
+ mState = SipSessionState.PINGING;
+ try {
+ processCommand(new OptionsCommand());
+ while (SipSessionState.PINGING.equals(mState)) {
+ Thread.sleep(1000);
+ }
+ } catch (SipException e) {
+ Log.e(TAG, "sendKeepAlive failed", e);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "sendKeepAlive interrupted", e);
+ }
+ }
+
+ private void processCommand(EventObject command) throws SipException {
+ if (!process(command)) {
+ throw new SipException("wrong state to execute: " + command);
+ }
+ }
+
+ protected String generateTag() {
+ // 32-bit randomness
+ return String.valueOf((long) (Math.random() * 0x100000000L));
+ }
+
+ public String toString() {
+ try {
+ String s = super.toString();
+ return s.substring(s.indexOf("@")) + ":" + mState;
+ } catch (Throwable e) {
+ return super.toString();
+ }
+ }
+
+ public boolean process(EventObject evt) throws SipException {
+ Log.d(TAG, " ~~~~~ " + this + ": " + mState + ": processing "
+ + log(evt));
+ synchronized (SipSessionGroup.this) {
+ if (isClosed()) return false;
+
+ Dialog dialog = null;
+ if (evt instanceof RequestEvent) {
+ dialog = ((RequestEvent) evt).getDialog();
+ } else if (evt instanceof ResponseEvent) {
+ dialog = ((ResponseEvent) evt).getDialog();
+ }
+ if (dialog != null) mDialog = dialog;
+
+ boolean processed;
+
+ switch (mState) {
+ case REGISTERING:
+ case DEREGISTERING:
+ processed = registeringToReady(evt);
+ break;
+ case PINGING:
+ processed = keepAliveProcess(evt);
+ break;
+ case READY_TO_CALL:
+ processed = readyForCall(evt);
+ break;
+ case INCOMING_CALL:
+ processed = incomingCall(evt);
+ break;
+ case INCOMING_CALL_ANSWERING:
+ processed = incomingCallToInCall(evt);
+ break;
+ case OUTGOING_CALL:
+ case OUTGOING_CALL_RING_BACK:
+ processed = outgoingCall(evt);
+ break;
+ case OUTGOING_CALL_CANCELING:
+ processed = outgoingCallToReady(evt);
+ break;
+ case IN_CALL:
+ processed = inCall(evt);
+ break;
+ default:
+ processed = false;
+ }
+ return (processed || processExceptions(evt));
+ }
+ }
+
+ private boolean processExceptions(EventObject evt) throws SipException {
+ if (isRequestEvent(Request.BYE, evt)) {
+ // terminate the call whenever a BYE is received
+ mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
+ endCallNormally();
+ return true;
+ } else if (isRequestEvent(Request.CANCEL, evt)) {
+ mSipHelper.sendResponse((RequestEvent) evt,
+ Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST);
+ return true;
+ } else if (evt instanceof TransactionTerminatedEvent) {
+ if (evt instanceof TimeoutEvent) {
+ processTimeout((TimeoutEvent) evt);
+ } else {
+ Log.d(TAG, "Transaction terminated:" + this);
+ if (!SipSessionState.IN_CALL.equals(mState)) {
+ removeSipSession(this);
+ }
+ return true;
+ }
+ return true;
+ } else if (evt instanceof DialogTerminatedEvent) {
+ processDialogTerminated((DialogTerminatedEvent) evt);
+ return true;
+ }
+ return false;
+ }
+
+ private void processDialogTerminated(DialogTerminatedEvent event) {
+ if (mDialog == event.getDialog()) {
+ onError(new SipException("dialog terminated"));
+ } else {
+ Log.d(TAG, "not the current dialog; current=" + mDialog
+ + ", terminated=" + event.getDialog());
+ }
+ }
+
+ private void processTimeout(TimeoutEvent event) {
+ Log.d(TAG, "processing Timeout..." + event);
+ Transaction current = event.isServerTransaction()
+ ? mServerTransaction
+ : mClientTransaction;
+ Transaction target = event.isServerTransaction()
+ ? event.getServerTransaction()
+ : event.getClientTransaction();
+
+ if ((current != target) && (mState != SipSessionState.PINGING)) {
+ Log.d(TAG, "not the current transaction; current=" + current
+ + ", timed out=" + target);
+ return;
+ }
+ switch (mState) {
+ case REGISTERING:
+ case DEREGISTERING:
+ reset();
+ mProxy.onRegistrationTimeout(this);
+ break;
+ case INCOMING_CALL:
+ case INCOMING_CALL_ANSWERING:
+ case OUTGOING_CALL_CANCELING:
+ endCallOnError(new SipException("timed out"));
+ break;
+ case PINGING:
+ reset();
+ mReRegisterFlag = true;
+ mState = SipSessionState.READY_TO_CALL;
+ break;
+
+ default:
+ // do nothing
+ break;
+ }
+ }
+
+ private int getExpiryTime(Response response) {
+ int expires = EXPIRY_TIME;
+ ExpiresHeader expiresHeader = (ExpiresHeader)
+ response.getHeader(ExpiresHeader.NAME);
+ if (expiresHeader != null) expires = expiresHeader.getExpires();
+ expiresHeader = (ExpiresHeader)
+ response.getHeader(MinExpiresHeader.NAME);
+ if (expiresHeader != null) {
+ expires = Math.max(expires, expiresHeader.getExpires());
+ }
+ return expires;
+ }
+
+ private boolean keepAliveProcess(EventObject evt) throws SipException {
+ if (evt instanceof OptionsCommand) {
+ mClientTransaction = mSipHelper.sendKeepAlive(mLocalProfile,
+ generateTag());
+ mDialog = mClientTransaction.getDialog();
+ addSipSession(this);
+ return true;
+ } else if (evt instanceof ResponseEvent) {
+ return parseOptionsResult(evt);
+ }
+ return false;
+ }
+
+ private boolean parseOptionsResult(EventObject evt) {
+ if (expectResponse(Request.OPTIONS, evt)) {
+ ResponseEvent event = (ResponseEvent) evt;
+ int rPort = getRPortFromResponse(event.getResponse());
+ if (rPort != -1) {
+ if (mRPort == 0) mRPort = rPort;
+ if (mRPort != rPort) {
+ mReRegisterFlag = true;
+ Log.w(TAG, String.format("rport is changed: %d <> %d",
+ mRPort, rPort));
+ mRPort = rPort;
+ } else {
+ Log.w(TAG, "rport is the same: " + rPort);
+ }
+ } else {
+ Log.w(TAG, "peer did not respect our rport request");
+ }
+ mState = SipSessionState.READY_TO_CALL;
+ return true;
+ }
+ return false;
+ }
+
+ private int getRPortFromResponse(Response response) {
+ ViaHeader viaHeader = (ViaHeader)(response.getHeader(
+ SIPHeaderNames.VIA));
+ return (viaHeader == null) ? -1 : viaHeader.getRPort();
+ }
+
+ private boolean registeringToReady(EventObject evt)
+ throws SipException {
+ if (expectResponse(Request.REGISTER, evt)) {
+ ResponseEvent event = (ResponseEvent) evt;
+ Response response = event.getResponse();
+
+ int statusCode = response.getStatusCode();
+ switch (statusCode) {
+ case Response.OK:
+ SipSessionState state = mState;
+ reset();
+ onRegistrationDone((state == SipSessionState.REGISTERING)
+ ? getExpiryTime(((ResponseEvent) evt).getResponse())
+ : -1);
+ mLastNonce = null;
+ mRPort = 0;
+ return true;
+ case Response.UNAUTHORIZED:
+ case Response.PROXY_AUTHENTICATION_REQUIRED:
+ String nonce = getNonceFromResponse(response);
+ if (((nonce != null) && nonce.equals(mLastNonce)) ||
+ (nonce == mLastNonce)) {
+ Log.v(TAG, "Incorrect username/password");
+ reset();
+ onRegistrationFailed(createCallbackException(response));
+ } else {
+ mSipHelper.handleChallenge(event, getAccountManager());
+ mLastNonce = nonce;
+ }
+ return true;
+ default:
+ if (statusCode >= 500) {
+ reset();
+ onRegistrationFailed(createCallbackException(response));
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private AccountManager getAccountManager() {
+ return new AccountManager() {
+ public UserCredentials getCredentials(ClientTransaction
+ challengedTransaction, String realm) {
+ return new UserCredentials() {
+ public String getUserName() {
+ return mLocalProfile.getUserName();
+ }
+
+ public String getPassword() {
+ return mPassword;
+ }
+
+ public String getSipDomain() {
+ return mLocalProfile.getSipDomain();
+ }
+ };
+ }
+ };
+ }
+
+ private String getNonceFromResponse(Response response) {
+ WWWAuthenticate authHeader = (WWWAuthenticate)(response.getHeader(
+ SIPHeaderNames.WWW_AUTHENTICATE));
+ return (authHeader == null) ? null : authHeader.getNonce();
+ }
+
+ private boolean readyForCall(EventObject evt) throws SipException {
+ // expect MakeCallCommand, RegisterCommand, DEREGISTER
+ if (evt instanceof MakeCallCommand) {
+ MakeCallCommand cmd = (MakeCallCommand) evt;
+ mPeerProfile = cmd.getPeerProfile();
+ SessionDescription sessionDescription =
+ cmd.getSessionDescription();
+ mClientTransaction = mSipHelper.sendInvite(mLocalProfile,
+ mPeerProfile, sessionDescription, generateTag());
+ mDialog = mClientTransaction.getDialog();
+ addSipSession(this);
+ mState = SipSessionState.OUTGOING_CALL;
+ mProxy.onCalling(this);
+ return true;
+ } else if (evt instanceof RegisterCommand) {
+ int duration = ((RegisterCommand) evt).getDuration();
+ mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
+ generateTag(), duration);
+ mDialog = mClientTransaction.getDialog();
+ addSipSession(this);
+ mState = SipSessionState.REGISTERING;
+ mProxy.onRegistering(this);
+ return true;
+ } else if (DEREGISTER == evt) {
+ mClientTransaction = mSipHelper.sendRegister(mLocalProfile,
+ generateTag(), 0);
+ mDialog = mClientTransaction.getDialog();
+ addSipSession(this);
+ mState = SipSessionState.DEREGISTERING;
+ mProxy.onRegistering(this);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean incomingCall(EventObject evt) throws SipException {
+ // expect MakeCallCommand(answering) , END_CALL cmd , Cancel
+ if (evt instanceof MakeCallCommand) {
+ // answer call
+ mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived,
+ mLocalProfile,
+ ((MakeCallCommand) evt).getSessionDescription(),
+ mServerTransaction);
+ mState = SipSessionState.INCOMING_CALL_ANSWERING;
+ return true;
+ } else if (END_CALL == evt) {
+ mSipHelper.sendInviteBusyHere(mInviteReceived,
+ mServerTransaction);
+ endCallNormally();
+ return true;
+ } else if (isRequestEvent(Request.CANCEL, evt)) {
+ RequestEvent event = (RequestEvent) evt;
+ mSipHelper.sendResponse(event, Response.OK);
+ mSipHelper.sendInviteRequestTerminated(
+ mInviteReceived.getRequest(), mServerTransaction);
+ endCallNormally();
+ return true;
+ }
+ return false;
+ }
+
+ private boolean incomingCallToInCall(EventObject evt)
+ throws SipException {
+ // expect ACK, CANCEL request
+ if (isRequestEvent(Request.ACK, evt)) {
+ establishCall();
+ return true;
+ } else if (isRequestEvent(Request.CANCEL, evt)) {
+ // http://tools.ietf.org/html/rfc3261#section-9.2
+ // Final response has been sent; do nothing here.
+ return true;
+ }
+ return false;
+ }
+
+ private boolean outgoingCall(EventObject evt) throws SipException {
+ if (expectResponse(Request.INVITE, evt)) {
+ ResponseEvent event = (ResponseEvent) evt;
+ Response response = event.getResponse();
+
+ int statusCode = response.getStatusCode();
+ switch (statusCode) {
+ case Response.RINGING:
+ if (mState == SipSessionState.OUTGOING_CALL) {
+ mState = SipSessionState.OUTGOING_CALL_RING_BACK;
+ mProxy.onRingingBack(this);
+ }
+ return true;
+ case Response.OK:
+ mSipHelper.sendInviteAck(event, mDialog);
+ mPeerSessionDescription = response.getRawContent();
+ establishCall();
+ return true;
+ case Response.PROXY_AUTHENTICATION_REQUIRED:
+ mClientTransaction = mSipHelper.handleChallenge(
+ (ResponseEvent) evt, getAccountManager());
+ mDialog = mClientTransaction.getDialog();
+ addSipSession(this);
+ return true;
+ case Response.BUSY_HERE:
+ reset();
+ mProxy.onCallBusy(this);
+ return true;
+ case Response.REQUEST_PENDING:
+ // TODO:
+ // rfc3261#section-14.1; re-schedule invite
+ return true;
+ default:
+ if (statusCode >= 400) {
+ // error: an ack is sent automatically by the stack
+ onError(createCallbackException(response));
+ return true;
+ } else if (statusCode >= 300) {
+ // TODO: handle 3xx (redirect)
+ } else {
+ return true;
+ }
+ }
+ return false;
+ } else if (END_CALL == evt) {
+ // RFC says that UA should not send out cancel when no
+ // response comes back yet. We are cheating for not checking
+ // response.
+ mSipHelper.sendCancel(mClientTransaction);
+ mState = SipSessionState.OUTGOING_CALL_CANCELING;
+ return true;
+ }
+ return false;
+ }
+
+ private boolean outgoingCallToReady(EventObject evt)
+ throws SipException {
+ if (evt instanceof ResponseEvent) {
+ ResponseEvent event = (ResponseEvent) evt;
+ Response response = event.getResponse();
+ int statusCode = response.getStatusCode();
+ if (expectResponse(Request.CANCEL, evt)) {
+ if (statusCode == Response.OK) {
+ // do nothing; wait for REQUEST_TERMINATED
+ return true;
+ }
+ } else if (expectResponse(Request.INVITE, evt)) {
+ if (statusCode == Response.OK) {
+ outgoingCall(evt); // abort Cancel
+ return true;
+ }
+ } else {
+ return false;
+ }
+
+ if (statusCode >= 400) {
+ onError(createCallbackException(response));
+ return true;
+ }
+ } else if (evt instanceof TransactionTerminatedEvent) {
+ // rfc3261#section-14.1:
+ // if re-invite gets timed out, terminate the dialog; but
+ // re-invite is not reliable, just let it go and pretend
+ // nothing happened.
+ onError(new SipException("timed out"));
+ }
+ return false;
+ }
+
+ private boolean inCall(EventObject evt) throws SipException {
+ // expect END_CALL cmd, BYE request, hold call (MakeCallCommand)
+ // OK retransmission is handled in SipStack
+ if (END_CALL == evt) {
+ // rfc3261#section-15.1.1
+ mSipHelper.sendBye(mDialog);
+ endCallNormally();
+ return true;
+ } else if (isRequestEvent(Request.INVITE, evt)) {
+ // got Re-INVITE
+ RequestEvent event = mInviteReceived = (RequestEvent) evt;
+ mState = SipSessionState.INCOMING_CALL;
+ mPeerSessionDescription = event.getRequest().getRawContent();
+ mServerTransaction = null;
+ mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription);
+ return true;
+ } else if (isRequestEvent(Request.BYE, evt)) {
+ mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
+ endCallNormally();
+ return true;
+ } else if (evt instanceof MakeCallCommand) {
+ // to change call
+ mClientTransaction = mSipHelper.sendReinvite(mDialog,
+ ((MakeCallCommand) evt).getSessionDescription());
+ mState = SipSessionState.OUTGOING_CALL;
+ return true;
+ }
+ return false;
+ }
+
+ private Exception createCallbackException(Response response) {
+ return new SipException(String.format("Response: %s (%d)",
+ response.getReasonPhrase(), response.getStatusCode()));
+ }
+
+ private void establishCall() {
+ mState = SipSessionState.IN_CALL;
+ mInCall = true;
+ mProxy.onCallEstablished(this, mPeerSessionDescription);
+ }
+
+ private void fallbackToPreviousInCall(Throwable exception) {
+ mState = SipSessionState.IN_CALL;
+ mProxy.onCallChangeFailed(this, exception.getClass().getName(),
+ exception.getMessage());
+ }
+
+ private void endCallNormally() {
+ reset();
+ mProxy.onCallEnded(this);
+ }
+
+ private void endCallOnError(Throwable exception) {
+ reset();
+ mProxy.onError(this, exception.getClass().getName(),
+ exception.getMessage());
+ }
+
+ private void onError(Throwable exception) {
+ if (mInCall) {
+ fallbackToPreviousInCall(exception);
+ } else {
+ endCallOnError(exception);
+ }
+ }
+
+ private void onRegistrationDone(int duration) {
+ mProxy.onRegistrationDone(this, duration);
+ }
+
+ private void onRegistrationFailed(Throwable exception) {
+ mProxy.onRegistrationFailed(this, exception.getClass().getName(),
+ exception.getMessage());
+ }
+ }
+
+ /**
+ * @return true if the event is a request event matching the specified
+ * method; false otherwise
+ */
+ private static boolean isRequestEvent(String method, EventObject event) {
+ try {
+ if (event instanceof RequestEvent) {
+ RequestEvent requestEvent = (RequestEvent) event;
+ return method.equals(requestEvent.getRequest().getMethod());
+ }
+ } catch (Throwable e) {
+ }
+ return false;
+ }
+
+ private static String getCseqMethod(Message message) {
+ return ((CSeqHeader) message.getHeader(CSeqHeader.NAME)).getMethod();
+ }
+
+ /**
+ * @return true if the event is a response event and the CSeqHeader method
+ * match the given arguments; false otherwise
+ */
+ private static boolean expectResponse(
+ String expectedMethod, EventObject evt) {
+ if (evt instanceof ResponseEvent) {
+ ResponseEvent event = (ResponseEvent) evt;
+ Response response = event.getResponse();
+ return expectedMethod.equalsIgnoreCase(getCseqMethod(response));
+ }
+ return false;
+ }
+
+ /**
+ * @return true if the event is a response event and the response code and
+ * CSeqHeader method match the given arguments; false otherwise
+ */
+ private static boolean expectResponse(
+ int responseCode, String expectedMethod, EventObject evt) {
+ if (evt instanceof ResponseEvent) {
+ ResponseEvent event = (ResponseEvent) evt;
+ Response response = event.getResponse();
+ if (response.getStatusCode() == responseCode) {
+ return expectedMethod.equalsIgnoreCase(getCseqMethod(response));
+ }
+ }
+ return false;
+ }
+
+ private static SipProfile createPeerProfile(Request request)
+ throws SipException {
+ try {
+ FromHeader fromHeader =
+ (FromHeader) request.getHeader(FromHeader.NAME);
+ Address address = fromHeader.getAddress();
+ SipURI uri = (SipURI) address.getURI();
+ String username = uri.getUser();
+ if (username == null) username = ANONYMOUS;
+ return new SipProfile.Builder(username, uri.getHost())
+ .setPort(uri.getPort())
+ .setDisplayName(address.getDisplayName())
+ .build();
+ } catch (InvalidArgumentException e) {
+ throw new SipException("createPeerProfile()", e);
+ } catch (ParseException e) {
+ throw new SipException("createPeerProfile()", e);
+ }
+ }
+
+ private static String log(EventObject evt) {
+ if (evt instanceof RequestEvent) {
+ return ((RequestEvent) evt).getRequest().toString();
+ } else if (evt instanceof ResponseEvent) {
+ return ((ResponseEvent) evt).getResponse().toString();
+ } else {
+ return evt.toString();
+ }
+ }
+
+ private class OptionsCommand extends EventObject {
+ public OptionsCommand() {
+ super(SipSessionGroup.this);
+ }
+ }
+
+ private class RegisterCommand extends EventObject {
+ private int mDuration;
+
+ public RegisterCommand(int duration) {
+ super(SipSessionGroup.this);
+ mDuration = duration;
+ }
+
+ public int getDuration() {
+ return mDuration;
+ }
+ }
+
+ private class MakeCallCommand extends EventObject {
+ private SessionDescription mSessionDescription;
+
+ public MakeCallCommand(SipProfile peerProfile,
+ SessionDescription sessionDescription) {
+ super(peerProfile);
+ mSessionDescription = sessionDescription;
+ }
+
+ public SipProfile getPeerProfile() {
+ return (SipProfile) getSource();
+ }
+
+ public SessionDescription getSessionDescription() {
+ return mSessionDescription;
+ }
+ }
+
+}
diff --git a/services/java/com/android/server/sip/SipSessionListenerProxy.java b/services/java/com/android/server/sip/SipSessionListenerProxy.java
new file mode 100644
index 0000000..fd49fd8
--- /dev/null
+++ b/services/java/com/android/server/sip/SipSessionListenerProxy.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2010 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.sip;
+
+import android.net.sip.ISipSession;
+import android.net.sip.ISipSessionListener;
+import android.net.sip.SipProfile;
+import android.util.Log;
+
+/** Class to help safely run a callback in a different thread. */
+class SipSessionListenerProxy extends ISipSessionListener.Stub {
+ private static final String TAG = "SipSession";
+
+ private ISipSessionListener mListener;
+
+ public void setListener(ISipSessionListener listener) {
+ mListener = listener;
+ }
+
+ public ISipSessionListener getListener() {
+ return mListener;
+ }
+
+ private void proxy(Runnable runnable) {
+ // One thread for each calling back.
+ // Note: Guarantee ordering if the issue becomes important. Currently,
+ // the chance of handling two callback events at a time is none.
+ new Thread(runnable).start();
+ }
+
+ public void onCalling(final ISipSession session) {
+ if (mListener == null) return;
+ proxy(new Runnable() {
+ public void run() {
+ try {
+ mListener.onCalling(session);
+ } catch (Throwable t) {
+ Log.w(TAG, "onCalling()", t);
+ }
+ }
+ });
+ }
+
+ public void onRinging(final ISipSession session, final SipProfile caller,
+ final byte[] sessionDescription) {
+ if (mListener == null) return;
+ proxy(new Runnable() {
+ public void run() {
+ try {
+ mListener.onRinging(session, caller, sessionDescription);
+ } catch (Throwable t) {
+ Log.w(TAG, "onRinging()", t);
+ }
+ }
+ });
+ }
+
+ public void onRingingBack(final ISipSession session) {
+ if (mListener == null) return;
+ proxy(new Runnable() {
+ public void run() {
+ try {
+ mListener.onRingingBack(session);
+ } catch (Throwable t) {
+ Log.w(TAG, "onRingingBack()", t);
+ }
+ }
+ });
+ }
+
+ public void onCallEstablished(final ISipSession session,
+ final byte[] sessionDescription) {
+ if (mListener == null) return;
+ proxy(new Runnable() {
+ public void run() {
+ try {
+ mListener.onCallEstablished(session, sessionDescription);
+ } catch (Throwable t) {
+ Log.w(TAG, "onCallEstablished()", t);
+ }
+ }
+ });
+ }
+
+ public void onCallEnded(final ISipSession session) {
+ if (mListener == null) return;
+ proxy(new Runnable() {
+ public void run() {
+ try {
+ mListener.onCallEnded(session);
+ } catch (Throwable t) {
+ Log.w(TAG, "onCallEnded()", t);
+ }
+ }
+ });
+ }
+
+ public void onCallBusy(final ISipSession session) {
+ if (mListener == null) return;
+ proxy(new Runnable() {
+ public void run() {
+ try {
+ mListener.onCallBusy(session);
+ } catch (Throwable t) {
+ Log.w(TAG, "onCallBusy()", t);
+ }
+ }
+ });
+ }
+
+ public void onCallChangeFailed(final ISipSession session,
+ final String className, final String message) {
+ if (mListener == null) return;
+ proxy(new Runnable() {
+ public void run() {
+ try {
+ mListener.onCallChangeFailed(session, className, message);
+ } catch (Throwable t) {
+ Log.w(TAG, "onCallChangeFailed()", t);
+ }
+ }
+ });
+ }
+
+ public void onError(final ISipSession session, final String className,
+ final String message) {
+ if (mListener == null) return;
+ proxy(new Runnable() {
+ public void run() {
+ try {
+ mListener.onError(session, className, message);
+ } catch (Throwable t) {
+ Log.w(TAG, "onError()", t);
+ }
+ }
+ });
+ }
+
+ public void onRegistering(final ISipSession session) {
+ if (mListener == null) return;
+ proxy(new Runnable() {
+ public void run() {
+ try {
+ mListener.onRegistering(session);
+ } catch (Throwable t) {
+ Log.w(TAG, "onRegistering()", t);
+ }
+ }
+ });
+ }
+
+ public void onRegistrationDone(final ISipSession session,
+ final int duration) {
+ if (mListener == null) return;
+ proxy(new Runnable() {
+ public void run() {
+ try {
+ mListener.onRegistrationDone(session, duration);
+ } catch (Throwable t) {
+ Log.w(TAG, "onRegistrationDone()", t);
+ }
+ }
+ });
+ }
+
+ public void onRegistrationFailed(final ISipSession session,
+ final String className, final String message) {
+ if (mListener == null) return;
+ proxy(new Runnable() {
+ public void run() {
+ try {
+ mListener.onRegistrationFailed(session, className, message);
+ } catch (Throwable t) {
+ Log.w(TAG, "onRegistrationFailed()", t);
+ }
+ }
+ });
+ }
+
+ public void onRegistrationTimeout(final ISipSession session) {
+ if (mListener == null) return;
+ proxy(new Runnable() {
+ public void run() {
+ try {
+ mListener.onRegistrationTimeout(session);
+ } catch (Throwable t) {
+ Log.w(TAG, "onRegistrationTimeout()", t);
+ }
+ }
+ });
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/MccTable.java b/telephony/java/com/android/internal/telephony/MccTable.java
index 5dd29af..9b0aa3c 100644
--- a/telephony/java/com/android/internal/telephony/MccTable.java
+++ b/telephony/java/com/android/internal/telephony/MccTable.java
@@ -416,7 +416,7 @@
table.add(new MccEntry(438,"tm",2)); //Turkmenistan
table.add(new MccEntry(440,"jp",2,"ja",14)); //Japan
table.add(new MccEntry(441,"jp",2,"ja",14)); //Japan
- table.add(new MccEntry(450,"kr",2)); //Korea (Republic of)
+ table.add(new MccEntry(450,"kr",2,"ko",13)); //Korea (Republic of)
table.add(new MccEntry(452,"vn",2)); //Viet Nam (Socialist Republic of)
table.add(new MccEntry(454,"hk",2)); //"Hong Kong, China"
table.add(new MccEntry(455,"mo",2)); //"Macao, China"
diff --git a/telephony/java/com/android/internal/telephony/SipPhoneNotifier.java b/telephony/java/com/android/internal/telephony/SipPhoneNotifier.java
new file mode 100644
index 0000000..1229d14
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SipPhoneNotifier.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2010 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.internal.telephony;
+
+import android.net.NetworkProperties;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.telephony.ITelephonyRegistry;
+
+/**
+ * Temporary. Will be removed after integrating with CallManager.
+ * 100% copy from DefaultPhoneNotifier. Cannot access its package level
+ * constructor; thus the copy.
+ * @hide
+ */
+public class SipPhoneNotifier implements PhoneNotifier {
+
+ static final String LOG_TAG = "GSM";
+ private static final boolean DBG = true;
+ private ITelephonyRegistry mRegistry;
+
+ public SipPhoneNotifier() {
+ mRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
+ "telephony.registry"));
+ }
+
+ public void notifyPhoneState(Phone sender) {
+ Call ringingCall = sender.getRingingCall();
+ String incomingNumber = "";
+ if (ringingCall != null && ringingCall.getEarliestConnection() != null){
+ incomingNumber = ringingCall.getEarliestConnection().getAddress();
+ }
+ try {
+ mRegistry.notifyCallState(convertCallState(sender.getState()), incomingNumber);
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ public void notifyServiceState(Phone sender) {
+ try {
+ mRegistry.notifyServiceState(sender.getServiceState());
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ public void notifySignalStrength(Phone sender) {
+ try {
+ mRegistry.notifySignalStrength(sender.getSignalStrength());
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ public void notifyMessageWaitingChanged(Phone sender) {
+ try {
+ mRegistry.notifyMessageWaitingChanged(sender.getMessageWaitingIndicator());
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ public void notifyCallForwardingChanged(Phone sender) {
+ try {
+ mRegistry.notifyCallForwardingChanged(sender.getCallForwardingIndicator());
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ public void notifyDataActivity(Phone sender) {
+ try {
+ mRegistry.notifyDataActivity(convertDataActivityState(sender.getDataActivityState()));
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ public void notifyDataConnection(Phone sender, String reason, String apnType) {
+ doNotifyDataConnection(sender, reason, apnType, sender.getDataConnectionState(apnType));
+ }
+
+ public void notifyDataConnection(Phone sender, String reason, String apnType,
+ Phone.DataState state) {
+ doNotifyDataConnection(sender, reason, apnType, state);
+ }
+
+ private void doNotifyDataConnection(Phone sender, String reason, String apnType,
+ Phone.DataState state) {
+ // TODO
+ // use apnType as the key to which connection we're talking about.
+ // pass apnType back up to fetch particular for this one.
+ TelephonyManager telephony = TelephonyManager.getDefault();
+ NetworkProperties networkProperties = null;
+ if (state == Phone.DataState.CONNECTED) {
+ networkProperties = sender.getNetworkProperties(apnType);
+ }
+ try {
+ mRegistry.notifyDataConnection(
+ convertDataState(state),
+ sender.isDataConnectivityPossible(), reason,
+ sender.getActiveApn(),
+ apnType,
+ networkProperties,
+ ((telephony!=null) ? telephony.getNetworkType() :
+ TelephonyManager.NETWORK_TYPE_UNKNOWN));
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ public void notifyDataConnectionFailed(Phone sender, String reason, String apnType) {
+ try {
+ mRegistry.notifyDataConnectionFailed(reason, apnType);
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ public void notifyCellLocation(Phone sender) {
+ Bundle data = new Bundle();
+ sender.getCellLocation().fillInNotifierBundle(data);
+ try {
+ mRegistry.notifyCellLocation(data);
+ } catch (RemoteException ex) {
+ // system process is dead
+ }
+ }
+
+ private void log(String s) {
+ Log.d(LOG_TAG, "[PhoneNotifier] " + s);
+ }
+
+ /**
+ * Convert the {@link State} enum into the TelephonyManager.CALL_STATE_* constants
+ * for the public API.
+ */
+ public static int convertCallState(Phone.State state) {
+ switch (state) {
+ case RINGING:
+ return TelephonyManager.CALL_STATE_RINGING;
+ case OFFHOOK:
+ return TelephonyManager.CALL_STATE_OFFHOOK;
+ default:
+ return TelephonyManager.CALL_STATE_IDLE;
+ }
+ }
+
+ /**
+ * Convert the TelephonyManager.CALL_STATE_* constants into the {@link State} enum
+ * for the public API.
+ */
+ public static Phone.State convertCallState(int state) {
+ switch (state) {
+ case TelephonyManager.CALL_STATE_RINGING:
+ return Phone.State.RINGING;
+ case TelephonyManager.CALL_STATE_OFFHOOK:
+ return Phone.State.OFFHOOK;
+ default:
+ return Phone.State.IDLE;
+ }
+ }
+
+ /**
+ * Convert the {@link DataState} enum into the TelephonyManager.DATA_* constants
+ * for the public API.
+ */
+ public static int convertDataState(Phone.DataState state) {
+ switch (state) {
+ case CONNECTING:
+ return TelephonyManager.DATA_CONNECTING;
+ case CONNECTED:
+ return TelephonyManager.DATA_CONNECTED;
+ case SUSPENDED:
+ return TelephonyManager.DATA_SUSPENDED;
+ default:
+ return TelephonyManager.DATA_DISCONNECTED;
+ }
+ }
+
+ /**
+ * Convert the TelephonyManager.DATA_* constants into {@link DataState} enum
+ * for the public API.
+ */
+ public static Phone.DataState convertDataState(int state) {
+ switch (state) {
+ case TelephonyManager.DATA_CONNECTING:
+ return Phone.DataState.CONNECTING;
+ case TelephonyManager.DATA_CONNECTED:
+ return Phone.DataState.CONNECTED;
+ case TelephonyManager.DATA_SUSPENDED:
+ return Phone.DataState.SUSPENDED;
+ default:
+ return Phone.DataState.DISCONNECTED;
+ }
+ }
+
+ /**
+ * Convert the {@link DataState} enum into the TelephonyManager.DATA_* constants
+ * for the public API.
+ */
+ public static int convertDataActivityState(Phone.DataActivityState state) {
+ switch (state) {
+ case DATAIN:
+ return TelephonyManager.DATA_ACTIVITY_IN;
+ case DATAOUT:
+ return TelephonyManager.DATA_ACTIVITY_OUT;
+ case DATAINANDOUT:
+ return TelephonyManager.DATA_ACTIVITY_INOUT;
+ case DORMANT:
+ return TelephonyManager.DATA_ACTIVITY_DORMANT;
+ default:
+ return TelephonyManager.DATA_ACTIVITY_NONE;
+ }
+ }
+
+ /**
+ * Convert the TelephonyManager.DATA_* constants into the {@link DataState} enum
+ * for the public API.
+ */
+ public static Phone.DataActivityState convertDataActivityState(int state) {
+ switch (state) {
+ case TelephonyManager.DATA_ACTIVITY_IN:
+ return Phone.DataActivityState.DATAIN;
+ case TelephonyManager.DATA_ACTIVITY_OUT:
+ return Phone.DataActivityState.DATAOUT;
+ case TelephonyManager.DATA_ACTIVITY_INOUT:
+ return Phone.DataActivityState.DATAINANDOUT;
+ case TelephonyManager.DATA_ACTIVITY_DORMANT:
+ return Phone.DataActivityState.DORMANT;
+ default:
+ return Phone.DataActivityState.NONE;
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/sip/CallFailCause.java b/telephony/java/com/android/internal/telephony/sip/CallFailCause.java
new file mode 100644
index 0000000..58fb408
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/sip/CallFailCause.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 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.internal.telephony.sip;
+
+/**
+ * Call fail causes from TS 24.008 .
+ * These are mostly the cause codes we need to distinguish for the UI.
+ * See 22.001 Annex F.4 for mapping of cause codes to local tones.
+ *
+ * {@hide}
+ *
+ */
+public interface CallFailCause {
+ static final int NORMAL_CLEARING = 16;
+ // Busy Tone
+ static final int USER_BUSY = 17;
+
+ // No Tone
+ static final int NUMBER_CHANGED = 22;
+ static final int STATUS_ENQUIRY = 30;
+ static final int NORMAL_UNSPECIFIED = 31;
+
+ // Congestion Tone
+ static final int NO_CIRCUIT_AVAIL = 34;
+ static final int TEMPORARY_FAILURE = 41;
+ static final int SWITCHING_CONGESTION = 42;
+ static final int CHANNEL_NOT_AVAIL = 44;
+ static final int QOS_NOT_AVAIL = 49;
+ static final int BEARER_NOT_AVAIL = 58;
+
+ // others
+ static final int ACM_LIMIT_EXCEEDED = 68;
+ static final int CALL_BARRED = 240;
+ static final int FDN_BLOCKED = 241;
+ static final int ERROR_UNSPECIFIED = 0xffff;
+}
diff --git a/telephony/java/com/android/internal/telephony/sip/CallProxy.java b/telephony/java/com/android/internal/telephony/sip/CallProxy.java
new file mode 100644
index 0000000..fad9663
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/sip/CallProxy.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2010 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.internal.telephony.sip;
+
+import com.android.internal.telephony.*;
+import java.util.List;
+
+// TODO: remove this class after integrating with CallManager
+class CallProxy extends Call {
+ private Call mTarget;
+
+ void setTarget(Call target) {
+ mTarget = target;
+ }
+
+ @Override
+ public List<Connection> getConnections() {
+ return mTarget.getConnections();
+ }
+
+ @Override
+ public Phone getPhone() {
+ return mTarget.getPhone();
+ }
+
+ @Override
+ public boolean isMultiparty() {
+ return mTarget.isMultiparty();
+ }
+
+ @Override
+ public void hangup() throws CallStateException {
+ mTarget.hangup();
+ }
+
+ @Override
+ public boolean hasConnection(Connection c) {
+ return mTarget.hasConnection(c);
+ }
+
+ @Override
+ public boolean hasConnections() {
+ return mTarget.hasConnections();
+ }
+
+ @Override
+ public State getState() {
+ return mTarget.getState();
+ }
+
+ @Override
+ public boolean isIdle() {
+ return mTarget.isIdle();
+ }
+
+ @Override
+ public Connection getEarliestConnection() {
+ return mTarget.getEarliestConnection();
+ }
+
+ @Override
+ public long getEarliestCreateTime() {
+ return mTarget.getEarliestCreateTime();
+ }
+
+ @Override
+ public long getEarliestConnectTime() {
+ return mTarget.getEarliestConnectTime();
+ }
+
+ @Override
+ public boolean isDialingOrAlerting() {
+ return mTarget.isDialingOrAlerting();
+ }
+
+ @Override
+ public boolean isRinging() {
+ return mTarget.isRinging();
+ }
+
+ @Override
+ public Connection getLatestConnection() {
+ return mTarget.getLatestConnection();
+ }
+
+ @Override
+ public boolean isGeneric() {
+ return mTarget.isGeneric();
+ }
+
+ @Override
+ public void setGeneric(boolean generic) {
+ mTarget.setGeneric(generic);
+ }
+
+ @Override
+ public void hangupIfAlive() {
+ mTarget.hangupIfAlive();
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/sip/SipCallBase.java b/telephony/java/com/android/internal/telephony/sip/SipCallBase.java
new file mode 100644
index 0000000..e7eda4f
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/sip/SipCallBase.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2010 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.internal.telephony.sip;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.DriverCall;
+import com.android.internal.telephony.Phone;
+
+import android.net.sip.SipManager;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import javax.sip.SipException;
+
+abstract class SipCallBase extends Call {
+ private static final int MAX_CONNECTIONS_PER_CALL = 5;
+
+ protected List<Connection> connections = new ArrayList<Connection>();
+
+ protected abstract void setState(State newState);
+
+ public void dispose() {
+ }
+
+ public List<Connection> getConnections() {
+ // FIXME should return Collections.unmodifiableList();
+ return connections;
+ }
+
+ public boolean isMultiparty() {
+ return connections.size() > 1;
+ }
+
+ public String toString() {
+ return state.toString();
+ }
+
+ /**
+ * Called by SipConnection when it has disconnected
+ */
+ void connectionDisconnected(Connection conn) {
+ if (state != State.DISCONNECTED) {
+ /* If only disconnected connections remain, we are disconnected*/
+
+ boolean hasOnlyDisconnectedConnections = true;
+
+ for (int i = 0, s = connections.size() ; i < s; i ++) {
+ if (connections.get(i).getState()
+ != State.DISCONNECTED
+ ) {
+ hasOnlyDisconnectedConnections = false;
+ break;
+ }
+ }
+
+ if (hasOnlyDisconnectedConnections) {
+ state = State.DISCONNECTED;
+ }
+ }
+ }
+
+
+ /*package*/ void detach(Connection conn) {
+ connections.remove(conn);
+
+ if (connections.size() == 0) {
+ state = State.IDLE;
+ }
+ }
+
+ /**
+ * @return true if there's no space in this call for additional
+ * connections to be added via "conference"
+ */
+ /*package*/ boolean isFull() {
+ return connections.size() == MAX_CONNECTIONS_PER_CALL;
+ }
+
+ void clearDisconnected() {
+ for (Iterator<Connection> it = connections.iterator(); it.hasNext(); ) {
+ Connection c = it.next();
+ if (c.getState() == State.DISCONNECTED) it.remove();
+ }
+
+ if (connections.isEmpty()) setState(State.IDLE);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/sip/SipCommandInterface.java b/telephony/java/com/android/internal/telephony/sip/SipCommandInterface.java
new file mode 100644
index 0000000..33c89f8
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/sip/SipCommandInterface.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2010 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.internal.telephony.sip;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.internal.telephony.BaseCommands;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
+
+/**
+ * SIP doesn't need CommandsInterface. The class does nothing but made to work
+ * with PhoneBase's constructor.
+ */
+class SipCommandInterface extends BaseCommands implements CommandsInterface {
+ SipCommandInterface(Context context) {
+ super(context);
+ }
+
+ @Override public void setOnNITZTime(Handler h, int what, Object obj) {
+ }
+
+ public void getIccCardStatus(Message result) {
+ }
+
+ public void supplyIccPin(String pin, Message result) {
+ }
+
+ public void supplyIccPuk(String puk, String newPin, Message result) {
+ }
+
+ public void supplyIccPin2(String pin, Message result) {
+ }
+
+ public void supplyIccPuk2(String puk, String newPin2, Message result) {
+ }
+
+ public void changeIccPin(String oldPin, String newPin, Message result) {
+ }
+
+ public void changeIccPin2(String oldPin2, String newPin2, Message result) {
+ }
+
+ public void changeBarringPassword(String facility, String oldPwd,
+ String newPwd, Message result) {
+ }
+
+ public void supplyNetworkDepersonalization(String netpin, Message result) {
+ }
+
+ public void getCurrentCalls(Message result) {
+ }
+
+ @Deprecated public void getPDPContextList(Message result) {
+ }
+
+ public void getDataCallList(Message result) {
+ }
+
+ public void dial(String address, int clirMode, Message result) {
+ }
+
+ public void dial(String address, int clirMode, UUSInfo uusInfo,
+ Message result) {
+ }
+
+ public void getIMSI(Message result) {
+ }
+
+ public void getIMEI(Message result) {
+ }
+
+ public void getIMEISV(Message result) {
+ }
+
+
+ public void hangupConnection (int gsmIndex, Message result) {
+ }
+
+ public void hangupWaitingOrBackground (Message result) {
+ }
+
+ public void hangupForegroundResumeBackground (Message result) {
+ }
+
+ public void switchWaitingOrHoldingAndActive (Message result) {
+ }
+
+ public void conference (Message result) {
+ }
+
+
+ public void setPreferredVoicePrivacy(boolean enable, Message result) {
+ }
+
+ public void getPreferredVoicePrivacy(Message result) {
+ }
+
+ public void separateConnection (int gsmIndex, Message result) {
+ }
+
+ public void acceptCall (Message result) {
+ }
+
+ public void rejectCall (Message result) {
+ }
+
+ public void explicitCallTransfer (Message result) {
+ }
+
+ public void getLastCallFailCause (Message result) {
+ }
+
+ /** @deprecated */
+ public void getLastPdpFailCause (Message result) {
+ }
+
+ public void getLastDataCallFailCause (Message result) {
+ }
+
+ public void setMute (boolean enableMute, Message response) {
+ }
+
+ public void getMute (Message response) {
+ }
+
+ public void getSignalStrength (Message result) {
+ }
+
+ public void getRegistrationState (Message result) {
+ }
+
+ public void getGPRSRegistrationState (Message result) {
+ }
+
+ public void getOperator(Message result) {
+ }
+
+ public void sendDtmf(char c, Message result) {
+ }
+
+ public void startDtmf(char c, Message result) {
+ }
+
+ public void stopDtmf(Message result) {
+ }
+
+ public void sendBurstDtmf(String dtmfString, int on, int off,
+ Message result) {
+ }
+
+ public void sendSMS (String smscPDU, String pdu, Message result) {
+ }
+
+ public void sendCdmaSms(byte[] pdu, Message result) {
+ }
+
+ public void deleteSmsOnSim(int index, Message response) {
+ }
+
+ public void deleteSmsOnRuim(int index, Message response) {
+ }
+
+ public void writeSmsToSim(int status, String smsc, String pdu, Message response) {
+ }
+
+ public void writeSmsToRuim(int status, String pdu, Message response) {
+ }
+
+ public void setupDefaultPDP(String apn, String user, String password,
+ Message result) {
+ }
+
+ public void deactivateDefaultPDP(int cid, Message result) {
+ }
+
+ public void setupDataCall(String radioTechnology, String profile,
+ String apn, String user, String password, String authType,
+ Message result) {
+ }
+
+ public void deactivateDataCall(int cid, Message result) {
+ }
+
+ public void setRadioPower(boolean on, Message result) {
+ }
+
+ public void setSuppServiceNotifications(boolean enable, Message result) {
+ }
+
+ public void acknowledgeLastIncomingGsmSms(boolean success, int cause,
+ Message result) {
+ }
+
+ public void acknowledgeLastIncomingCdmaSms(boolean success, int cause,
+ Message result) {
+ }
+
+
+ public void iccIO (int command, int fileid, String path, int p1, int p2,
+ int p3, String data, String pin2, Message result) {
+ }
+
+ public void getCLIR(Message result) {
+ }
+
+ public void setCLIR(int clirMode, Message result) {
+ }
+
+ public void queryCallWaiting(int serviceClass, Message response) {
+ }
+
+ public void setCallWaiting(boolean enable, int serviceClass,
+ Message response) {
+ }
+
+ public void setNetworkSelectionModeAutomatic(Message response) {
+ }
+
+ public void setNetworkSelectionModeManual(
+ String operatorNumeric, Message response) {
+ }
+
+ public void getNetworkSelectionMode(Message response) {
+ }
+
+ public void getAvailableNetworks(Message response) {
+ }
+
+ public void setCallForward(int action, int cfReason, int serviceClass,
+ String number, int timeSeconds, Message response) {
+ }
+
+ public void queryCallForwardStatus(int cfReason, int serviceClass,
+ String number, Message response) {
+ }
+
+ public void queryCLIP(Message response) {
+ }
+
+ public void getBasebandVersion (Message response) {
+ }
+
+ public void queryFacilityLock (String facility, String password,
+ int serviceClass, Message response) {
+ }
+
+ public void setFacilityLock (String facility, boolean lockState,
+ String password, int serviceClass, Message response) {
+ }
+
+ public void sendUSSD (String ussdString, Message response) {
+ }
+
+ public void cancelPendingUssd (Message response) {
+ }
+
+ public void resetRadio(Message result) {
+ }
+
+ public void invokeOemRilRequestRaw(byte[] data, Message response) {
+ }
+
+ public void invokeOemRilRequestStrings(String[] strings, Message response) {
+ }
+
+ public void setBandMode (int bandMode, Message response) {
+ }
+
+ public void queryAvailableBandMode (Message response) {
+ }
+
+ public void sendTerminalResponse(String contents, Message response) {
+ }
+
+ public void sendEnvelope(String contents, Message response) {
+ }
+
+ public void handleCallSetupRequestFromSim(
+ boolean accept, Message response) {
+ }
+
+ public void setPreferredNetworkType(int networkType , Message response) {
+ }
+
+ public void getPreferredNetworkType(Message response) {
+ }
+
+ public void getNeighboringCids(Message response) {
+ }
+
+ public void setLocationUpdates(boolean enable, Message response) {
+ }
+
+ public void getSmscAddress(Message result) {
+ }
+
+ public void setSmscAddress(String address, Message result) {
+ }
+
+ public void reportSmsMemoryStatus(boolean available, Message result) {
+ }
+
+ public void reportStkServiceIsRunning(Message result) {
+ }
+
+ public void getGsmBroadcastConfig(Message response) {
+ }
+
+ public void setGsmBroadcastConfig(SmsBroadcastConfigInfo[] config, Message response) {
+ }
+
+ public void setGsmBroadcastActivation(boolean activate, Message response) {
+ }
+
+
+ // ***** Methods for CDMA support
+ public void getDeviceIdentity(Message response) {
+ }
+
+ public void getCDMASubscription(Message response) {
+ }
+
+ public void setPhoneType(int phoneType) { //Set by CDMAPhone and GSMPhone constructor
+ }
+
+ public void queryCdmaRoamingPreference(Message response) {
+ }
+
+ public void setCdmaRoamingPreference(int cdmaRoamingType, Message response) {
+ }
+
+ public void setCdmaSubscription(int cdmaSubscription , Message response) {
+ }
+
+ public void queryTTYMode(Message response) {
+ }
+
+ public void setTTYMode(int ttyMode, Message response) {
+ }
+
+ public void sendCDMAFeatureCode(String FeatureCode, Message response) {
+ }
+
+ public void getCdmaBroadcastConfig(Message response) {
+ }
+
+ public void setCdmaBroadcastConfig(int[] configValuesArray, Message response) {
+ }
+
+ public void setCdmaBroadcastActivation(boolean activate, Message response) {
+ }
+
+ public void exitEmergencyCallbackMode(Message response) {
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java b/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java
new file mode 100644
index 0000000..ad05b82
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/sip/SipConnectionBase.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2010 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.internal.telephony.sip;
+
+import android.content.Context;
+import android.net.sip.SipAudioCall;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.SystemClock;
+import android.util.Log;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+
+import com.android.internal.telephony.*;
+
+abstract class SipConnectionBase extends Connection {
+ //***** Event Constants
+ private static final int EVENT_DTMF_DONE = 1;
+ private static final int EVENT_PAUSE_DONE = 2;
+ private static final int EVENT_NEXT_POST_DIAL = 3;
+ private static final int EVENT_WAKE_LOCK_TIMEOUT = 4;
+
+ //***** Constants
+ private static final int PAUSE_DELAY_FIRST_MILLIS = 100;
+ private static final int PAUSE_DELAY_MILLIS = 3 * 1000;
+ private static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000;
+
+ private static final String LOG_TAG = "SIP_CONN";
+
+ private SipAudioCall mSipAudioCall;
+
+ private String dialString; // outgoing calls only
+ private String postDialString; // outgoing calls only
+ private int nextPostDialChar; // index into postDialString
+ private boolean isIncoming;
+ private boolean disconnected;
+
+ int index; // index in SipCallTracker.connections[], -1 if unassigned
+ // The Sip index is 1 + this
+
+ /*
+ * These time/timespan values are based on System.currentTimeMillis(),
+ * i.e., "wall clock" time.
+ */
+ private long createTime;
+ private long connectTime;
+ private long disconnectTime;
+
+ /*
+ * These time/timespan values are based on SystemClock.elapsedRealTime(),
+ * i.e., time since boot. They are appropriate for comparison and
+ * calculating deltas.
+ */
+ private long connectTimeReal;
+ private long duration;
+ private long holdingStartTime; // The time when the Connection last transitioned
+ // into HOLDING
+
+ private DisconnectCause mCause = DisconnectCause.NOT_DISCONNECTED;
+ private PostDialState postDialState = PostDialState.NOT_STARTED;
+
+ SipConnectionBase(String calleeSipUri) {
+ dialString = calleeSipUri;
+
+ postDialString = PhoneNumberUtils.extractPostDialPortion(dialString);
+
+ isIncoming = false;
+ createTime = System.currentTimeMillis();
+ }
+
+ protected void setState(Call.State state) {
+ switch (state) {
+ case ACTIVE:
+ connectTimeReal = SystemClock.elapsedRealtime();
+ connectTime = System.currentTimeMillis();
+ break;
+ case DISCONNECTED:
+ duration = SystemClock.elapsedRealtime() - connectTimeReal;
+ disconnectTime = System.currentTimeMillis();
+ break;
+ case HOLDING:
+ holdingStartTime = SystemClock.elapsedRealtime();
+ break;
+ }
+ }
+
+ @Override
+ public long getCreateTime() {
+ return createTime;
+ }
+
+ @Override
+ public long getConnectTime() {
+ return connectTime;
+ }
+
+ @Override
+ public long getDisconnectTime() {
+ return disconnectTime;
+ }
+
+ @Override
+ public long getDurationMillis() {
+ if (connectTimeReal == 0) {
+ return 0;
+ } else if (duration == 0) {
+ return SystemClock.elapsedRealtime() - connectTimeReal;
+ } else {
+ return duration;
+ }
+ }
+
+ @Override
+ public long getHoldDurationMillis() {
+ if (getState() != Call.State.HOLDING) {
+ // If not holding, return 0
+ return 0;
+ } else {
+ return SystemClock.elapsedRealtime() - holdingStartTime;
+ }
+ }
+
+ @Override
+ public DisconnectCause getDisconnectCause() {
+ return mCause;
+ }
+
+ void setDisconnectCause(DisconnectCause cause) {
+ mCause = cause;
+ }
+
+ @Override
+ public PostDialState getPostDialState() {
+ return postDialState;
+ }
+
+ @Override
+ public void proceedAfterWaitChar() {
+ // TODO
+ }
+
+ @Override
+ public void proceedAfterWildChar(String str) {
+ // TODO
+ }
+
+ @Override
+ public void cancelPostDial() {
+ // TODO
+ }
+
+ protected abstract Phone getPhone();
+
+ DisconnectCause disconnectCauseFromCode(int causeCode) {
+ /**
+ * See 22.001 Annex F.4 for mapping of cause codes
+ * to local tones
+ */
+
+ switch (causeCode) {
+ case CallFailCause.USER_BUSY:
+ return DisconnectCause.BUSY;
+
+ case CallFailCause.NO_CIRCUIT_AVAIL:
+ case CallFailCause.TEMPORARY_FAILURE:
+ case CallFailCause.SWITCHING_CONGESTION:
+ case CallFailCause.CHANNEL_NOT_AVAIL:
+ case CallFailCause.QOS_NOT_AVAIL:
+ case CallFailCause.BEARER_NOT_AVAIL:
+ return DisconnectCause.CONGESTION;
+
+ case CallFailCause.ACM_LIMIT_EXCEEDED:
+ return DisconnectCause.LIMIT_EXCEEDED;
+
+ case CallFailCause.CALL_BARRED:
+ return DisconnectCause.CALL_BARRED;
+
+ case CallFailCause.FDN_BLOCKED:
+ return DisconnectCause.FDN_BLOCKED;
+
+ case CallFailCause.ERROR_UNSPECIFIED:
+ case CallFailCause.NORMAL_CLEARING:
+ default:
+ Phone phone = getPhone();
+ int serviceState = phone.getServiceState().getState();
+ if (serviceState == ServiceState.STATE_POWER_OFF) {
+ return DisconnectCause.POWER_OFF;
+ } else if (serviceState == ServiceState.STATE_OUT_OF_SERVICE
+ || serviceState == ServiceState.STATE_EMERGENCY_ONLY ) {
+ return DisconnectCause.OUT_OF_SERVICE;
+ } else if (causeCode == CallFailCause.ERROR_UNSPECIFIED) {
+ return DisconnectCause.ERROR_UNSPECIFIED;
+ } else if (causeCode == CallFailCause.NORMAL_CLEARING) {
+ return DisconnectCause.NORMAL;
+ } else {
+ // If nothing else matches, report unknown call drop reason
+ // to app, not NORMAL call end.
+ return DisconnectCause.ERROR_UNSPECIFIED;
+ }
+ }
+ }
+
+ @Override
+ public String getRemainingPostDialString() {
+ if (postDialState == PostDialState.CANCELLED
+ || postDialState == PostDialState.COMPLETE
+ || postDialString == null
+ || postDialString.length() <= nextPostDialChar) {
+ return "";
+ }
+
+ return postDialString.substring(nextPostDialChar);
+ }
+
+ private void log(String msg) {
+ Log.d(LOG_TAG, "[SipConn] " + msg);
+ }
+
+ @Override
+ public int getNumberPresentation() {
+ // TODO: add PRESENTATION_URL
+ return Connection.PRESENTATION_ALLOWED;
+ }
+
+ @Override
+ public UUSInfo getUUSInfo() {
+ // FIXME: what's this for SIP?
+ return null;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
new file mode 100755
index 0000000..e11bd42
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -0,0 +1,759 @@
+/*
+ * Copyright (C) 2010 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.internal.telephony.sip;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.net.rtp.AudioGroup;
+import android.net.sip.SipAudioCall;
+import android.net.sip.SipManager;
+import android.net.sip.SipProfile;
+import android.net.sip.SipSessionState;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.os.SystemProperties;
+import android.preference.PreferenceManager;
+import android.provider.Telephony;
+import android.telephony.CellLocation;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallerInfo;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.DataConnection;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.IccFileHandler;
+import com.android.internal.telephony.IccPhoneBookInterfaceManager;
+import com.android.internal.telephony.IccSmsInterfaceManager;
+import com.android.internal.telephony.MmiCode;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.PhoneNotifier;
+import com.android.internal.telephony.PhoneProxy;
+import com.android.internal.telephony.PhoneSubInfo;
+import com.android.internal.telephony.TelephonyProperties;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.sip.SipException;
+
+/**
+ * {@hide}
+ */
+public class SipPhone extends SipPhoneBase {
+ private static final String LOG_TAG = "SipPhone";
+ private static final boolean LOCAL_DEBUG = true;
+
+ //private List<SipConnection> connections = new ArrayList<SipConnection>();
+
+ // A call that is ringing or (call) waiting
+ private SipCall ringingCall = new SipCall();
+ private SipCall foregroundCall = new SipCall();
+ private SipCall backgroundCall = new SipCall();
+
+ private SipManager mSipManager;
+ private SipProfile mProfile;
+
+ SipPhone (Context context, PhoneNotifier notifier, SipProfile profile) {
+ super(context, notifier);
+
+ Log.v(LOG_TAG, " +++++++++++++++++++++ new SipPhone: " + profile.getUriString());
+ ringingCall = new SipCall();
+ foregroundCall = new SipCall();
+ backgroundCall = new SipCall();
+ mProfile = profile;
+ mSipManager = SipManager.getInstance(context);
+
+ // FIXME: what's this for SIP?
+ //Change the system property
+ //SystemProperties.set(TelephonyProperties.CURRENT_ACTIVE_PHONE,
+ // new Integer(Phone.PHONE_TYPE_GSM).toString());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (!(o instanceof SipPhone)) return false;
+ SipPhone that = (SipPhone) o;
+ return mProfile.getUriString().equals(that.mProfile.getUriString());
+ }
+
+ public String getPhoneName() {
+ return mProfile.getProfileName();
+ }
+
+ public boolean canTake(Object incomingCall) {
+ synchronized (SipPhone.class) {
+ if (!(incomingCall instanceof SipAudioCall)) return false;
+ if (ringingCall.getState().isAlive()) return false;
+
+ // FIXME: is it true that we cannot take any incoming call if
+ // both foreground and background are active
+ if (foregroundCall.getState().isAlive()
+ && backgroundCall.getState().isAlive()) {
+ return false;
+ }
+
+ SipAudioCall sipAudioCall = (SipAudioCall) incomingCall;
+ Log.v(LOG_TAG, " ++++++ taking call from: "
+ + sipAudioCall.getPeerProfile().getUriString());
+ String localUri = sipAudioCall.getLocalProfile().getUriString();
+ if (localUri.equals(mProfile.getUriString())) {
+ boolean makeCallWait = foregroundCall.getState().isAlive();
+ ringingCall.initIncomingCall(sipAudioCall, makeCallWait);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public void acceptCall() throws CallStateException {
+ synchronized (SipPhone.class) {
+ // FIXME if SWITCH fails, should retry with ANSWER
+ // in case the active/holding call disappeared and this
+ // is no longer call waiting
+
+ if (ringingCall.getState() == Call.State.INCOMING) {
+ Log.v(LOG_TAG, "acceptCall");
+ // Always unmute when answering a new call
+ setMute(false);
+ // make ringingCall foreground
+ foregroundCall.switchWith(ringingCall);
+ foregroundCall.acceptCall();
+ } else if (ringingCall.getState() == Call.State.WAITING) {
+ setMute(false);
+ switchHoldingAndActive();
+ // make ringingCall foreground
+ foregroundCall.switchWith(ringingCall);
+ foregroundCall.acceptCall();
+ } else {
+ throw new CallStateException("phone not ringing");
+ }
+ }
+ }
+
+ public void rejectCall() throws CallStateException {
+ synchronized (SipPhone.class) {
+ if (ringingCall.getState().isRinging()) {
+ Log.v(LOG_TAG, "rejectCall");
+ ringingCall.rejectCall();
+ } else {
+ throw new CallStateException("phone not ringing");
+ }
+ }
+ }
+
+ public Connection dial(String dialString) throws CallStateException {
+ synchronized (SipPhone.class) {
+ return dialInternal(dialString);
+ }
+ }
+
+ private Connection dialInternal(String dialString)
+ throws CallStateException {
+ // TODO: parse SIP URL?
+ // Need to make sure dialString gets parsed properly
+ //String newDialString = PhoneNumberUtils.stripSeparators(dialString);
+ //return mCT.dial(newDialString);
+ clearDisconnected();
+
+ if (!canDial()) {
+ throw new CallStateException("cannot dial in current state");
+ }
+ if (foregroundCall.getState() == SipCall.State.ACTIVE) {
+ switchHoldingAndActive();
+ }
+ if (foregroundCall.getState() != SipCall.State.IDLE) {
+ //we should have failed in !canDial() above before we get here
+ throw new CallStateException("cannot dial in current state");
+ }
+
+ setMute(false);
+ //cm.dial(pendingMO.address, clirMode, obtainCompleteMessage());
+ try {
+ Connection c = foregroundCall.dial(dialString);
+ return c;
+ } catch (SipException e) {
+ Log.e(LOG_TAG, "dial()", e);
+ throw new CallStateException("dial error: " + e);
+ }
+ }
+
+ public void switchHoldingAndActive() throws CallStateException {
+ Log.v(LOG_TAG, " ~~~~~~ switch fg and bg");
+ synchronized (SipPhone.class) {
+ foregroundCall.switchWith(backgroundCall);
+ if (backgroundCall.getState().isAlive()) backgroundCall.hold();
+ if (foregroundCall.getState().isAlive()) foregroundCall.unhold();
+ }
+ }
+
+ public boolean canConference() {
+ return true;
+ }
+
+ public void conference() throws CallStateException {
+ // TODO
+ }
+
+ public boolean canTransfer() {
+ return false;
+ }
+
+ public void explicitCallTransfer() throws CallStateException {
+ //mCT.explicitCallTransfer();
+ }
+
+ public void clearDisconnected() {
+ ringingCall.clearDisconnected();
+ foregroundCall.clearDisconnected();
+ backgroundCall.clearDisconnected();
+
+ updatePhoneState();
+ notifyPreciseCallStateChanged();
+ }
+
+ public void sendDtmf(char c) {
+ if (!PhoneNumberUtils.is12Key(c)) {
+ Log.e(LOG_TAG,
+ "sendDtmf called with invalid character '" + c + "'");
+ } else if (foregroundCall.getState().isAlive()) {
+ foregroundCall.sendDtmf(c);
+ }
+ }
+
+ public void startDtmf(char c) {
+ if (!PhoneNumberUtils.is12Key(c)) {
+ Log.e(LOG_TAG,
+ "startDtmf called with invalid character '" + c + "'");
+ } else {
+ sendDtmf(c);
+ }
+ }
+
+ public void stopDtmf() {
+ // no op
+ }
+
+ public void sendBurstDtmf(String dtmfString) {
+ Log.e(LOG_TAG, "[SipPhone] sendBurstDtmf() is a CDMA method");
+ }
+
+ public void getOutgoingCallerIdDisplay(Message onComplete) {
+ // FIXME: what to reply?
+ AsyncResult.forMessage(onComplete, null, null);
+ onComplete.sendToTarget();
+ }
+
+ public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
+ Message onComplete) {
+ // FIXME: what's this for SIP?
+ AsyncResult.forMessage(onComplete, null, null);
+ onComplete.sendToTarget();
+ }
+
+ public void getCallWaiting(Message onComplete) {
+ // FIXME: what to reply?
+ AsyncResult.forMessage(onComplete, null, null);
+ onComplete.sendToTarget();
+ }
+
+ public void setCallWaiting(boolean enable, Message onComplete) {
+ // FIXME: what to reply?
+ Log.e(LOG_TAG, "call waiting not supported");
+ }
+
+ public void setMute(boolean muted) {
+ foregroundCall.setMute(muted);
+ }
+
+ public boolean getMute() {
+ return foregroundCall.getMute();
+ }
+
+ public Call getForegroundCall() {
+ return foregroundCall;
+ }
+
+ public Call getBackgroundCall() {
+ return backgroundCall;
+ }
+
+ public Call getRingingCall() {
+ return ringingCall;
+ }
+
+ public ServiceState getServiceState() {
+ // FIXME: we may need to provide this when data connectivity is lost
+ // or when server is down
+ return super.getServiceState();
+ }
+
+ private String getUriString(SipProfile p) {
+ // SipProfile.getUriString() may contain "SIP:" and port
+ return p.getUserName() + "@" + getSipDomain(p);
+ }
+
+ private String getSipDomain(SipProfile p) {
+ String domain = p.getSipDomain();
+ // TODO: move this to SipProfile
+ if (domain.endsWith(":5060")) {
+ return domain.substring(0, domain.length() - 5);
+ } else {
+ return domain;
+ }
+ }
+
+ private class SipCall extends SipCallBase {
+ void switchWith(SipCall that) {
+ synchronized (SipPhone.class) {
+ SipCall tmp = new SipCall();
+ tmp.takeOver(this);
+ this.takeOver(that);
+ that.takeOver(tmp);
+ }
+ }
+
+ private void takeOver(SipCall that) {
+ connections = that.connections;
+ state = that.state;
+ for (Connection c : connections) {
+ ((SipConnection) c).changeOwner(this);
+ }
+ }
+
+ @Override
+ public Phone getPhone() {
+ return SipPhone.this;
+ }
+
+ @Override
+ public List<Connection> getConnections() {
+ synchronized (SipPhone.class) {
+ // FIXME should return Collections.unmodifiableList();
+ return connections;
+ }
+ }
+
+ private CallerInfo getCallerInfo(String number) {
+ CallerInfo info = CallerInfo.getCallerInfo(mContext, number);
+ if ((info == null) || (info.name == null)) return null;
+ Log.v(LOG_TAG, "++******++ got info from contact:");
+ Log.v(LOG_TAG, " name: " + info.name);
+ Log.v(LOG_TAG, " numb: " + info.phoneNumber);
+ Log.v(LOG_TAG, " pres: " + info.numberPresentation);
+ return info;
+ }
+
+ Connection dial(String calleeSipUri) throws SipException {
+ CallerInfo info = getCallerInfo(calleeSipUri);
+ if (!calleeSipUri.contains("@")) {
+ calleeSipUri += "@" + getSipDomain(mProfile);
+ if (info != null) info.phoneNumber = calleeSipUri;
+ }
+ try {
+ SipProfile callee =
+ new SipProfile.Builder(calleeSipUri).build();
+ SipConnection c = new SipConnection(this, callee, info);
+ connections.add(c);
+ c.dial();
+ setState(Call.State.DIALING);
+ return c;
+ } catch (ParseException e) {
+ // TODO: notify someone
+ throw new SipException("dial", e);
+ }
+ }
+
+ @Override
+ public void hangup() throws CallStateException {
+ Log.v(LOG_TAG, "hang up call: " + getState() + ": " + this
+ + " on phone " + getPhone());
+ CallStateException excp = null;
+ for (Connection c : connections) {
+ try {
+ c.hangup();
+ } catch (CallStateException e) {
+ excp = e;
+ }
+ }
+ if (excp != null) throw excp;
+ setState(State.DISCONNECTING);
+ }
+
+ void initIncomingCall(SipAudioCall sipAudioCall, boolean makeCallWait) {
+ SipProfile callee = sipAudioCall.getPeerProfile();
+ CallerInfo info = getCallerInfo(getUriString(callee));
+ if (info == null) info = getCallerInfo(callee.getUserName());
+ if (info == null) info = getCallerInfo(callee.getDisplayName());
+ SipConnection c = new SipConnection(this, callee, info);
+ connections.add(c);
+
+ Call.State newState = makeCallWait ? State.WAITING : State.INCOMING;
+ c.initIncomingCall(sipAudioCall, newState);
+
+ setState(newState);
+ notifyNewRingingConnectionP(c);
+ }
+
+ void rejectCall() throws CallStateException {
+ hangup();
+ }
+
+ void acceptCall() throws CallStateException {
+ if (this != foregroundCall) {
+ throw new CallStateException("acceptCall() in a non-fg call");
+ }
+ if (connections.size() != 1) {
+ throw new CallStateException("acceptCall() in a conf call");
+ }
+ ((SipConnection) connections.get(0)).acceptCall();
+ }
+
+ void hold() throws CallStateException {
+ AudioGroup audioGroup = getAudioGroup();
+ if (audioGroup == null) return;
+ audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
+ setState(State.HOLDING);
+ for (Connection c : connections) ((SipConnection) c).hold();
+ }
+
+ void unhold() throws CallStateException {
+ AudioGroup audioGroup = getAudioGroup();
+ if (audioGroup == null) return;
+ audioGroup.setMode(AudioGroup.MODE_NORMAL);
+ setState(State.ACTIVE);
+ for (Connection c : connections) ((SipConnection) c).unhold();
+ }
+
+ void setMute(boolean muted) {
+ AudioGroup audioGroup = getAudioGroup();
+ if (audioGroup == null) return;
+ audioGroup.setMode(
+ muted ? AudioGroup.MODE_MUTED : AudioGroup.MODE_NORMAL);
+ }
+
+ boolean getMute() {
+ AudioGroup audioGroup = getAudioGroup();
+ if (audioGroup == null) return false;
+ return (audioGroup.getMode() == AudioGroup.MODE_MUTED);
+ }
+
+ void sendDtmf(char c) {
+ AudioGroup audioGroup = getAudioGroup();
+ if (audioGroup == null) return;
+ audioGroup.sendDtmf(convertDtmf(c));
+ }
+
+ private int convertDtmf(char c) {
+ int code = c - '0';
+ if ((code < 0) || (code > 9)) {
+ switch (c) {
+ case '*': return 10;
+ case '#': return 11;
+ case 'A': return 12;
+ case 'B': return 13;
+ case 'C': return 14;
+ case 'D': return 15;
+ default:
+ throw new IllegalArgumentException(
+ "invalid DTMF char: " + (int) c);
+ }
+ }
+ return code;
+ }
+
+ @Override
+ protected void setState(State newState) {
+ if (state != newState) {
+ Log.v(LOG_TAG, "++******++ call state changed: " + state
+ + " --> " + newState + ": " + this + ": on phone "
+ + getPhone() + " " + connections.size());
+
+ if (newState == Call.State.ALERTING) {
+ state = newState; // need in ALERTING to enable ringback
+ SipPhone.this.startRingbackTone();
+ } else if (state == Call.State.ALERTING) {
+ SipPhone.this.stopRingbackTone();
+ }
+ state = newState;
+ updatePhoneState();
+ notifyPreciseCallStateChanged();
+ }
+ }
+
+ void onConnectionStateChanged(SipConnection conn) {
+ // this can be called back when a conf call is formed
+ if (state != State.ACTIVE) {
+ setState(conn.getState());
+ }
+ }
+
+ void onConnectionEnded(SipConnection conn) {
+ // set state to DISCONNECTED only when all conns are disconnected
+ if (state != State.DISCONNECTED) {
+ boolean allConnectionsDisconnected = true;
+ for (Connection c : connections) {
+ if (c.getState() != State.DISCONNECTED) {
+ allConnectionsDisconnected = false;
+ break;
+ }
+ }
+ if (allConnectionsDisconnected) setState(State.DISCONNECTED);
+ }
+ notifyDisconnectP(conn);
+ }
+
+ private AudioGroup getAudioGroup() {
+ if (connections.isEmpty()) return null;
+ return ((SipConnection) connections.get(0)).getAudioGroup();
+ }
+ }
+
+ private class SipConnection extends SipConnectionBase {
+ private SipCall mOwner;
+ private SipAudioCall mSipAudioCall;
+ private Call.State mState = Call.State.IDLE;
+ private SipProfile mPeer;
+ private boolean mIncoming = false;
+
+ private SipAudioCallAdapter mAdapter = new SipAudioCallAdapter() {
+ @Override
+ protected void onCallEnded(DisconnectCause cause) {
+ if (getDisconnectCause() != DisconnectCause.LOCAL) {
+ setDisconnectCause(cause);
+ }
+ synchronized (SipPhone.class) {
+ setState(Call.State.DISCONNECTED);
+ mOwner.onConnectionEnded(SipConnection.this);
+ Log.v(LOG_TAG, "-------- connection ended: "
+ + mPeer.getUriString() + ": "
+ + mSipAudioCall.getState() + ", cause: "
+ + getDisconnectCause() + ", on phone "
+ + getPhone());
+ }
+ }
+
+ @Override
+ public void onChanged(SipAudioCall call) {
+ synchronized (SipPhone.class) {
+ Call.State newState = getCallStateFrom(call);
+ if (mState == newState) return;
+ if (newState == Call.State.INCOMING) {
+ setState(mOwner.getState()); // INCOMING or WAITING
+ } else {
+ setState(newState);
+ }
+ mOwner.onConnectionStateChanged(SipConnection.this);
+ Log.v(LOG_TAG, "++******++ connection state changed: "
+ + mPeer.getUriString() + ": " + mState
+ + " on phone " + getPhone());
+ }
+ }
+
+ @Override
+ protected void onError(String errorMessage) {
+ Log.w(LOG_TAG, "SIP error: " + errorMessage);
+ if (mSipAudioCall.isInCall()) {
+ // Don't end the call when in call.
+ // TODO: how to deliver the error to PhoneApp
+ return;
+ }
+
+ // FIXME: specify error
+ onCallEnded(DisconnectCause.ERROR_UNSPECIFIED);
+ }
+ };
+
+ public SipConnection(SipCall owner, SipProfile callee,
+ CallerInfo info) {
+ super(getUriString(callee));
+ mOwner = owner;
+ mPeer = callee;
+ if (info == null) info = createCallerInfo();
+ setUserData(info);
+ }
+
+ private CallerInfo createCallerInfo() {
+ SipProfile p = mPeer;
+ String name = p.getDisplayName();
+ if (TextUtils.isEmpty(name)) name = p.getUserName();
+ CallerInfo info = new CallerInfo();
+ info.name = name;
+ info.phoneNumber = getUriString(p);
+ return info;
+ }
+
+ void initIncomingCall(SipAudioCall sipAudioCall, Call.State newState) {
+ setState(newState);
+ mSipAudioCall = sipAudioCall;
+ sipAudioCall.setListener(mAdapter); // call back to set state
+ mIncoming = true;
+ }
+
+ void acceptCall() throws CallStateException {
+ try {
+ mSipAudioCall.answerCall();
+ } catch (SipException e) {
+ throw new CallStateException("acceptCall(): " + e);
+ }
+ }
+
+ void changeOwner(SipCall owner) {
+ mOwner = owner;
+ }
+
+ AudioGroup getAudioGroup() {
+ if (mSipAudioCall == null) return null;
+ return mSipAudioCall.getAudioGroup();
+ }
+
+ void dial() throws SipException {
+ setState(Call.State.DIALING);
+ mSipAudioCall = mSipManager.makeAudioCall(mContext, mProfile,
+ mPeer, null);
+ mSipAudioCall.setRingbackToneEnabled(false);
+ mSipAudioCall.setListener(mAdapter);
+ }
+
+ void hold() throws CallStateException {
+ try {
+ mSipAudioCall.holdCall();
+ } catch (SipException e) {
+ throw new CallStateException("hold(): " + e);
+ }
+ }
+
+ void unhold() throws CallStateException {
+ try {
+ mSipAudioCall.continueCall();
+ } catch (SipException e) {
+ throw new CallStateException("unhold(): " + e);
+ }
+ }
+
+ @Override
+ protected void setState(Call.State state) {
+ if (state == mState) return;
+ super.setState(state);
+ mState = state;
+ }
+
+ @Override
+ public Call.State getState() {
+ return mState;
+ }
+
+ @Override
+ public boolean isIncoming() {
+ return mIncoming;
+ }
+
+ @Override
+ public String getAddress() {
+ return getUriString(mPeer);
+ }
+
+ @Override
+ public SipCall getCall() {
+ return mOwner;
+ }
+
+ @Override
+ protected Phone getPhone() {
+ return mOwner.getPhone();
+ }
+
+ @Override
+ public void hangup() throws CallStateException {
+ Log.v(LOG_TAG, "hangup conn: " + mPeer.getUriString() + ": "
+ + ": on phone " + getPhone());
+ try {
+ mSipAudioCall.endCall();
+ setState(Call.State.DISCONNECTING);
+ setDisconnectCause(DisconnectCause.LOCAL);
+ } catch (SipException e) {
+ throw new CallStateException("hangup(): " + e);
+ }
+ }
+
+ @Override
+ public void separate() throws CallStateException {
+ // TODO: what's this for SIP?
+ /*
+ if (!disconnected) {
+ owner.separate(this);
+ } else {
+ throw new CallStateException ("disconnected");
+ }
+ */
+ }
+
+ }
+
+ private static Call.State getCallStateFrom(SipAudioCall sipAudioCall) {
+ if (sipAudioCall.isOnHold()) return Call.State.HOLDING;
+ SipSessionState sessionState = sipAudioCall.getState();
+ switch (sessionState) {
+ case READY_TO_CALL: return Call.State.IDLE;
+ case INCOMING_CALL:
+ case INCOMING_CALL_ANSWERING: return Call.State.INCOMING;
+ case OUTGOING_CALL: return Call.State.DIALING;
+ case OUTGOING_CALL_RING_BACK: return Call.State.ALERTING;
+ case OUTGOING_CALL_CANCELING: return Call.State.DISCONNECTING;
+ case IN_CALL: return Call.State.ACTIVE;
+ default:
+ Log.w(LOG_TAG, "illegal connection state: " + sessionState);
+ return Call.State.DISCONNECTED;
+ }
+ }
+
+ private abstract class SipAudioCallAdapter extends SipAudioCall.Adapter {
+ protected abstract void onCallEnded(Connection.DisconnectCause cause);
+ protected abstract void onError(String errorMessage);
+
+ @Override
+ public void onCallEnded(SipAudioCall call) {
+ onCallEnded(Connection.DisconnectCause.NORMAL);
+ }
+
+ @Override
+ public void onCallBusy(SipAudioCall call) {
+ onCallEnded(Connection.DisconnectCause.BUSY);
+ }
+
+ @Override
+ public void onError(SipAudioCall call, String errorMessage) {
+ onError(errorMessage);
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java b/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
new file mode 100755
index 0000000..721b8af
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
@@ -0,0 +1,569 @@
+/*
+ * Copyright (C) 2010 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.internal.telephony.sip;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.NetworkProperties;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.os.SystemProperties;
+import android.preference.PreferenceManager;
+import android.provider.Telephony;
+import android.telephony.CellLocation;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.text.TextUtils;
+import android.util.Log;
+
+import static com.android.internal.telephony.CommandsInterface.CF_ACTION_DISABLE;
+import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ENABLE;
+import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ERASURE;
+import static com.android.internal.telephony.CommandsInterface.CF_ACTION_REGISTRATION;
+import static com.android.internal.telephony.CommandsInterface.CF_REASON_ALL;
+import static com.android.internal.telephony.CommandsInterface.CF_REASON_ALL_CONDITIONAL;
+import static com.android.internal.telephony.CommandsInterface.CF_REASON_NO_REPLY;
+import static com.android.internal.telephony.CommandsInterface.CF_REASON_NOT_REACHABLE;
+import static com.android.internal.telephony.CommandsInterface.CF_REASON_BUSY;
+import static com.android.internal.telephony.CommandsInterface.CF_REASON_UNCONDITIONAL;
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;
+import static com.android.internal.telephony.TelephonyProperties.PROPERTY_BASEBAND_VERSION;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.DataConnection;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.IccFileHandler;
+import com.android.internal.telephony.IccPhoneBookInterfaceManager;
+import com.android.internal.telephony.IccSmsInterfaceManager;
+import com.android.internal.telephony.MmiCode;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.PhoneNotifier;
+import com.android.internal.telephony.PhoneProxy;
+import com.android.internal.telephony.PhoneSubInfo;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.UUSInfo;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+abstract class SipPhoneBase extends PhoneBase {
+ // NOTE that LOG_TAG here is "Sip", which means that log messages
+ // from this file will go into the radio log rather than the main
+ // log. (Use "adb logcat -b radio" to see them.)
+ static final String LOG_TAG = "SipPhone";
+ private static final boolean LOCAL_DEBUG = true;
+
+ //SipCallTracker mCT;
+ PhoneSubInfo mSubInfo;
+
+ Registrant mPostDialHandler;
+
+ final RegistrantList mRingbackRegistrants = new RegistrantList();
+
+ private State state = State.IDLE;
+
+ public SipPhoneBase(Context context, PhoneNotifier notifier) {
+ super(notifier, context, new SipCommandInterface(context), false);
+
+ // FIXME: what's this for SIP?
+ //Change the system property
+ //SystemProperties.set(TelephonyProperties.CURRENT_ACTIVE_PHONE,
+ // new Integer(Phone.PHONE_TYPE_GSM).toString());
+ }
+
+ public abstract Call getForegroundCall();
+
+ public abstract Call getBackgroundCall();
+
+ public abstract Call getRingingCall();
+
+ public Connection dial(String dialString, UUSInfo uusInfo)
+ throws CallStateException {
+ // ignore UUSInfo
+ return dial(dialString);
+ }
+
+ void migrateFrom(SipPhoneBase from) {
+ migrate(mRingbackRegistrants, from.mRingbackRegistrants);
+ migrate(mPreciseCallStateRegistrants, from.mPreciseCallStateRegistrants);
+ migrate(mNewRingingConnectionRegistrants, from.mNewRingingConnectionRegistrants);
+ migrate(mIncomingRingRegistrants, from.mIncomingRingRegistrants);
+ migrate(mDisconnectRegistrants, from.mDisconnectRegistrants);
+ migrate(mServiceStateRegistrants, from.mServiceStateRegistrants);
+ migrate(mMmiCompleteRegistrants, from.mMmiCompleteRegistrants);
+ migrate(mMmiRegistrants, from.mMmiRegistrants);
+ migrate(mUnknownConnectionRegistrants, from.mUnknownConnectionRegistrants);
+ migrate(mSuppServiceFailedRegistrants, from.mSuppServiceFailedRegistrants);
+ }
+
+ static void migrate(RegistrantList to, RegistrantList from) {
+ from.removeCleared();
+ for (int i = 0, n = from.size(); i < n; i++) {
+ to.add((Registrant) from.get(i));
+ }
+ }
+
+ @Override
+ public void registerForRingbackTone(Handler h, int what, Object obj) {
+ mRingbackRegistrants.addUnique(h, what, obj);
+ }
+
+ @Override
+ public void unregisterForRingbackTone(Handler h) {
+ mRingbackRegistrants.remove(h);
+ }
+
+ protected void startRingbackTone() {
+ AsyncResult result = new AsyncResult(null, new Boolean(true), null);
+ mRingbackRegistrants.notifyRegistrants(result);
+ }
+
+ protected void stopRingbackTone() {
+ AsyncResult result = new AsyncResult(null, new Boolean(false), null);
+ mRingbackRegistrants.notifyRegistrants(result);
+ }
+
+ public void dispose() {
+ mIsTheCurrentActivePhone = false;
+ mSubInfo.dispose();
+ }
+
+ public void removeReferences() {
+ mSubInfo = null;
+ }
+
+ public ServiceState getServiceState() {
+ // FIXME: we may need to provide this when data connectivity is lost
+ // or when server is down
+ ServiceState s = new ServiceState();
+ s.setState(ServiceState.STATE_IN_SERVICE);
+ return s;
+ }
+
+ public CellLocation getCellLocation() {
+ return null; //mSST.cellLoc;
+ }
+
+ public State getState() {
+ return state;
+ }
+
+ public String getPhoneName() {
+ return "SIP";
+ }
+
+ public int getPhoneType() {
+ // FIXME: add SIP phone type
+ return Phone.PHONE_TYPE_GSM;
+ }
+
+ public SignalStrength getSignalStrength() {
+ return new SignalStrength();
+ }
+
+ public boolean getMessageWaitingIndicator() {
+ return false;
+ }
+
+ public boolean getCallForwardingIndicator() {
+ return false;
+ }
+
+ public List<? extends MmiCode> getPendingMmiCodes() {
+ return new ArrayList<MmiCode>(0);
+ }
+
+ public DataState getDataConnectionState() {
+ return DataState.DISCONNECTED;
+ }
+
+ public DataState getDataConnectionState(String apnType) {
+ return DataState.DISCONNECTED;
+ }
+
+ public DataActivityState getDataActivityState() {
+ return DataActivityState.NONE;
+ }
+
+ /**
+ * Notify any interested party of a Phone state change {@link Phone.State}
+ */
+ void notifyPhoneStateChanged() {
+ mNotifier.notifyPhoneState(this);
+ }
+
+ /**
+ * Notify registrants of a change in the call state. This notifies changes in {@link Call.State}
+ * Use this when changes in the precise call state are needed, else use notifyPhoneStateChanged.
+ */
+ void notifyPreciseCallStateChanged() {
+ /* we'd love it if this was package-scoped*/
+ super.notifyPreciseCallStateChangedP();
+ }
+
+ void notifyNewRingingConnection(Connection c) {
+ /* we'd love it if this was package-scoped*/
+ super.notifyNewRingingConnectionP(c);
+ }
+
+ void notifyDisconnect(Connection cn) {
+ mDisconnectRegistrants.notifyResult(cn);
+ }
+
+ void notifyUnknownConnection() {
+ mUnknownConnectionRegistrants.notifyResult(this);
+ }
+
+ void notifySuppServiceFailed(SuppService code) {
+ mSuppServiceFailedRegistrants.notifyResult(code);
+ }
+
+ void notifyServiceStateChanged(ServiceState ss) {
+ super.notifyServiceStateChangedP(ss);
+ }
+
+ public void notifyCallForwardingIndicator() {
+ mNotifier.notifyCallForwardingChanged(this);
+ }
+
+ public boolean canDial() {
+ int serviceState = getServiceState().getState();
+ Log.v(LOG_TAG, "canDial(): serviceState = " + serviceState);
+ if (serviceState == ServiceState.STATE_POWER_OFF) return false;
+
+ String disableCall = SystemProperties.get(
+ TelephonyProperties.PROPERTY_DISABLE_CALL, "false");
+ Log.v(LOG_TAG, "canDial(): disableCall = " + disableCall);
+ if (disableCall.equals("true")) return false;
+
+ Log.v(LOG_TAG, "canDial(): ringingCall: " + getRingingCall().getState());
+ Log.v(LOG_TAG, "canDial(): foregndCall: " + getForegroundCall().getState());
+ Log.v(LOG_TAG, "canDial(): backgndCall: " + getBackgroundCall().getState());
+ return !getRingingCall().isRinging()
+ && (!getForegroundCall().getState().isAlive()
+ || !getBackgroundCall().getState().isAlive());
+ }
+
+ public boolean handleInCallMmiCommands(String dialString)
+ throws CallStateException {
+ return false;
+ }
+
+ boolean isInCall() {
+ Call.State foregroundCallState = getForegroundCall().getState();
+ Call.State backgroundCallState = getBackgroundCall().getState();
+ Call.State ringingCallState = getRingingCall().getState();
+
+ return (foregroundCallState.isAlive() || backgroundCallState.isAlive()
+ || ringingCallState.isAlive());
+ }
+
+ public boolean handlePinMmi(String dialString) {
+ return false;
+ }
+
+ public void sendUssdResponse(String ussdMessge) {
+ }
+
+ public void registerForSuppServiceNotification(
+ Handler h, int what, Object obj) {
+ }
+
+ public void unregisterForSuppServiceNotification(Handler h) {
+ }
+
+ public void setRadioPower(boolean power) {
+ }
+
+ public String getVoiceMailNumber() {
+ return null;
+ }
+
+ public String getVoiceMailAlphaTag() {
+ return null;
+ }
+
+ public String getDeviceId() {
+ return null;
+ }
+
+ public String getDeviceSvn() {
+ return null;
+ }
+
+ public String getEsn() {
+ Log.e(LOG_TAG, "[SipPhone] getEsn() is a CDMA method");
+ return "0";
+ }
+
+ public String getMeid() {
+ Log.e(LOG_TAG, "[SipPhone] getMeid() is a CDMA method");
+ return "0";
+ }
+
+ public String getSubscriberId() {
+ return null;
+ }
+
+ public String getIccSerialNumber() {
+ return null;
+ }
+
+ public String getLine1Number() {
+ return null;
+ }
+
+ public String getLine1AlphaTag() {
+ return null;
+ }
+
+ public void setLine1Number(String alphaTag, String number, Message onComplete) {
+ // FIXME: what to reply for SIP?
+ AsyncResult.forMessage(onComplete, null, null);
+ onComplete.sendToTarget();
+ }
+
+ public void setVoiceMailNumber(String alphaTag, String voiceMailNumber,
+ Message onComplete) {
+ // FIXME: what to reply for SIP?
+ AsyncResult.forMessage(onComplete, null, null);
+ onComplete.sendToTarget();
+ }
+
+ private boolean isValidCommandInterfaceCFReason(int commandInterfaceCFReason) {
+ switch (commandInterfaceCFReason) {
+ case CF_REASON_UNCONDITIONAL:
+ case CF_REASON_BUSY:
+ case CF_REASON_NO_REPLY:
+ case CF_REASON_NOT_REACHABLE:
+ case CF_REASON_ALL:
+ case CF_REASON_ALL_CONDITIONAL:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private boolean isValidCommandInterfaceCFAction (int commandInterfaceCFAction) {
+ switch (commandInterfaceCFAction) {
+ case CF_ACTION_DISABLE:
+ case CF_ACTION_ENABLE:
+ case CF_ACTION_REGISTRATION:
+ case CF_ACTION_ERASURE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ protected boolean isCfEnable(int action) {
+ return (action == CF_ACTION_ENABLE) || (action == CF_ACTION_REGISTRATION);
+ }
+
+ public void getCallForwardingOption(int commandInterfaceCFReason, Message onComplete) {
+ if (isValidCommandInterfaceCFReason(commandInterfaceCFReason)) {
+ // FIXME: what to reply?
+ AsyncResult.forMessage(onComplete, null, null);
+ onComplete.sendToTarget();
+ }
+ }
+
+ public void setCallForwardingOption(int commandInterfaceCFAction,
+ int commandInterfaceCFReason, String dialingNumber,
+ int timerSeconds, Message onComplete) {
+ if (isValidCommandInterfaceCFAction(commandInterfaceCFAction)
+ && isValidCommandInterfaceCFReason(commandInterfaceCFReason)) {
+ // FIXME: what to reply?
+ AsyncResult.forMessage(onComplete, null, null);
+ onComplete.sendToTarget();
+ }
+ }
+
+ public void getOutgoingCallerIdDisplay(Message onComplete) {
+ // FIXME: what to reply?
+ AsyncResult.forMessage(onComplete, null, null);
+ onComplete.sendToTarget();
+ }
+
+ public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
+ Message onComplete) {
+ // FIXME: what's this for SIP?
+ AsyncResult.forMessage(onComplete, null, null);
+ onComplete.sendToTarget();
+ }
+
+ public void getCallWaiting(Message onComplete) {
+ // FIXME: what to reply?
+ AsyncResult.forMessage(onComplete, null, null);
+ onComplete.sendToTarget();
+ }
+
+ public void setCallWaiting(boolean enable, Message onComplete) {
+ // FIXME: what to reply?
+ Log.e(LOG_TAG, "call waiting not supported");
+ }
+
+ public boolean getIccRecordsLoaded() {
+ return false;
+ }
+
+ public IccCard getIccCard() {
+ return null;
+ }
+
+ public void getAvailableNetworks(Message response) {
+ // FIXME: what to reply?
+ }
+
+ public void setNetworkSelectionModeAutomatic(Message response) {
+ // FIXME: what to reply?
+ }
+
+ public void selectNetworkManually(
+ com.android.internal.telephony.gsm.NetworkInfo network,
+ Message response) {
+ // FIXME: what to reply?
+ }
+
+ public void getNeighboringCids(Message response) {
+ // FIXME: what to reply?
+ }
+
+ public void setOnPostDialCharacter(Handler h, int what, Object obj) {
+ mPostDialHandler = new Registrant(h, what, obj);
+ }
+
+ public void getDataCallList(Message response) {
+ // FIXME: what to reply?
+ }
+
+ public List<DataConnection> getCurrentDataConnectionList () {
+ return null;
+ }
+
+ public void updateServiceLocation() {
+ }
+
+ public void enableLocationUpdates() {
+ }
+
+ public void disableLocationUpdates() {
+ }
+
+ public boolean getDataRoamingEnabled() {
+ return false;
+ }
+
+ public void setDataRoamingEnabled(boolean enable) {
+ }
+
+ public boolean enableDataConnectivity() {
+ return false;
+ }
+
+ public boolean disableDataConnectivity() {
+ return false;
+ }
+
+ public boolean isDataConnectivityPossible() {
+ return false;
+ }
+
+ boolean updateCurrentCarrierInProvider() {
+ return false;
+ }
+
+ public void saveClirSetting(int commandInterfaceCLIRMode) {
+ // FIXME: what's this for SIP?
+ }
+
+ /**
+ * Retrieves the PhoneSubInfo of the SipPhone
+ */
+ public PhoneSubInfo getPhoneSubInfo(){
+ return mSubInfo;
+ }
+
+ /** {@inheritDoc} */
+ public IccSmsInterfaceManager getIccSmsInterfaceManager(){
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager(){
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public IccFileHandler getIccFileHandler(){
+ return null;
+ }
+
+ public void activateCellBroadcastSms(int activate, Message response) {
+ Log.e(LOG_TAG, "Error! This functionality is not implemented for SIP.");
+ }
+
+ public void getCellBroadcastSmsConfig(Message response) {
+ Log.e(LOG_TAG, "Error! This functionality is not implemented for SIP.");
+ }
+
+ public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response){
+ Log.e(LOG_TAG, "Error! This functionality is not implemented for SIP.");
+ }
+
+ //@Override
+ public boolean needsOtaServiceProvisioning() {
+ // FIXME: what's this for SIP?
+ return false;
+ }
+
+ //@Override
+ public NetworkProperties getNetworkProperties(String apnType) {
+ // FIXME: what's this for SIP?
+ return null;
+ }
+
+ void updatePhoneState() {
+ State oldState = state;
+
+ if (getRingingCall().isRinging()) {
+ state = State.RINGING;
+ } else if (getForegroundCall().isIdle()
+ && getBackgroundCall().isIdle()) {
+ state = State.IDLE;
+ } else {
+ state = State.OFFHOOK;
+ }
+ Log.e(LOG_TAG, " ^^^^^^ new phone state: " + state);
+
+ if (state != oldState) {
+ notifyPhoneStateChanged();
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhoneFactory.java b/telephony/java/com/android/internal/telephony/sip/SipPhoneFactory.java
new file mode 100644
index 0000000..c9e9762
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhoneFactory.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010 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.internal.telephony.sip;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneNotifier;
+
+import android.content.Context;
+import android.net.sip.SipProfile;
+import android.util.Log;
+
+import java.text.ParseException;
+
+/**
+ * @hide
+ */
+public class SipPhoneFactory {
+ private static PhoneNotifier sPhoneNotifier = makeDefaultPhoneNotifier();
+ private static Context sContext;
+
+ public static void makeDefaultPhones(Context context) {
+ makeDefaultPhone(context);
+ }
+
+ public static void makeDefaultPhone(Context context) {
+ sContext = context;
+ SipPhoneProxy.getInstance().setPhone(
+ makePhone("sip:anonymous@localhost"));
+ }
+
+ public static Phone getDefaultPhone() {
+ return SipPhoneProxy.getInstance();
+ }
+
+ public static SipPhone makePhone(String sipProfileUri) {
+ try {
+ SipProfile profile = new SipProfile.Builder(sipProfileUri).build();
+ return new SipPhone(sContext, sPhoneNotifier, profile);
+ } catch (ParseException e) {
+ Log.v("SipPhoneProxy", "setPhone", e);
+ return null;
+ }
+ }
+
+ private static PhoneNotifier makeDefaultPhoneNotifier() {
+ try {
+ return new com.android.internal.telephony.SipPhoneNotifier();
+ } catch (Error e) {
+ Log.e("SipPhoneProxy", "makeDefaultPhoneNotifier", e);
+ throw e;
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhoneProxy.java b/telephony/java/com/android/internal/telephony/sip/SipPhoneProxy.java
new file mode 100644
index 0000000..8fa2963
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhoneProxy.java
@@ -0,0 +1,763 @@
+/*
+ * Copyright (C) 2010 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.internal.telephony.sip;
+
+import com.android.internal.telephony.*;
+import com.android.internal.telephony.gsm.NetworkInfo;
+import com.android.internal.telephony.test.SimulatedRadioControl;
+
+import android.content.Context;
+import android.net.NetworkProperties;
+import android.os.Handler;
+import android.os.Message;
+import android.telephony.CellLocation;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Temporary. Will be removed after integrating with CallManager.
+ * (TODO)
+ * @hide
+ */
+public class SipPhoneProxy implements Phone {
+ private static final String LOG_TAG = "PHONE";
+
+ private static SipPhoneProxy sPhoneProxy = new SipPhoneProxy();
+
+ public static SipPhoneProxy getInstance() {
+ return sPhoneProxy;
+ }
+
+ private SipPhone mActivePhone;
+ private CallProxy mRingingCall = new CallProxy();
+ private CallProxy mForegroundCall = new CallProxy();
+ private CallProxy mBackgroundCall = new CallProxy();
+
+ private SipPhoneProxy() {
+ }
+
+ public void onNewCall(Object call) {
+ if (mActivePhone.canTake(call)) {
+ Log.v("SipPhoneProxy", "onNewCall(): call taken: " + call);
+ } else {
+ Log.v("SipPhoneProxy", "onNewCall(): call dropped: " + call);
+ }
+ }
+
+ public synchronized void setPhone(SipPhone phone) {
+ if (phone == null) return;
+ if (mActivePhone != null) phone.migrateFrom(mActivePhone);
+ mActivePhone = phone;
+ mForegroundCall.setTarget(phone.getForegroundCall());
+ mBackgroundCall.setTarget(phone.getBackgroundCall());
+ mRingingCall.setTarget(phone.getRingingCall());
+ }
+
+ public synchronized Call getForegroundCall() {
+ return mForegroundCall;
+ }
+
+ public synchronized Call getBackgroundCall() {
+ return mBackgroundCall;
+ }
+
+ public synchronized Call getRingingCall() {
+ return mRingingCall;
+ }
+
+
+ public ServiceState getServiceState() {
+ return mActivePhone.getServiceState();
+ }
+
+ public CellLocation getCellLocation() {
+ return mActivePhone.getCellLocation();
+ }
+
+ public DataState getDataConnectionState() {
+ return mActivePhone.getDataConnectionState();
+ }
+
+ public DataState getDataConnectionState(String apnType) {
+ return mActivePhone.getDataConnectionState(apnType);
+ }
+
+ public DataActivityState getDataActivityState() {
+ return mActivePhone.getDataActivityState();
+ }
+
+ public Context getContext() {
+ return mActivePhone.getContext();
+ }
+
+ public void disableDnsCheck(boolean b) {
+ mActivePhone.disableDnsCheck(b);
+ }
+
+ public boolean isDnsCheckDisabled() {
+ return mActivePhone.isDnsCheckDisabled();
+ }
+
+ public State getState() {
+ return mActivePhone.getState();
+ }
+
+ public String getPhoneName() {
+ return mActivePhone.getPhoneName();
+ }
+
+ public int getPhoneType() {
+ return mActivePhone.getPhoneType();
+ }
+
+ public String[] getActiveApnTypes() {
+ return mActivePhone.getActiveApnTypes();
+ }
+
+ public String getActiveApn() {
+ return mActivePhone.getActiveApn();
+ }
+
+ public SignalStrength getSignalStrength() {
+ return mActivePhone.getSignalStrength();
+ }
+
+ public void registerForUnknownConnection(Handler h, int what, Object obj) {
+ mActivePhone.registerForUnknownConnection(h, what, obj);
+ }
+
+ public void unregisterForUnknownConnection(Handler h) {
+ mActivePhone.unregisterForUnknownConnection(h);
+ }
+
+ public void registerForPreciseCallStateChanged(Handler h, int what, Object obj) {
+ mActivePhone.registerForPreciseCallStateChanged(h, what, obj);
+ }
+
+ public void unregisterForPreciseCallStateChanged(Handler h) {
+ mActivePhone.unregisterForPreciseCallStateChanged(h);
+ }
+
+ public void registerForNewRingingConnection(Handler h, int what, Object obj) {
+ mActivePhone.registerForNewRingingConnection(h, what, obj);
+ }
+
+ public void unregisterForNewRingingConnection(Handler h) {
+ mActivePhone.unregisterForNewRingingConnection(h);
+ }
+
+ public void registerForIncomingRing(Handler h, int what, Object obj) {
+ mActivePhone.registerForIncomingRing(h, what, obj);
+ }
+
+ public void unregisterForIncomingRing(Handler h) {
+ mActivePhone.unregisterForIncomingRing(h);
+ }
+
+ public void registerForDisconnect(Handler h, int what, Object obj) {
+ mActivePhone.registerForDisconnect(h, what, obj);
+ }
+
+ public void unregisterForDisconnect(Handler h) {
+ mActivePhone.unregisterForDisconnect(h);
+ }
+
+ public void registerForMmiInitiate(Handler h, int what, Object obj) {
+ mActivePhone.registerForMmiInitiate(h, what, obj);
+ }
+
+ public void unregisterForMmiInitiate(Handler h) {
+ mActivePhone.unregisterForMmiInitiate(h);
+ }
+
+ public void registerForMmiComplete(Handler h, int what, Object obj) {
+ mActivePhone.registerForMmiComplete(h, what, obj);
+ }
+
+ public void unregisterForMmiComplete(Handler h) {
+ mActivePhone.unregisterForMmiComplete(h);
+ }
+
+ public List<? extends MmiCode> getPendingMmiCodes() {
+ return mActivePhone.getPendingMmiCodes();
+ }
+
+ public void sendUssdResponse(String ussdMessge) {
+ mActivePhone.sendUssdResponse(ussdMessge);
+ }
+
+ public void registerForServiceStateChanged(Handler h, int what, Object obj) {
+ mActivePhone.registerForServiceStateChanged(h, what, obj);
+ }
+
+ public void unregisterForServiceStateChanged(Handler h) {
+ mActivePhone.unregisterForServiceStateChanged(h);
+ }
+
+ public void registerForSuppServiceNotification(Handler h, int what, Object obj) {
+ mActivePhone.registerForSuppServiceNotification(h, what, obj);
+ }
+
+ public void unregisterForSuppServiceNotification(Handler h) {
+ mActivePhone.unregisterForSuppServiceNotification(h);
+ }
+
+ public void registerForSuppServiceFailed(Handler h, int what, Object obj) {
+ mActivePhone.registerForSuppServiceFailed(h, what, obj);
+ }
+
+ public void unregisterForSuppServiceFailed(Handler h) {
+ mActivePhone.unregisterForSuppServiceFailed(h);
+ }
+
+ public void registerForInCallVoicePrivacyOn(Handler h, int what, Object obj){
+ mActivePhone.registerForInCallVoicePrivacyOn(h,what,obj);
+ }
+
+ public void unregisterForInCallVoicePrivacyOn(Handler h){
+ mActivePhone.unregisterForInCallVoicePrivacyOn(h);
+ }
+
+ public void registerForInCallVoicePrivacyOff(Handler h, int what, Object obj){
+ mActivePhone.registerForInCallVoicePrivacyOff(h,what,obj);
+ }
+
+ public void unregisterForInCallVoicePrivacyOff(Handler h){
+ mActivePhone.unregisterForInCallVoicePrivacyOff(h);
+ }
+
+ public void registerForCdmaOtaStatusChange(Handler h, int what, Object obj) {
+ mActivePhone.registerForCdmaOtaStatusChange(h,what,obj);
+ }
+
+ public void unregisterForCdmaOtaStatusChange(Handler h) {
+ mActivePhone.unregisterForCdmaOtaStatusChange(h);
+ }
+
+ public void registerForSubscriptionInfoReady(Handler h, int what, Object obj) {
+ mActivePhone.registerForSubscriptionInfoReady(h, what, obj);
+ }
+
+ public void unregisterForSubscriptionInfoReady(Handler h) {
+ mActivePhone.unregisterForSubscriptionInfoReady(h);
+ }
+
+ public void registerForEcmTimerReset(Handler h, int what, Object obj) {
+ mActivePhone.registerForEcmTimerReset(h,what,obj);
+ }
+
+ public void unregisterForEcmTimerReset(Handler h) {
+ mActivePhone.unregisterForEcmTimerReset(h);
+ }
+
+ public void registerForRingbackTone(Handler h, int what, Object obj) {
+ mActivePhone.registerForRingbackTone(h,what,obj);
+ }
+
+ public void unregisterForRingbackTone(Handler h) {
+ mActivePhone.unregisterForRingbackTone(h);
+ }
+
+ public void registerForResendIncallMute(Handler h, int what, Object obj) {
+ mActivePhone.registerForResendIncallMute(h,what,obj);
+ }
+
+ public void unregisterForResendIncallMute(Handler h) {
+ mActivePhone.unregisterForResendIncallMute(h);
+ }
+
+ public boolean getIccRecordsLoaded() {
+ return mActivePhone.getIccRecordsLoaded();
+ }
+
+ public IccCard getIccCard() {
+ return mActivePhone.getIccCard();
+ }
+
+ public void acceptCall() throws CallStateException {
+ mActivePhone.acceptCall();
+ }
+
+ public void rejectCall() throws CallStateException {
+ mActivePhone.rejectCall();
+ }
+
+ public void switchHoldingAndActive() throws CallStateException {
+ mActivePhone.switchHoldingAndActive();
+ }
+
+ public boolean canConference() {
+ return mActivePhone.canConference();
+ }
+
+ public void conference() throws CallStateException {
+ mActivePhone.conference();
+ }
+
+ public void enableEnhancedVoicePrivacy(boolean enable, Message onComplete) {
+ mActivePhone.enableEnhancedVoicePrivacy(enable, onComplete);
+ }
+
+ public void getEnhancedVoicePrivacy(Message onComplete) {
+ mActivePhone.getEnhancedVoicePrivacy(onComplete);
+ }
+
+ public boolean canTransfer() {
+ return mActivePhone.canTransfer();
+ }
+
+ public void explicitCallTransfer() throws CallStateException {
+ mActivePhone.explicitCallTransfer();
+ }
+
+ public void clearDisconnected() {
+ mActivePhone.clearDisconnected();
+ }
+
+ public Connection dial(String dialString) throws CallStateException {
+ return mActivePhone.dial(dialString);
+ }
+
+ public boolean handlePinMmi(String dialString) {
+ return mActivePhone.handlePinMmi(dialString);
+ }
+
+ public boolean handleInCallMmiCommands(String command) throws CallStateException {
+ return mActivePhone.handleInCallMmiCommands(command);
+ }
+
+ public void sendDtmf(char c) {
+ mActivePhone.sendDtmf(c);
+ }
+
+ public void startDtmf(char c) {
+ mActivePhone.startDtmf(c);
+ }
+
+ public void stopDtmf() {
+ mActivePhone.stopDtmf();
+ }
+
+ public void setRadioPower(boolean power) {
+ mActivePhone.setRadioPower(power);
+ }
+
+ public boolean getMessageWaitingIndicator() {
+ return mActivePhone.getMessageWaitingIndicator();
+ }
+
+ public boolean getCallForwardingIndicator() {
+ return mActivePhone.getCallForwardingIndicator();
+ }
+
+ public String getLine1Number() {
+ return mActivePhone.getLine1Number();
+ }
+
+ public String getCdmaMin() {
+ return mActivePhone.getCdmaMin();
+ }
+
+ public boolean isMinInfoReady() {
+ return mActivePhone.isMinInfoReady();
+ }
+
+ public String getCdmaPrlVersion() {
+ return mActivePhone.getCdmaPrlVersion();
+ }
+
+ public String getLine1AlphaTag() {
+ return mActivePhone.getLine1AlphaTag();
+ }
+
+ public void setLine1Number(String alphaTag, String number, Message onComplete) {
+ mActivePhone.setLine1Number(alphaTag, number, onComplete);
+ }
+
+ public String getVoiceMailNumber() {
+ return mActivePhone.getVoiceMailNumber();
+ }
+
+ /** @hide */
+ public int getVoiceMessageCount(){
+ return mActivePhone.getVoiceMessageCount();
+ }
+
+ public String getVoiceMailAlphaTag() {
+ return mActivePhone.getVoiceMailAlphaTag();
+ }
+
+ public void setVoiceMailNumber(String alphaTag,String voiceMailNumber,
+ Message onComplete) {
+ mActivePhone.setVoiceMailNumber(alphaTag, voiceMailNumber, onComplete);
+ }
+
+ public void getCallForwardingOption(int commandInterfaceCFReason,
+ Message onComplete) {
+ mActivePhone.getCallForwardingOption(commandInterfaceCFReason,
+ onComplete);
+ }
+
+ public void setCallForwardingOption(int commandInterfaceCFReason,
+ int commandInterfaceCFAction, String dialingNumber,
+ int timerSeconds, Message onComplete) {
+ mActivePhone.setCallForwardingOption(commandInterfaceCFReason,
+ commandInterfaceCFAction, dialingNumber, timerSeconds, onComplete);
+ }
+
+ public void getOutgoingCallerIdDisplay(Message onComplete) {
+ mActivePhone.getOutgoingCallerIdDisplay(onComplete);
+ }
+
+ public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
+ Message onComplete) {
+ mActivePhone.setOutgoingCallerIdDisplay(commandInterfaceCLIRMode,
+ onComplete);
+ }
+
+ public void getCallWaiting(Message onComplete) {
+ mActivePhone.getCallWaiting(onComplete);
+ }
+
+ public void setCallWaiting(boolean enable, Message onComplete) {
+ mActivePhone.setCallWaiting(enable, onComplete);
+ }
+
+ public void getAvailableNetworks(Message response) {
+ mActivePhone.getAvailableNetworks(response);
+ }
+
+ public void setNetworkSelectionModeAutomatic(Message response) {
+ mActivePhone.setNetworkSelectionModeAutomatic(response);
+ }
+
+ public void selectNetworkManually(NetworkInfo network, Message response) {
+ mActivePhone.selectNetworkManually(network, response);
+ }
+
+ public void setPreferredNetworkType(int networkType, Message response) {
+ mActivePhone.setPreferredNetworkType(networkType, response);
+ }
+
+ public void getPreferredNetworkType(Message response) {
+ mActivePhone.getPreferredNetworkType(response);
+ }
+
+ public void getNeighboringCids(Message response) {
+ mActivePhone.getNeighboringCids(response);
+ }
+
+ public void setOnPostDialCharacter(Handler h, int what, Object obj) {
+ mActivePhone.setOnPostDialCharacter(h, what, obj);
+ }
+
+ public void setMute(boolean muted) {
+ mActivePhone.setMute(muted);
+ }
+
+ public boolean getMute() {
+ return mActivePhone.getMute();
+ }
+
+ public void invokeOemRilRequestRaw(byte[] data, Message response) {
+ mActivePhone.invokeOemRilRequestRaw(data, response);
+ }
+
+ public void invokeOemRilRequestStrings(String[] strings, Message response) {
+ mActivePhone.invokeOemRilRequestStrings(strings, response);
+ }
+
+ public void getDataCallList(Message response) {
+ mActivePhone.getDataCallList(response);
+ }
+
+ public List<DataConnection> getCurrentDataConnectionList() {
+ return mActivePhone.getCurrentDataConnectionList();
+ }
+
+ public void updateServiceLocation() {
+ mActivePhone.updateServiceLocation();
+ }
+
+ public void enableLocationUpdates() {
+ mActivePhone.enableLocationUpdates();
+ }
+
+ public void disableLocationUpdates() {
+ mActivePhone.disableLocationUpdates();
+ }
+
+ public void setUnitTestMode(boolean f) {
+ mActivePhone.setUnitTestMode(f);
+ }
+
+ public boolean getUnitTestMode() {
+ return mActivePhone.getUnitTestMode();
+ }
+
+ public void setBandMode(int bandMode, Message response) {
+ mActivePhone.setBandMode(bandMode, response);
+ }
+
+ public void queryAvailableBandMode(Message response) {
+ mActivePhone.queryAvailableBandMode(response);
+ }
+
+ public boolean getDataRoamingEnabled() {
+ return mActivePhone.getDataRoamingEnabled();
+ }
+
+ public void setDataRoamingEnabled(boolean enable) {
+ mActivePhone.setDataRoamingEnabled(enable);
+ }
+
+ public void queryCdmaRoamingPreference(Message response) {
+ mActivePhone.queryCdmaRoamingPreference(response);
+ }
+
+ public void setCdmaRoamingPreference(int cdmaRoamingType, Message response) {
+ mActivePhone.setCdmaRoamingPreference(cdmaRoamingType, response);
+ }
+
+ public void setCdmaSubscription(int cdmaSubscriptionType, Message response) {
+ mActivePhone.setCdmaSubscription(cdmaSubscriptionType, response);
+ }
+
+ public SimulatedRadioControl getSimulatedRadioControl() {
+ return mActivePhone.getSimulatedRadioControl();
+ }
+
+ public boolean enableDataConnectivity() {
+ return mActivePhone.enableDataConnectivity();
+ }
+
+ public boolean disableDataConnectivity() {
+ return mActivePhone.disableDataConnectivity();
+ }
+
+ public int enableApnType(String type) {
+ return mActivePhone.enableApnType(type);
+ }
+
+ public int disableApnType(String type) {
+ return mActivePhone.disableApnType(type);
+ }
+
+ public boolean isDataConnectivityEnabled() {
+ return mActivePhone.isDataConnectivityEnabled();
+ }
+
+ public boolean isDataConnectivityPossible() {
+ return mActivePhone.isDataConnectivityPossible();
+ }
+
+ public String getInterfaceName(String apnType) {
+ return mActivePhone.getInterfaceName(apnType);
+ }
+
+ public String getIpAddress(String apnType) {
+ return mActivePhone.getIpAddress(apnType);
+ }
+
+ public String getGateway(String apnType) {
+ return mActivePhone.getGateway(apnType);
+ }
+
+ public String[] getDnsServers(String apnType) {
+ return mActivePhone.getDnsServers(apnType);
+ }
+
+ public String getDeviceId() {
+ return mActivePhone.getDeviceId();
+ }
+
+ public String getDeviceSvn() {
+ return mActivePhone.getDeviceSvn();
+ }
+
+ public String getSubscriberId() {
+ return mActivePhone.getSubscriberId();
+ }
+
+ public String getIccSerialNumber() {
+ return mActivePhone.getIccSerialNumber();
+ }
+
+ public String getEsn() {
+ return mActivePhone.getEsn();
+ }
+
+ public String getMeid() {
+ return mActivePhone.getMeid();
+ }
+
+ public PhoneSubInfo getPhoneSubInfo(){
+ return mActivePhone.getPhoneSubInfo();
+ }
+
+ public IccSmsInterfaceManager getIccSmsInterfaceManager(){
+ return mActivePhone.getIccSmsInterfaceManager();
+ }
+
+ public IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager(){
+ return mActivePhone.getIccPhoneBookInterfaceManager();
+ }
+
+ public void setTTYMode(int ttyMode, Message onComplete) {
+ mActivePhone.setTTYMode(ttyMode, onComplete);
+ }
+
+ public void queryTTYMode(Message onComplete) {
+ mActivePhone.queryTTYMode(onComplete);
+ }
+
+ public void activateCellBroadcastSms(int activate, Message response) {
+ mActivePhone.activateCellBroadcastSms(activate, response);
+ }
+
+ public void getCellBroadcastSmsConfig(Message response) {
+ mActivePhone.getCellBroadcastSmsConfig(response);
+ }
+
+ public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response) {
+ mActivePhone.setCellBroadcastSmsConfig(configValuesArray, response);
+ }
+
+ public void notifyDataActivity() {
+ mActivePhone.notifyDataActivity();
+ }
+
+ public void getSmscAddress(Message result) {
+ mActivePhone.getSmscAddress(result);
+ }
+
+ public void setSmscAddress(String address, Message result) {
+ mActivePhone.setSmscAddress(address, result);
+ }
+
+ public int getCdmaEriIconIndex() {
+ return mActivePhone.getCdmaEriIconIndex();
+ }
+
+ public String getCdmaEriText() {
+ return mActivePhone.getCdmaEriText();
+ }
+
+ public int getCdmaEriIconMode() {
+ return mActivePhone.getCdmaEriIconMode();
+ }
+
+ public void sendBurstDtmf(String dtmfString, int on, int off, Message onComplete){
+ mActivePhone.sendBurstDtmf(dtmfString, on, off, onComplete);
+ }
+
+ public void exitEmergencyCallbackMode(){
+ mActivePhone.exitEmergencyCallbackMode();
+ }
+
+ public boolean isOtaSpNumber(String dialStr){
+ return mActivePhone.isOtaSpNumber(dialStr);
+ }
+
+ public void registerForCallWaiting(Handler h, int what, Object obj){
+ mActivePhone.registerForCallWaiting(h,what,obj);
+ }
+
+ public void unregisterForCallWaiting(Handler h){
+ mActivePhone.unregisterForCallWaiting(h);
+ }
+
+ public void registerForSignalInfo(Handler h, int what, Object obj) {
+ mActivePhone.registerForSignalInfo(h,what,obj);
+ }
+
+ public void unregisterForSignalInfo(Handler h) {
+ mActivePhone.unregisterForSignalInfo(h);
+ }
+
+ public void registerForDisplayInfo(Handler h, int what, Object obj) {
+ mActivePhone.registerForDisplayInfo(h,what,obj);
+ }
+
+ public void unregisterForDisplayInfo(Handler h) {
+ mActivePhone.unregisterForDisplayInfo(h);
+ }
+
+ public void registerForNumberInfo(Handler h, int what, Object obj) {
+ mActivePhone.registerForNumberInfo(h, what, obj);
+ }
+
+ public void unregisterForNumberInfo(Handler h) {
+ mActivePhone.unregisterForNumberInfo(h);
+ }
+
+ public void registerForRedirectedNumberInfo(Handler h, int what, Object obj) {
+ mActivePhone.registerForRedirectedNumberInfo(h, what, obj);
+ }
+
+ public void unregisterForRedirectedNumberInfo(Handler h) {
+ mActivePhone.unregisterForRedirectedNumberInfo(h);
+ }
+
+ public void registerForLineControlInfo(Handler h, int what, Object obj) {
+ mActivePhone.registerForLineControlInfo( h, what, obj);
+ }
+
+ public void unregisterForLineControlInfo(Handler h) {
+ mActivePhone.unregisterForLineControlInfo(h);
+ }
+
+ public void registerFoT53ClirlInfo(Handler h, int what, Object obj) {
+ mActivePhone.registerFoT53ClirlInfo(h, what, obj);
+ }
+
+ public void unregisterForT53ClirInfo(Handler h) {
+ mActivePhone.unregisterForT53ClirInfo(h);
+ }
+
+ public void registerForT53AudioControlInfo(Handler h, int what, Object obj) {
+ mActivePhone.registerForT53AudioControlInfo( h, what, obj);
+ }
+
+ public void unregisterForT53AudioControlInfo(Handler h) {
+ mActivePhone.unregisterForT53AudioControlInfo(h);
+ }
+
+ public void setOnEcbModeExitResponse(Handler h, int what, Object obj){
+ mActivePhone.setOnEcbModeExitResponse(h,what,obj);
+ }
+
+ public void unsetOnEcbModeExitResponse(Handler h){
+ mActivePhone.unsetOnEcbModeExitResponse(h);
+ }
+
+ public Connection dial(String dialString, UUSInfo uusInfo)
+ throws CallStateException {
+ return mActivePhone.dial(dialString, uusInfo);
+ }
+
+ public boolean needsOtaServiceProvisioning() {
+ return mActivePhone.needsOtaServiceProvisioning();
+ }
+
+ public NetworkProperties getNetworkProperties(String apnType) {
+ return mActivePhone.getNetworkProperties(apnType);
+ }
+}
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
index f4773b5..8c7254c 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
@@ -754,7 +754,7 @@
private static class NewWindowWebView extends WebView {
public NewWindowWebView(Context context, Map<String, Object> jsIfaces) {
- super(context, null, 0, jsIfaces);
+ super(context, null, 0, jsIfaces, false);
}
}
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java
index 56822f3..1b73f97 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java
@@ -21,9 +21,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
/**
* A class that collects information about tests that ran and can create HTML
@@ -146,7 +144,10 @@
" color: #fff;}" +
"span.noLtc {" +
" background-color: #944000;" +
- " color: #fff;" +
+ " color: #fff;}" +
+ "span.noEventSender {" +
+ " background-color: #815600;" +
+ " color: #fff;}" +
"</style>";
private static final String SCRIPT =
@@ -278,12 +279,17 @@
/** Detect missing LTC function */
String additionalTextOutputString = result.getAdditionalTextOutputString();
- if (additionalTextOutputString != null &&
- additionalTextOutputString.contains("com.android.dumprendertree") &&
- additionalTextOutputString.contains("LayoutTestController") &&
- additionalTextOutputString.contains("has no method")) {
+ if (additionalTextOutputString != null &&
+ additionalTextOutputString.contains("com.android.dumprendertree") &&
+ additionalTextOutputString.contains("has no method")) {
+ if (additionalTextOutputString.contains("LayoutTestController")) {
html.append(" <span class=\"listItem noLtc\">LTC function missing</span>");
}
+ if (additionalTextOutputString.contains("EventSender")) {
+ html.append(" <span class=\"listItem noEventSender\">");
+ html.append("ES function missing</span>");
+ }
+ }
html.append("</h3>");
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index ef18787..73994f7 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -152,6 +152,24 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
-
+
+ <activity
+ android:name="PathsActivity"
+ android:label="_Paths">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name="Transform3dActivity"
+ android:label="_3d">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
</application>
</manifest>
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/PathsActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/PathsActivity.java
new file mode 100644
index 0000000..39d9942
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/PathsActivity.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2010 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.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class PathsActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final PathsView view = new PathsView(this);
+ setContentView(view);
+ }
+
+ static class PathsView extends View {
+ private final Bitmap mBitmap1;
+ private final Paint mSmallPaint;
+ private final Paint mMediumPaint;
+ private final Paint mLargePaint;
+ private final BitmapShader mShader;
+ private final Path mPath;
+ private final RectF mPathBounds;
+ private final Paint mBoundsPaint;
+ private final Bitmap mBitmap;
+ private final float mOffset;
+ private final Paint mLinePaint;
+
+ PathsView(Context c) {
+ super(c);
+
+ mBitmap1 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
+
+ mSmallPaint = new Paint();
+ mSmallPaint.setAntiAlias(true);
+ mSmallPaint.setColor(0xffff0000);
+ mSmallPaint.setStrokeWidth(1.0f);
+ mSmallPaint.setStyle(Paint.Style.STROKE);
+
+ mLinePaint = new Paint();
+ mLinePaint.setAntiAlias(true);
+ mLinePaint.setColor(0xffff00ff);
+ mLinePaint.setStrokeWidth(1.0f);
+ mLinePaint.setStyle(Paint.Style.STROKE);
+
+ mMediumPaint = new Paint();
+ mMediumPaint.setAntiAlias(true);
+ mMediumPaint.setColor(0xff0000ff);
+ mMediumPaint.setStrokeWidth(10.0f);
+ mMediumPaint.setStyle(Paint.Style.STROKE);
+
+ mLargePaint = new Paint();
+ mLargePaint.setAntiAlias(true);
+ mLargePaint.setColor(0xff00ff00);
+ mLargePaint.setStrokeWidth(15.0f);
+ mLargePaint.setStyle(Paint.Style.FILL);
+
+ mShader = new BitmapShader(mBitmap1, BitmapShader.TileMode.MIRROR,
+ BitmapShader.TileMode.MIRROR);
+
+ mPath = new Path();
+ mPath.moveTo(0.0f, 0.0f);
+ mPath.cubicTo(0.0f, 0.0f, 100.0f, 150.0f, 100.0f, 200.0f);
+ mPath.cubicTo(100.0f, 200.0f, 50.0f, 300.0f, -80.0f, 200.0f);
+ mPath.cubicTo(-80.0f, 200.0f, 100.0f, 200.0f, 200.0f, 0.0f);
+
+ mPathBounds = new RectF();
+ mPath.computeBounds(mPathBounds, true);
+
+ mBoundsPaint = new Paint();
+ mBoundsPaint.setColor(0x4000ff00);
+
+ mOffset = mMediumPaint.getStrokeWidth();
+ final int width = (int) (mPathBounds.width() + mOffset * 3.0f + 0.5f);
+ final int height = (int) (mPathBounds.height() + mOffset * 3.0f + 0.5f);
+ mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8);
+ Canvas canvas = new Canvas(mBitmap);
+ canvas.translate(-mPathBounds.left + mOffset * 1.5f, -mPathBounds.top + mOffset * 1.5f);
+ canvas.drawPath(mPath, mMediumPaint);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ canvas.drawARGB(255, 255, 255, 255);
+
+ canvas.save();
+ canvas.translate(200.0f, 60.0f);
+ canvas.drawPath(mPath, mSmallPaint);
+
+ canvas.translate(350.0f, 0.0f);
+ canvas.drawPath(mPath, mMediumPaint);
+
+ mLargePaint.setShader(mShader);
+ canvas.translate(350.0f, 0.0f);
+ canvas.drawPath(mPath, mLargePaint);
+ mLargePaint.setShader(null);
+ canvas.restore();
+
+ canvas.save();
+ canvas.translate(200.0f, 360.0f);
+ canvas.drawPath(mPath, mSmallPaint);
+ canvas.drawRect(mPathBounds, mBoundsPaint);
+
+ canvas.translate(350.0f, 0.0f);
+ canvas.drawBitmap(mBitmap, mPathBounds.left - mOffset * 1.5f,
+ mPathBounds.top - mOffset * 1.5f, null);
+ canvas.drawRect(mPathBounds, mBoundsPaint);
+ canvas.drawLine(0.0f, -360.0f, 0.0f, 500.0f, mLinePaint);
+
+ mLargePaint.setShader(mShader);
+ canvas.translate(350.0f, 0.0f);
+ canvas.drawPath(mPath, mLargePaint);
+ canvas.drawRect(mPathBounds, mBoundsPaint);
+ mLargePaint.setShader(null);
+ canvas.restore();
+ }
+ }
+}
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/Transform3dActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/Transform3dActivity.java
new file mode 100644
index 0000000..6134cde
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/Transform3dActivity.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2010 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.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Camera;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class Transform3dActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final Transform3dView view = new Transform3dView(this);
+ setContentView(view);
+ }
+
+ static class Transform3dView extends View {
+ private final Bitmap mBitmap1;
+ private Camera mCamera;
+ private Matrix mMatrix;
+
+ Transform3dView(Context c) {
+ super(c);
+
+ mBitmap1 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
+ mCamera = new Camera();
+ mMatrix = new Matrix();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ canvas.drawARGB(255, 255, 255, 255);
+
+ final float centerX = getWidth() / 2.0f - mBitmap1.getWidth() / 2.0f;
+ final float centerY = getHeight() / 2.0f - mBitmap1.getHeight() / 2.0f;
+ final Camera camera = mCamera;
+
+ final Matrix matrix = mMatrix;
+
+ rotate(centerX, centerY, camera, matrix, 32.0f);
+ drawBitmap(canvas, centerX, centerY, 0.0f, matrix);
+
+ rotate(centerX, centerY, camera, matrix, 12.0f);
+ drawBitmap(canvas, centerX, centerY, -mBitmap1.getWidth(), matrix);
+
+ rotate(centerX, centerY, camera, matrix, 52.0f);
+ drawBitmap(canvas, centerX, centerY, mBitmap1.getWidth(), matrix);
+
+ rotate(centerX, centerY, camera, matrix, 122.0f);
+ drawBitmap(canvas, centerX, centerY, mBitmap1.getWidth() * 2.0f, matrix);
+
+ }
+
+ private void drawBitmap(Canvas canvas, float centerX, float centerY, float offset,
+ Matrix matrix) {
+ canvas.save();
+ canvas.translate(offset, 0.0f);
+ canvas.concat(matrix);
+ canvas.drawBitmap(mBitmap1, centerX, centerY, null);
+ canvas.restore();
+ }
+
+ private void rotate(float centerX, float centerY, Camera camera,
+ Matrix matrix, float angle) {
+ camera.save();
+ camera.rotateY(angle);
+ camera.getMatrix(matrix);
+ camera.restore();
+
+ matrix.preTranslate(-centerX, -centerY);
+ matrix.postTranslate(centerX, centerY);
+ }
+ }
+}
diff --git a/voip/jni/rtp/Android.mk b/voip/jni/rtp/Android.mk
new file mode 100644
index 0000000..a364355
--- /dev/null
+++ b/voip/jni/rtp/Android.mk
@@ -0,0 +1,44 @@
+#
+# Copyright (C) 2010 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := librtp_jni
+
+LOCAL_SRC_FILES := \
+ AudioCodec.cpp \
+ AudioGroup.cpp \
+ RtpStream.cpp \
+ util.cpp \
+ rtp_jni.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libnativehelper \
+ libcutils \
+ libutils \
+ libmedia
+
+LOCAL_STATIC_LIBRARIES :=
+
+LOCAL_C_INCLUDES += \
+ $(JNI_H_INCLUDE)
+
+LOCAL_CFLAGS += -fvisibility=hidden
+
+LOCAL_PRELINK_MODULE := false
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/voip/jni/rtp/AudioCodec.cpp b/voip/jni/rtp/AudioCodec.cpp
new file mode 100644
index 0000000..ddd07fc
--- /dev/null
+++ b/voip/jni/rtp/AudioCodec.cpp
@@ -0,0 +1,161 @@
+/*
+ * Copyrightm (C) 2010 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.
+ */
+
+#include <string.h>
+
+#include "AudioCodec.h"
+
+namespace {
+
+int8_t gExponents[128] = {
+ 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+};
+
+//------------------------------------------------------------------------------
+
+class UlawCodec : public AudioCodec
+{
+public:
+ bool set(int sampleRate, int sampleCount) {
+ mSampleCount = sampleCount;
+ return sampleCount > 0;
+ }
+ int encode(void *payload, int16_t *samples);
+ int decode(int16_t *samples, void *payload, int length);
+private:
+ int mSampleCount;
+};
+
+int UlawCodec::encode(void *payload, int16_t *samples)
+{
+ int8_t *ulaws = (int8_t *)payload;
+ for (int i = 0; i < mSampleCount; ++i) {
+ int sample = samples[i];
+ int sign = (sample >> 8) & 0x80;
+ if (sample < 0) {
+ sample = -sample;
+ }
+ sample += 132;
+ if (sample > 32767) {
+ sample = 32767;
+ }
+ int exponent = gExponents[sample >> 8];
+ int mantissa = (sample >> (exponent + 3)) & 0x0F;
+ ulaws[i] = ~(sign | (exponent << 4) | mantissa);
+ }
+ return mSampleCount;
+}
+
+int UlawCodec::decode(int16_t *samples, void *payload, int length)
+{
+ int8_t *ulaws = (int8_t *)payload;
+ for (int i = 0; i < length; ++i) {
+ int ulaw = ~ulaws[i];
+ int exponent = (ulaw >> 4) & 0x07;
+ int mantissa = ulaw & 0x0F;
+ int sample = (((mantissa << 3) + 132) << exponent) - 132;
+ samples[i] = (ulaw < 0 ? -sample : sample);
+ }
+ return length;
+}
+
+AudioCodec *newUlawCodec()
+{
+ return new UlawCodec;
+}
+
+//------------------------------------------------------------------------------
+
+class AlawCodec : public AudioCodec
+{
+public:
+ bool set(int sampleRate, int sampleCount) {
+ mSampleCount = sampleCount;
+ return sampleCount > 0;
+ }
+ int encode(void *payload, int16_t *samples);
+ int decode(int16_t *samples, void *payload, int length);
+private:
+ int mSampleCount;
+};
+
+int AlawCodec::encode(void *payload, int16_t *samples)
+{
+ int8_t *alaws = (int8_t *)payload;
+ for (int i = 0; i < mSampleCount; ++i) {
+ int sample = samples[i];
+ int sign = (sample >> 8) & 0x80;
+ if (sample < 0) {
+ sample = -sample;
+ }
+ if (sample > 32767) {
+ sample = 32767;
+ }
+ int exponent = gExponents[sample >> 8];
+ int mantissa = (sample >> (exponent == 0 ? 4 : exponent + 3)) & 0x0F;
+ alaws[i] = (sign | (exponent << 4) | mantissa) ^ 0xD5;
+ }
+ return mSampleCount;
+}
+
+int AlawCodec::decode(int16_t *samples, void *payload, int length)
+{
+ int8_t *alaws = (int8_t *)payload;
+ for (int i = 0; i < length; ++i) {
+ int alaw = alaws[i] ^ 0x55;
+ int exponent = (alaw >> 4) & 0x07;
+ int mantissa = alaw & 0x0F;
+ int sample = (exponent == 0 ? (mantissa << 4) + 8 :
+ ((mantissa << 3) + 132) << exponent);
+ samples[i] = (alaw < 0 ? sample : -sample);
+ }
+ return length;
+}
+
+AudioCodec *newAlawCodec()
+{
+ return new AlawCodec;
+}
+
+struct AudioCodecType {
+ const char *name;
+ AudioCodec *(*create)();
+} gAudioCodecTypes[] = {
+ {"PCMA", newAlawCodec},
+ {"PCMU", newUlawCodec},
+ {NULL, NULL},
+};
+
+} // namespace
+
+AudioCodec *newAudioCodec(const char *codecName)
+{
+ AudioCodecType *type = gAudioCodecTypes;
+ while (type->name != NULL) {
+ if (strcmp(codecName, type->name) == 0) {
+ return type->create();
+ }
+ ++type;
+ }
+ return NULL;
+}
diff --git a/voip/jni/rtp/AudioCodec.h b/voip/jni/rtp/AudioCodec.h
new file mode 100644
index 0000000..797494c
--- /dev/null
+++ b/voip/jni/rtp/AudioCodec.h
@@ -0,0 +1,36 @@
+/*
+ * Copyrightm (C) 2010 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.
+ */
+
+#include <stdint.h>
+
+#ifndef __AUDIO_CODEC_H__
+#define __AUDIO_CODEC_H__
+
+class AudioCodec
+{
+public:
+ virtual ~AudioCodec() {}
+ // Returns true if initialization succeeds.
+ virtual bool set(int sampleRate, int sampleCount) = 0;
+ // Returns the length of payload in bytes.
+ virtual int encode(void *payload, int16_t *samples) = 0;
+ // Returns the number of decoded samples.
+ virtual int decode(int16_t *samples, void *payload, int length) = 0;
+};
+
+AudioCodec *newAudioCodec(const char *codecName);
+
+#endif
diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp
new file mode 100644
index 0000000..fc1ed9b
--- /dev/null
+++ b/voip/jni/rtp/AudioGroup.cpp
@@ -0,0 +1,1004 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <time.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#define LOG_TAG "AudioGroup"
+#include <cutils/atomic.h>
+#include <utils/Log.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+#include <utils/threads.h>
+#include <utils/SystemClock.h>
+#include <media/AudioSystem.h>
+#include <media/AudioRecord.h>
+#include <media/AudioTrack.h>
+#include <media/mediarecorder.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+
+#include "AudioCodec.h"
+
+extern int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss);
+
+namespace {
+
+using namespace android;
+
+int gRandom = -1;
+
+// We use a circular array to implement jitter buffer. The simplest way is doing
+// a modulo operation on the index while accessing the array. However modulo can
+// be expensive on some platforms, such as ARM. Thus we round up the size of the
+// array to the nearest power of 2 and then use bitwise-and instead of modulo.
+// Currently we make it 256ms long and assume packet interval is 32ms or less.
+// The first 64ms is the place where samples get mixed. The rest 192ms is the
+// real jitter buffer. For a stream at 8000Hz it takes 4096 bytes. These numbers
+// are chosen by experiments and each of them can be adjusted as needed.
+
+// Other notes:
+// + We use elapsedRealtime() to get the time. Since we use 32bit variables
+// instead of 64bit ones, comparison must be done by subtraction.
+// + Sampling rate must be multiple of 1000Hz, and packet length must be in
+// milliseconds. No floating points.
+// + If we cannot get enough CPU, we drop samples and simulate packet loss.
+// + Resampling is not done yet, so streams in one group must use the same rate.
+// For the first release we might only support 8kHz and 16kHz.
+
+class AudioStream
+{
+public:
+ AudioStream();
+ ~AudioStream();
+ bool set(int mode, int socket, sockaddr_storage *remote,
+ const char *codecName, int sampleRate, int sampleCount,
+ int codecType, int dtmfType);
+
+ void sendDtmf(int event);
+ bool mix(int32_t *output, int head, int tail, int sampleRate);
+ void encode(int tick, AudioStream *chain);
+ void decode(int tick);
+
+private:
+ enum {
+ NORMAL = 0,
+ SEND_ONLY = 1,
+ RECEIVE_ONLY = 2,
+ LAST_MODE = 2,
+ };
+
+ int mMode;
+ int mSocket;
+ sockaddr_storage mRemote;
+ AudioCodec *mCodec;
+ uint32_t mCodecMagic;
+ uint32_t mDtmfMagic;
+
+ int mTick;
+ int mSampleRate;
+ int mSampleCount;
+ int mInterval;
+
+ int16_t *mBuffer;
+ int mBufferMask;
+ int mBufferHead;
+ int mBufferTail;
+ int mLatencyScore;
+
+ uint16_t mSequence;
+ uint32_t mTimestamp;
+ uint32_t mSsrc;
+
+ int mDtmfEvent;
+ int mDtmfStart;
+
+ AudioStream *mNext;
+
+ friend class AudioGroup;
+};
+
+AudioStream::AudioStream()
+{
+ mSocket = -1;
+ mCodec = NULL;
+ mBuffer = NULL;
+ mNext = NULL;
+}
+
+AudioStream::~AudioStream()
+{
+ close(mSocket);
+ delete mCodec;
+ delete [] mBuffer;
+ LOGD("stream[%d] is dead", mSocket);
+}
+
+bool AudioStream::set(int mode, int socket, sockaddr_storage *remote,
+ const char *codecName, int sampleRate, int sampleCount,
+ int codecType, int dtmfType)
+{
+ if (mode < 0 || mode > LAST_MODE) {
+ return false;
+ }
+ mMode = mode;
+
+ if (codecName) {
+ mRemote = *remote;
+ mCodec = newAudioCodec(codecName);
+ if (!mCodec || !mCodec->set(sampleRate, sampleCount)) {
+ return false;
+ }
+ }
+
+ mCodecMagic = (0x8000 | codecType) << 16;
+ mDtmfMagic = (dtmfType == -1) ? 0 : (0x8000 | dtmfType) << 16;
+
+ mTick = elapsedRealtime();
+ mSampleRate = sampleRate / 1000;
+ mSampleCount = sampleCount;
+ mInterval = mSampleCount / mSampleRate;
+
+ // Allocate jitter buffer.
+ for (mBufferMask = 8192; mBufferMask < sampleRate; mBufferMask <<= 1);
+ mBufferMask >>= 2;
+ mBuffer = new int16_t[mBufferMask];
+ --mBufferMask;
+ mBufferHead = 0;
+ mBufferTail = 0;
+ mLatencyScore = 0;
+
+ // Initialize random bits.
+ read(gRandom, &mSequence, sizeof(mSequence));
+ read(gRandom, &mTimestamp, sizeof(mTimestamp));
+ read(gRandom, &mSsrc, sizeof(mSsrc));
+
+ mDtmfEvent = -1;
+ mDtmfStart = 0;
+
+ // Only take over the socket when succeeded.
+ mSocket = socket;
+
+ LOGD("stream[%d] is configured as %s %dkHz %dms", mSocket,
+ (codecName ? codecName : "RAW"), mSampleRate, mInterval);
+ return true;
+}
+
+void AudioStream::sendDtmf(int event)
+{
+ if (mDtmfMagic != 0) {
+ mDtmfEvent = event << 24;
+ mDtmfStart = mTimestamp + mSampleCount;
+ }
+}
+
+bool AudioStream::mix(int32_t *output, int head, int tail, int sampleRate)
+{
+ if (mMode == SEND_ONLY) {
+ return false;
+ }
+
+ if (head - mBufferHead < 0) {
+ head = mBufferHead;
+ }
+ if (tail - mBufferTail > 0) {
+ tail = mBufferTail;
+ }
+ if (tail - head <= 0) {
+ return false;
+ }
+
+ head *= mSampleRate;
+ tail *= mSampleRate;
+
+ if (sampleRate == mSampleRate) {
+ for (int i = head; i - tail < 0; ++i) {
+ output[i - head] += mBuffer[i & mBufferMask];
+ }
+ } else {
+ // TODO: implement resampling.
+ return false;
+ }
+ return true;
+}
+
+void AudioStream::encode(int tick, AudioStream *chain)
+{
+ if (tick - mTick >= mInterval) {
+ // We just missed the train. Pretend that packets in between are lost.
+ int skipped = (tick - mTick) / mInterval;
+ mTick += skipped * mInterval;
+ mSequence += skipped;
+ mTimestamp += skipped * mSampleCount;
+ LOGD("stream[%d] skips %d packets", mSocket, skipped);
+ }
+
+ tick = mTick;
+ mTick += mInterval;
+ ++mSequence;
+ mTimestamp += mSampleCount;
+
+ if (mMode == RECEIVE_ONLY) {
+ return;
+ }
+
+ // If there is an ongoing DTMF event, send it now.
+ if (mDtmfEvent != -1) {
+ int duration = mTimestamp - mDtmfStart;
+ // Make sure duration is reasonable.
+ if (duration >= 0 && duration < mSampleRate * 100) {
+ duration += mSampleCount;
+ int32_t buffer[4] = {
+ htonl(mDtmfMagic | mSequence),
+ htonl(mDtmfStart),
+ mSsrc,
+ htonl(mDtmfEvent | duration),
+ };
+ if (duration >= mSampleRate * 100) {
+ buffer[3] |= htonl(1 << 23);
+ mDtmfEvent = -1;
+ }
+ sendto(mSocket, buffer, sizeof(buffer), MSG_DONTWAIT,
+ (sockaddr *)&mRemote, sizeof(mRemote));
+ return;
+ }
+ mDtmfEvent = -1;
+ }
+
+ // It is time to mix streams.
+ bool mixed = false;
+ int32_t buffer[mSampleCount + 3];
+ memset(buffer, 0, sizeof(buffer));
+ while (chain) {
+ if (chain != this &&
+ chain->mix(buffer, tick - mInterval, tick, mSampleRate)) {
+ mixed = true;
+ }
+ chain = chain->mNext;
+ }
+ if (!mixed) {
+ LOGD("stream[%d] no data", mSocket);
+ return;
+ }
+
+ // Cook the packet and send it out.
+ int16_t samples[mSampleCount];
+ for (int i = 0; i < mSampleCount; ++i) {
+ int32_t sample = buffer[i];
+ if (sample < -32768) {
+ sample = -32768;
+ }
+ if (sample > 32767) {
+ sample = 32767;
+ }
+ samples[i] = sample;
+ }
+ if (!mCodec) {
+ // Special case for device stream.
+ send(mSocket, samples, sizeof(samples), MSG_DONTWAIT);
+ return;
+ }
+
+ buffer[0] = htonl(mCodecMagic | mSequence);
+ buffer[1] = htonl(mTimestamp);
+ buffer[2] = mSsrc;
+ int length = mCodec->encode(&buffer[3], samples);
+ if (length <= 0) {
+ LOGD("stream[%d] encoder error", mSocket);
+ return;
+ }
+ sendto(mSocket, buffer, length + 12, MSG_DONTWAIT, (sockaddr *)&mRemote,
+ sizeof(mRemote));
+}
+
+void AudioStream::decode(int tick)
+{
+ char c;
+ if (mMode == SEND_ONLY) {
+ recv(mSocket, &c, 1, MSG_DONTWAIT);
+ return;
+ }
+
+ // Make sure mBufferHead and mBufferTail are reasonable.
+ if ((unsigned int)(tick + 256 - mBufferHead) > 1024) {
+ mBufferHead = tick - 64;
+ mBufferTail = mBufferHead;
+ }
+
+ if (tick - mBufferHead > 64) {
+ // Throw away outdated samples.
+ mBufferHead = tick - 64;
+ if (mBufferTail - mBufferHead < 0) {
+ mBufferTail = mBufferHead;
+ }
+ }
+
+ if (mBufferTail - tick <= 80) {
+ mLatencyScore = tick;
+ } else if (tick - mLatencyScore >= 5000) {
+ // Reset the jitter buffer to 40ms if the latency keeps larger than 80ms
+ // in the past 5s. This rarely happens, so let us just keep it simple.
+ LOGD("stream[%d] latency control", mSocket);
+ mBufferTail = tick + 40;
+ }
+
+ if (mBufferTail - mBufferHead > 256 - mInterval) {
+ // Buffer overflow. Drop the packet.
+ LOGD("stream[%d] buffer overflow", mSocket);
+ recv(mSocket, &c, 1, MSG_DONTWAIT);
+ return;
+ }
+
+ // Receive the packet and decode it.
+ int16_t samples[mSampleCount];
+ int length = 0;
+ if (!mCodec) {
+ // Special case for device stream.
+ length = recv(mSocket, samples, sizeof(samples),
+ MSG_TRUNC | MSG_DONTWAIT) >> 1;
+ } else {
+ __attribute__((aligned(4))) uint8_t buffer[2048];
+ length = recv(mSocket, buffer, sizeof(buffer),
+ MSG_TRUNC | MSG_DONTWAIT);
+
+ // Do we need to check SSRC, sequence, and timestamp? They are not
+ // reliable but at least they can be used to identity duplicates?
+ if (length < 12 || length > (int)sizeof(buffer) ||
+ (ntohl(*(uint32_t *)buffer) & 0xC07F0000) != mCodecMagic) {
+ LOGD("stream[%d] malformed packet", mSocket);
+ return;
+ }
+ int offset = 12 + ((buffer[0] & 0x0F) << 2);
+ if ((buffer[0] & 0x10) != 0) {
+ offset += 4 + (ntohs(*(uint16_t *)&buffer[offset + 2]) << 2);
+ }
+ if ((buffer[0] & 0x20) != 0) {
+ length -= buffer[length - 1];
+ }
+ length -= offset;
+ if (length >= 0) {
+ length = mCodec->decode(samples, &buffer[offset], length);
+ }
+ }
+ if (length != mSampleCount) {
+ LOGD("stream[%d] decoder error", mSocket);
+ return;
+ }
+
+ if (tick - mBufferTail > 0) {
+ // Buffer underrun. Reset the jitter buffer to 40ms.
+ LOGD("stream[%d] buffer underrun", mSocket);
+ if (mBufferTail - mBufferHead <= 0) {
+ mBufferHead = tick + 40;
+ mBufferTail = mBufferHead;
+ } else {
+ int tail = (tick + 40) * mSampleRate;
+ for (int i = mBufferTail * mSampleRate; i - tail < 0; ++i) {
+ mBuffer[i & mBufferMask] = 0;
+ }
+ mBufferTail = tick + 40;
+ }
+ }
+
+ // Append to the jitter buffer.
+ int tail = mBufferTail * mSampleRate;
+ for (int i = 0; i < mSampleCount; ++i) {
+ mBuffer[tail & mBufferMask] = samples[i];
+ ++tail;
+ }
+ mBufferTail += mInterval;
+}
+
+//------------------------------------------------------------------------------
+
+class AudioGroup
+{
+public:
+ AudioGroup();
+ ~AudioGroup();
+ bool set(int sampleRate, int sampleCount);
+
+ bool setMode(int mode);
+ bool sendDtmf(int event);
+ bool add(AudioStream *stream);
+ bool remove(int socket);
+
+private:
+ enum {
+ ON_HOLD = 0,
+ MUTED = 1,
+ NORMAL = 2,
+ EC_ENABLED = 3,
+ LAST_MODE = 3,
+ };
+ int mMode;
+ AudioStream *mChain;
+ int mEventQueue;
+ volatile int mDtmfEvent;
+
+ int mSampleCount;
+ int mDeviceSocket;
+ AudioTrack mTrack;
+ AudioRecord mRecord;
+
+ bool networkLoop();
+ bool deviceLoop();
+
+ class NetworkThread : public Thread
+ {
+ public:
+ NetworkThread(AudioGroup *group) : Thread(false), mGroup(group) {}
+
+ bool start()
+ {
+ if (run("Network", ANDROID_PRIORITY_AUDIO) != NO_ERROR) {
+ LOGE("cannot start network thread");
+ return false;
+ }
+ return true;
+ }
+
+ private:
+ AudioGroup *mGroup;
+ bool threadLoop()
+ {
+ return mGroup->networkLoop();
+ }
+ };
+ sp<NetworkThread> mNetworkThread;
+
+ class DeviceThread : public Thread
+ {
+ public:
+ DeviceThread(AudioGroup *group) : Thread(false), mGroup(group) {}
+
+ bool start()
+ {
+ char c;
+ while (recv(mGroup->mDeviceSocket, &c, 1, MSG_DONTWAIT) == 1);
+
+ if (run("Device", ANDROID_PRIORITY_AUDIO) != NO_ERROR) {
+ LOGE("cannot start device thread");
+ return false;
+ }
+ return true;
+ }
+
+ private:
+ AudioGroup *mGroup;
+ bool threadLoop()
+ {
+ return mGroup->deviceLoop();
+ }
+ };
+ sp<DeviceThread> mDeviceThread;
+};
+
+AudioGroup::AudioGroup()
+{
+ mMode = ON_HOLD;
+ mChain = NULL;
+ mEventQueue = -1;
+ mDtmfEvent = -1;
+ mDeviceSocket = -1;
+ mNetworkThread = new NetworkThread(this);
+ mDeviceThread = new DeviceThread(this);
+}
+
+AudioGroup::~AudioGroup()
+{
+ mNetworkThread->requestExitAndWait();
+ mDeviceThread->requestExitAndWait();
+ mTrack.stop();
+ mRecord.stop();
+ close(mEventQueue);
+ close(mDeviceSocket);
+ while (mChain) {
+ AudioStream *next = mChain->mNext;
+ delete mChain;
+ mChain = next;
+ }
+ LOGD("group[%d] is dead", mDeviceSocket);
+}
+
+#define FROYO_COMPATIBLE
+#ifdef FROYO_COMPATIBLE
+
+// Copied from AudioRecord.cpp.
+status_t AudioRecord_getMinFrameCount(
+ int* frameCount,
+ uint32_t sampleRate,
+ int format,
+ int channelCount)
+{
+ size_t size = 0;
+ if (AudioSystem::getInputBufferSize(sampleRate, format, channelCount, &size)
+ != NO_ERROR) {
+ LOGE("AudioSystem could not query the input buffer size.");
+ return NO_INIT;
+ }
+
+ if (size == 0) {
+ LOGE("Unsupported configuration: sampleRate %d, format %d, channelCount %d",
+ sampleRate, format, channelCount);
+ return BAD_VALUE;
+ }
+
+ // We double the size of input buffer for ping pong use of record buffer.
+ size <<= 1;
+
+ if (AudioSystem::isLinearPCM(format)) {
+ size /= channelCount * (format == AudioSystem::PCM_16_BIT ? 2 : 1);
+ }
+
+ *frameCount = size;
+ return NO_ERROR;
+}
+
+// Copied from AudioTrack.cpp.
+status_t AudioTrack_getMinFrameCount(
+ int* frameCount,
+ int streamType,
+ uint32_t sampleRate)
+{
+ int afSampleRate;
+ if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) {
+ return NO_INIT;
+ }
+ int afFrameCount;
+ if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) {
+ return NO_INIT;
+ }
+ uint32_t afLatency;
+ if (AudioSystem::getOutputLatency(&afLatency, streamType) != NO_ERROR) {
+ return NO_INIT;
+ }
+
+ // Ensure that buffer depth covers at least audio hardware latency
+ uint32_t minBufCount = afLatency / ((1000 * afFrameCount) / afSampleRate);
+ if (minBufCount < 2) minBufCount = 2;
+
+ *frameCount = (sampleRate == 0) ? afFrameCount * minBufCount :
+ afFrameCount * minBufCount * sampleRate / afSampleRate;
+ return NO_ERROR;
+}
+
+#endif
+
+bool AudioGroup::set(int sampleRate, int sampleCount)
+{
+ mEventQueue = epoll_create(2);
+ if (mEventQueue == -1) {
+ LOGE("epoll_create: %s", strerror(errno));
+ return false;
+ }
+
+ mSampleCount = sampleCount;
+
+ // Find out the frame count for AudioTrack and AudioRecord.
+ int output = 0;
+ int input = 0;
+#ifdef FROYO_COMPATIBLE
+ if (AudioTrack_getMinFrameCount(&output, AudioSystem::VOICE_CALL,
+ sampleRate) != NO_ERROR || output <= 0 ||
+ AudioRecord_getMinFrameCount(&input, sampleRate,
+ AudioSystem::PCM_16_BIT, 1) != NO_ERROR || input <= 0) {
+ LOGE("cannot compute frame count");
+ return false;
+ }
+#else
+ if (AudioTrack::getMinFrameCount(&output, AudioSystem::VOICE_CALL,
+ sampleRate) != NO_ERROR || output <= 0 ||
+ AudioRecord::getMinFrameCount(&input, sampleRate,
+ AudioSystem::PCM_16_BIT, 1) != NO_ERROR || input <= 0) {
+ LOGE("cannot compute frame count");
+ return false;
+ }
+#endif
+ LOGD("reported frame count: output %d, input %d", output, input);
+
+ output = (output + sampleCount - 1) / sampleCount * sampleCount;
+ input = (input + sampleCount - 1) / sampleCount * sampleCount;
+ if (input < output * 2) {
+ input = output * 2;
+ }
+ LOGD("adjusted frame count: output %d, input %d", output, input);
+
+ // Initialize AudioTrack and AudioRecord.
+ if (mTrack.set(AudioSystem::VOICE_CALL, sampleRate, AudioSystem::PCM_16_BIT,
+ AudioSystem::CHANNEL_OUT_MONO, output) != NO_ERROR ||
+ mRecord.set(AUDIO_SOURCE_MIC, sampleRate, AudioSystem::PCM_16_BIT,
+ AudioSystem::CHANNEL_IN_MONO, input) != NO_ERROR) {
+ LOGE("cannot initialize audio device");
+ return false;
+ }
+ LOGD("latency: output %d, input %d", mTrack.latency(), mRecord.latency());
+
+ // TODO: initialize echo canceler here.
+
+ // Create device socket.
+ int pair[2];
+ if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair)) {
+ LOGE("socketpair: %s", strerror(errno));
+ return false;
+ }
+ mDeviceSocket = pair[0];
+
+ // Create device stream.
+ mChain = new AudioStream;
+ if (!mChain->set(AudioStream::NORMAL, pair[1], NULL, NULL,
+ sampleRate, sampleCount, -1, -1)) {
+ close(pair[1]);
+ LOGE("cannot initialize device stream");
+ return false;
+ }
+
+ // Give device socket a reasonable timeout and buffer size.
+ timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 1000 * sampleCount / sampleRate * 500;
+ if (setsockopt(pair[0], SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) ||
+ setsockopt(pair[0], SOL_SOCKET, SO_RCVBUF, &output, sizeof(output)) ||
+ setsockopt(pair[1], SOL_SOCKET, SO_SNDBUF, &output, sizeof(output))) {
+ LOGE("setsockopt: %s", strerror(errno));
+ return false;
+ }
+
+ // Add device stream into event queue.
+ epoll_event event;
+ event.events = EPOLLIN;
+ event.data.ptr = mChain;
+ if (epoll_ctl(mEventQueue, EPOLL_CTL_ADD, pair[1], &event)) {
+ LOGE("epoll_ctl: %s", strerror(errno));
+ return false;
+ }
+
+ // Anything else?
+ LOGD("stream[%d] joins group[%d]", pair[1], pair[0]);
+ return true;
+}
+
+bool AudioGroup::setMode(int mode)
+{
+ if (mode < 0 || mode > LAST_MODE) {
+ return false;
+ }
+ if (mMode == mode) {
+ return true;
+ }
+
+ LOGD("group[%d] switches from mode %d to %d", mDeviceSocket, mMode, mode);
+ mMode = mode;
+
+ mDeviceThread->requestExitAndWait();
+ if (mode == ON_HOLD) {
+ mTrack.stop();
+ mRecord.stop();
+ return true;
+ }
+
+ mTrack.start();
+ if (mode == MUTED) {
+ mRecord.stop();
+ } else {
+ mRecord.start();
+ }
+
+ if (!mDeviceThread->start()) {
+ mTrack.stop();
+ mRecord.stop();
+ return false;
+ }
+ return true;
+}
+
+bool AudioGroup::sendDtmf(int event)
+{
+ if (event < 0 || event > 15) {
+ return false;
+ }
+
+ // DTMF is rarely used, so we try to make it as lightweight as possible.
+ // Using volatile might be dodgy, but using a pipe or pthread primitives
+ // or stop-set-restart threads seems too heavy. Will investigate later.
+ timespec ts;
+ ts.tv_sec = 0;
+ ts.tv_nsec = 100000000;
+ for (int i = 0; mDtmfEvent != -1 && i < 20; ++i) {
+ nanosleep(&ts, NULL);
+ }
+ if (mDtmfEvent != -1) {
+ return false;
+ }
+ mDtmfEvent = event;
+ nanosleep(&ts, NULL);
+ return true;
+}
+
+bool AudioGroup::add(AudioStream *stream)
+{
+ mNetworkThread->requestExitAndWait();
+
+ epoll_event event;
+ event.events = EPOLLIN;
+ event.data.ptr = stream;
+ if (epoll_ctl(mEventQueue, EPOLL_CTL_ADD, stream->mSocket, &event)) {
+ LOGE("epoll_ctl: %s", strerror(errno));
+ return false;
+ }
+
+ stream->mNext = mChain->mNext;
+ mChain->mNext = stream;
+ if (!mNetworkThread->start()) {
+ // Only take over the stream when succeeded.
+ mChain->mNext = stream->mNext;
+ return false;
+ }
+
+ LOGD("stream[%d] joins group[%d]", stream->mSocket, mDeviceSocket);
+ return true;
+}
+
+bool AudioGroup::remove(int socket)
+{
+ mNetworkThread->requestExitAndWait();
+
+ for (AudioStream *stream = mChain; stream->mNext; stream = stream->mNext) {
+ AudioStream *target = stream->mNext;
+ if (target->mSocket == socket) {
+ stream->mNext = target->mNext;
+ LOGD("stream[%d] leaves group[%d]", socket, mDeviceSocket);
+ delete target;
+ break;
+ }
+ }
+
+ // Do not start network thread if there is only one stream.
+ if (!mChain->mNext || !mNetworkThread->start()) {
+ return false;
+ }
+ return true;
+}
+
+bool AudioGroup::networkLoop()
+{
+ int tick = elapsedRealtime();
+ int deadline = tick + 10;
+ int count = 0;
+
+ for (AudioStream *stream = mChain; stream; stream = stream->mNext) {
+ if (!stream->mTick || tick - stream->mTick >= 0) {
+ stream->encode(tick, mChain);
+ }
+ if (deadline - stream->mTick > 0) {
+ deadline = stream->mTick;
+ }
+ ++count;
+ }
+
+ if (mDtmfEvent != -1) {
+ int event = mDtmfEvent;
+ for (AudioStream *stream = mChain; stream; stream = stream->mNext) {
+ stream->sendDtmf(event);
+ }
+ mDtmfEvent = -1;
+ }
+
+ deadline -= tick;
+ if (deadline < 1) {
+ deadline = 1;
+ }
+
+ epoll_event events[count];
+ count = epoll_wait(mEventQueue, events, count, deadline);
+ if (count == -1) {
+ LOGE("epoll_wait: %s", strerror(errno));
+ return false;
+ }
+ for (int i = 0; i < count; ++i) {
+ ((AudioStream *)events[i].data.ptr)->decode(tick);
+ }
+
+ return true;
+}
+
+bool AudioGroup::deviceLoop()
+{
+ int16_t output[mSampleCount];
+
+ if (recv(mDeviceSocket, output, sizeof(output), 0) <= 0) {
+ memset(output, 0, sizeof(output));
+ }
+ if (mTrack.write(output, sizeof(output)) != (int)sizeof(output)) {
+ LOGE("cannot write to AudioTrack");
+ return false;
+ }
+
+ if (mMode != MUTED) {
+ uint32_t frameCount = mRecord.frameCount();
+ AudioRecord::Buffer input;
+ input.frameCount = frameCount;
+
+ if (mRecord.obtainBuffer(&input, -1) != NO_ERROR) {
+ LOGE("cannot read from AudioRecord");
+ return false;
+ }
+
+ if (input.frameCount < (uint32_t)mSampleCount) {
+ input.frameCount = 0;
+ } else {
+ if (mMode == NORMAL) {
+ send(mDeviceSocket, input.i8, sizeof(output), MSG_DONTWAIT);
+ } else {
+ // TODO: Echo canceller runs here.
+ send(mDeviceSocket, input.i8, sizeof(output), MSG_DONTWAIT);
+ }
+ if (input.frameCount < frameCount) {
+ input.frameCount = mSampleCount;
+ }
+ }
+
+ mRecord.releaseBuffer(&input);
+ }
+
+ return true;
+}
+
+//------------------------------------------------------------------------------
+
+static jfieldID gNative;
+static jfieldID gMode;
+
+jint add(JNIEnv *env, jobject thiz, jint mode,
+ jint socket, jstring jRemoteAddress, jint remotePort,
+ jstring jCodecName, jint sampleRate, jint sampleCount,
+ jint codecType, jint dtmfType)
+{
+ const char *codecName = NULL;
+ AudioStream *stream = NULL;
+ AudioGroup *group = NULL;
+
+ // Sanity check.
+ sockaddr_storage remote;
+ if (parse(env, jRemoteAddress, remotePort, &remote) < 0) {
+ // Exception already thrown.
+ return -1;
+ }
+ if (sampleRate < 0 || sampleCount < 0 || codecType < 0 || codecType > 127) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ goto error;
+ }
+ if (!jCodecName) {
+ jniThrowNullPointerException(env, "codecName");
+ return -1;
+ }
+ codecName = env->GetStringUTFChars(jCodecName, NULL);
+ if (!codecName) {
+ // Exception already thrown.
+ return -1;
+ }
+
+ // Create audio stream.
+ stream = new AudioStream;
+ if (!stream->set(mode, socket, &remote, codecName, sampleRate, sampleCount,
+ codecType, dtmfType)) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "cannot initialize audio stream");
+ goto error;
+ }
+ socket = -1;
+
+ // Create audio group.
+ group = (AudioGroup *)env->GetIntField(thiz, gNative);
+ if (!group) {
+ int mode = env->GetIntField(thiz, gMode);
+ group = new AudioGroup;
+ if (!group->set(8000, 256) || !group->setMode(mode)) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "cannot initialize audio group");
+ goto error;
+ }
+ }
+
+ // Add audio stream into audio group.
+ if (!group->add(stream)) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "cannot add audio stream");
+ goto error;
+ }
+
+ // Succeed.
+ env->SetIntField(thiz, gNative, (int)group);
+ env->ReleaseStringUTFChars(jCodecName, codecName);
+ return socket;
+
+error:
+ delete group;
+ delete stream;
+ close(socket);
+ env->SetIntField(thiz, gNative, NULL);
+ env->ReleaseStringUTFChars(jCodecName, codecName);
+ return -1;
+}
+
+void remove(JNIEnv *env, jobject thiz, jint socket)
+{
+ AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative);
+ if (group) {
+ if (socket == -1 || !group->remove(socket)) {
+ delete group;
+ env->SetIntField(thiz, gNative, NULL);
+ }
+ }
+}
+
+void setMode(JNIEnv *env, jobject thiz, jint mode)
+{
+ AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative);
+ if (group && !group->setMode(mode)) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+ env->SetIntField(thiz, gMode, mode);
+}
+
+void sendDtmf(JNIEnv *env, jobject thiz, jint event)
+{
+ AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative);
+ if (group && !group->sendDtmf(event)) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ }
+}
+
+JNINativeMethod gMethods[] = {
+ {"add", "(IILjava/lang/String;ILjava/lang/String;IIII)I", (void *)add},
+ {"remove", "(I)V", (void *)remove},
+ {"setMode", "(I)V", (void *)setMode},
+ {"sendDtmf", "(I)V", (void *)sendDtmf},
+};
+
+} // namespace
+
+int registerAudioGroup(JNIEnv *env)
+{
+ gRandom = open("/dev/urandom", O_RDONLY);
+ if (gRandom == -1) {
+ LOGE("urandom: %s", strerror(errno));
+ return -1;
+ }
+
+ jclass clazz;
+ if ((clazz = env->FindClass("android/net/rtp/AudioGroup")) == NULL ||
+ (gNative = env->GetFieldID(clazz, "mNative", "I")) == NULL ||
+ (gMode = env->GetFieldID(clazz, "mMode", "I")) == NULL ||
+ env->RegisterNatives(clazz, gMethods, NELEM(gMethods)) < 0) {
+ LOGE("JNI registration failed");
+ return -1;
+ }
+ return 0;
+}
diff --git a/voip/jni/rtp/RtpStream.cpp b/voip/jni/rtp/RtpStream.cpp
new file mode 100644
index 0000000..33b88e43
--- /dev/null
+++ b/voip/jni/rtp/RtpStream.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#define LOG_TAG "RtpStream"
+#include <utils/Log.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+
+extern int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss);
+
+namespace {
+
+jfieldID gNative;
+
+jint create(JNIEnv *env, jobject thiz, jstring jAddress)
+{
+ env->SetIntField(thiz, gNative, -1);
+
+ sockaddr_storage ss;
+ if (parse(env, jAddress, 0, &ss) < 0) {
+ // Exception already thrown.
+ return -1;
+ }
+
+ int socket = ::socket(ss.ss_family, SOCK_DGRAM, 0);
+ socklen_t len = sizeof(ss);
+ if (socket == -1 || bind(socket, (sockaddr *)&ss, sizeof(ss)) != 0 ||
+ getsockname(socket, (sockaddr *)&ss, &len) != 0) {
+ jniThrowException(env, "java/net/SocketException", strerror(errno));
+ ::close(socket);
+ return -1;
+ }
+
+ uint16_t *p = (ss.ss_family == AF_INET) ?
+ &((sockaddr_in *)&ss)->sin_port : &((sockaddr_in6 *)&ss)->sin6_port;
+ uint16_t port = ntohs(*p);
+ if ((port & 1) == 0) {
+ env->SetIntField(thiz, gNative, socket);
+ return port;
+ }
+ ::close(socket);
+
+ socket = ::socket(ss.ss_family, SOCK_DGRAM, 0);
+ if (socket != -1) {
+ uint16_t delta = port << 1;
+ ++port;
+
+ for (int i = 0; i < 1000; ++i) {
+ do {
+ port += delta;
+ } while (port < 1024);
+ *p = htons(port);
+
+ if (bind(socket, (sockaddr *)&ss, sizeof(ss)) == 0) {
+ env->SetIntField(thiz, gNative, socket);
+ return port;
+ }
+ }
+ }
+
+ jniThrowException(env, "java/net/SocketException", strerror(errno));
+ ::close(socket);
+ return -1;
+}
+
+jint dup(JNIEnv *env, jobject thiz)
+{
+ int socket1 = env->GetIntField(thiz, gNative);
+ int socket2 = ::dup(socket1);
+ if (socket2 == -1) {
+ jniThrowException(env, "java/lang/IllegalStateException", strerror(errno));
+ }
+ LOGD("dup %d to %d", socket1, socket2);
+ return socket2;
+}
+
+void close(JNIEnv *env, jobject thiz)
+{
+ int socket = env->GetIntField(thiz, gNative);
+ ::close(socket);
+ env->SetIntField(thiz, gNative, -1);
+ LOGD("close %d", socket);
+}
+
+JNINativeMethod gMethods[] = {
+ {"create", "(Ljava/lang/String;)I", (void *)create},
+ {"dup", "()I", (void *)dup},
+ {"close", "()V", (void *)close},
+};
+
+} // namespace
+
+int registerRtpStream(JNIEnv *env)
+{
+ jclass clazz;
+ if ((clazz = env->FindClass("android/net/rtp/RtpStream")) == NULL ||
+ (gNative = env->GetFieldID(clazz, "mNative", "I")) == NULL ||
+ env->RegisterNatives(clazz, gMethods, NELEM(gMethods)) < 0) {
+ LOGE("JNI registration failed");
+ return -1;
+ }
+ return 0;
+}
diff --git a/voip/jni/rtp/rtp_jni.cpp b/voip/jni/rtp/rtp_jni.cpp
new file mode 100644
index 0000000..9f4bff9
--- /dev/null
+++ b/voip/jni/rtp/rtp_jni.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#include <stdio.h>
+
+#include "jni.h"
+
+extern int registerRtpStream(JNIEnv *env);
+extern int registerAudioGroup(JNIEnv *env);
+
+__attribute__((visibility("default"))) jint JNI_OnLoad(JavaVM *vm, void *unused)
+{
+ JNIEnv *env = NULL;
+ if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK ||
+ registerRtpStream(env) < 0 || registerAudioGroup(env) < 0) {
+ return -1;
+ }
+ return JNI_VERSION_1_4;
+}
diff --git a/voip/jni/rtp/util.cpp b/voip/jni/rtp/util.cpp
new file mode 100644
index 0000000..1d702fc
--- /dev/null
+++ b/voip/jni/rtp/util.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+
+int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss)
+{
+ if (!jAddress) {
+ jniThrowNullPointerException(env, "address");
+ return -1;
+ }
+ if (port < 0 || port > 65535) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "port");
+ return -1;
+ }
+ const char *address = env->GetStringUTFChars(jAddress, NULL);
+ if (!address) {
+ // Exception already thrown.
+ return -1;
+ }
+ memset(ss, 0, sizeof(*ss));
+
+ sockaddr_in *sin = (sockaddr_in *)ss;
+ if (inet_pton(AF_INET, address, &(sin->sin_addr)) > 0) {
+ sin->sin_family = AF_INET;
+ sin->sin_port = htons(port);
+ env->ReleaseStringUTFChars(jAddress, address);
+ return 0;
+ }
+
+ sockaddr_in6 *sin6 = (sockaddr_in6 *)ss;
+ if (inet_pton(AF_INET6, address, &(sin6->sin6_addr)) > 0) {
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_port = htons(port);
+ env->ReleaseStringUTFChars(jAddress, address);
+ return 0;
+ }
+
+ env->ReleaseStringUTFChars(jAddress, address);
+ jniThrowException(env, "java/lang/IllegalArgumentException", "address");
+ return -1;
+}