Merge "Add wakelock to UserCallActivity"
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 8845bf9..eb97fe5 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -45,10 +45,10 @@
<string name="no_vm_number" msgid="4164780423805688336">"Erantzungailuaren zenbakia falta da"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Ez da erantzungailuaren zenbakirik gorde SIM txartelean."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Gehitu zenbakia"</string>
- <string name="change_default_dialer_dialog_title" msgid="9101655962941740507">"<xliff:g id="NEW_APP">%s</xliff:g> telefonoaren aplikazio lehenetsia izatea nahi duzu?"</string>
+ <string name="change_default_dialer_dialog_title" msgid="9101655962941740507">"Telefonoa aplikazio lehenetsia <xliff:g id="NEW_APP">%s</xliff:g> izatea nahi duzu?"</string>
<string name="change_default_dialer_dialog_affirmative" msgid="8606546663509166276">"Ezarri lehenetsi gisa"</string>
<string name="change_default_dialer_dialog_negative" msgid="9078144617060173845">"Utzi"</string>
- <string name="change_default_dialer_warning_message" msgid="1417671460801684999">"Deien aspektu guztiak erabili eta kontrolatu ahal izango ditu <xliff:g id="NEW_APP">%s</xliff:g> aplikazioak. Fidagarriak diren aplikazioak bakarrik erabili beharko lirateke telefonoaren aplikazio lehenetsi gisa."</string>
+ <string name="change_default_dialer_warning_message" msgid="1417671460801684999">"Deien aspektu guztiak erabili eta kontrolatu ahal izango ditu <xliff:g id="NEW_APP">%s</xliff:g> aplikazioak. Fidagarriak diren aplikazioak bakarrik erabili beharko lirateke Telefonoa aplikazio lehenetsi gisa."</string>
<string name="blocked_numbers" msgid="2751843139572970579">"Blokeatutako zenbakiak"</string>
<string name="blocked_numbers_msg" msgid="1045015186124965643">"Ez duzu jasoko deirik edo testu-mezurik blokeatutako zenbakietatik."</string>
<string name="block_number" msgid="1101252256321306179">"Gehitu zenbakia"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index 7dd43f4..111d86b 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -46,4 +46,8 @@
different source (connection service) from the existing ringing call when reaching
maximum ringing calls. -->
<bool name="silence_incoming_when_different_service_and_maximum_ringing">false</bool>
+
+ <!-- Determines if the granting temporary location permission to the default dialer
+ during an emergency call should be allowed. The default is false. -->
+ <bool name="grant_location_permission_enabled">false</bool>
</resources>
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index d32468b..2bf05ae 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -604,6 +604,13 @@
}
public void destroy() {
+ // We should not keep these bitmaps around because the Call objects may be held for logging
+ // purposes.
+ // TODO: Make a container object that only stores the information we care about for Logging.
+ if (mCallerInfo != null) {
+ mCallerInfo.cachedPhotoIcon = null;
+ mCallerInfo.cachedPhoto = null;
+ }
Log.addEvent(this, LogUtils.Events.DESTROYED);
}
@@ -1997,6 +2004,39 @@
}
}
+ /**
+ * Sets this {@link Call} to has the specified {@code parentCall}. Also sets the parent to
+ * have this call as a child.
+ * @param parentCall
+ */
+ void setParentAndChildCall(Call parentCall) {
+ boolean isParentChanging = (mParentCall != parentCall);
+ setParentCall(parentCall);
+ setChildOf(parentCall);
+ if (isParentChanging) {
+ notifyParentChanged(parentCall);
+ }
+ }
+
+ /**
+ * Notifies listeners when the parent call changes.
+ * Used by {@link #setParentAndChildCall(Call)}, and in {@link CallsManager}.
+ * @param parentCall The new parent call for this call.
+ */
+ void notifyParentChanged(Call parentCall) {
+ Log.addEvent(this, LogUtils.Events.SET_PARENT, parentCall);
+ for (Listener l : mListeners) {
+ l.onParentChanged(this);
+ }
+ }
+
+ /**
+ * Unlike {@link #setParentAndChildCall(Call)}, only sets the parent call but does NOT set
+ * the child.
+ * TODO: This is only required when adding existing connections as a workaround so that we
+ * can avoid sending the "onParentChanged" callback until later.
+ * @param parentCall The new parent call.
+ */
void setParentCall(Call parentCall) {
if (parentCall == this) {
Log.e(this, new Exception(), "setting the parent to self");
@@ -2006,20 +2046,23 @@
// nothing to do
return;
}
- Preconditions.checkState(parentCall == null || mParentCall == null);
-
- Call oldParent = mParentCall;
if (mParentCall != null) {
mParentCall.removeChildCall(this);
}
mParentCall = parentCall;
- if (mParentCall != null) {
- mParentCall.addChildCall(this);
- }
+ }
- Log.addEvent(this, LogUtils.Events.SET_PARENT, mParentCall);
- for (Listener l : mListeners) {
- l.onParentChanged(this);
+ /**
+ * To be called after {@link #setParentCall(Call)} to complete setting the parent by adding
+ * this call as a child of another call.
+ * <p>
+ * Note: if using this method alone, the caller must call {@link #notifyParentChanged(Call)} to
+ * ensure the InCall UI is updated with the change in parent.
+ * @param parentCall The new parent for this call.
+ */
+ void setChildOf(Call parentCall) {
+ if (parentCall != null && !parentCall.getChildCalls().contains(this)) {
+ parentCall.addChildCall(this);
}
}
@@ -2138,7 +2181,7 @@
* that the insurance policy lives in the framework side of things.
*/
private void fixParentAfterDisconnect() {
- setParentCall(null);
+ setParentAndChildCall(null);
}
/**
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 7416c26..99e9e03 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -2122,7 +2122,7 @@
Trace.beginSection("removeCall");
Log.v(this, "removeCall(%s)", call);
- call.setParentCall(null); // need to clean up parent relationship before destroying.
+ call.setParentAndChildCall(null); // clean up parent relationship before destroying.
call.removeListener(this);
call.clearConnectionService();
// TODO: clean up RTT pipes
@@ -2630,7 +2630,30 @@
if (extras != null && extras.containsKey(Connection.EXTRA_ORIGINAL_CONNECTION_ID)) {
call.setOriginalConnectionId(extras.getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID));
}
+ Log.i(this, "createCallForExistingConnection: %s", connection);
+ Call parentCall = null;
+ if (!TextUtils.isEmpty(connection.getParentCallId())) {
+ String parentId = connection.getParentCallId();
+ parentCall = mCalls
+ .stream()
+ .filter(c -> c.getId().equals(parentId))
+ .findFirst()
+ .orElse(null);
+ if (parentCall != null) {
+ Log.i(this, "createCallForExistingConnection: %s added as child of %s.",
+ call.getId(),
+ parentCall.getId());
+ // Set JUST the parent property, which won't send an update to the Incall UI.
+ call.setParentCall(parentCall);
+ }
+ }
addCall(call);
+ if (parentCall != null) {
+ // Now, set the call as a child of the parent since it has been added to Telecom. This
+ // is where we will inform InCall.
+ call.setChildOf(parentCall);
+ call.notifyParentChanged(parentCall);
+ }
return call;
}
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index d445bd6..d9be775 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -77,6 +77,15 @@
logIncoming("handleCreateConnectionComplete %s", callId);
ConnectionServiceWrapper.this
.handleCreateConnectionComplete(callId, request, connection);
+
+ if (mServiceInterface != null) {
+ logOutgoing("createConnectionComplete %s", callId);
+ try {
+ mServiceInterface.createConnectionComplete(callId,
+ Log.getExternalSession());
+ } catch (RemoteException e) {
+ }
+ }
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -320,10 +329,10 @@
if (childCall != null) {
if (conferenceCallId == null) {
Log.d(this, "unsetting parent: %s", conferenceCallId);
- childCall.setParentCall(null);
+ childCall.setParentAndChildCall(null);
} else {
Call conferenceCall = mCallIdMapper.getCall(conferenceCallId);
- childCall.setParentCall(conferenceCall);
+ childCall.setParentAndChildCall(conferenceCall);
}
} else {
// Log.w(this, "setIsConferenced, unknown call id: %s", args.arg1);
@@ -436,7 +445,7 @@
Call childCall = mCallIdMapper.getCall(connId);
Log.d(this, "found child: %s", connId);
if (childCall != null) {
- childCall.setParentCall(conferenceCall);
+ childCall.setParentAndChildCall(conferenceCall);
}
}
}
diff --git a/src/com/android/server/telecom/DefaultDialerCache.java b/src/com/android/server/telecom/DefaultDialerCache.java
index 9f7be16..3b36119 100644
--- a/src/com/android/server/telecom/DefaultDialerCache.java
+++ b/src/com/android/server/telecom/DefaultDialerCache.java
@@ -91,6 +91,22 @@
}
};
+ private final BroadcastReceiver mUserRemovedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
+ int removedUser = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+ UserHandle.USER_NULL);
+ if (removedUser == UserHandle.USER_NULL) {
+ Log.w(LOG_TAG, "Expected EXTRA_USER_HANDLE with ACTION_USER_REMOVED");
+ } else {
+ removeUserFromCache(removedUser);
+ Log.i(LOG_TAG, "Removing user %s", removedUser);
+ }
+ }
+ }
+ };
+
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final ContentObserver mDefaultDialerObserver = new ContentObserver(mHandler) {
@Override
@@ -137,6 +153,8 @@
IntentFilter bootIntentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
context.registerReceiverAsUser(mReceiver, UserHandle.ALL, bootIntentFilter, null, null);
+ IntentFilter userRemovedFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
+ context.registerReceiver(mUserRemovedReceiver, userRemovedFilter);
Uri defaultDialerSetting =
Settings.Secure.getUriFor(Settings.Secure.DIALER_DEFAULT_APPLICATION);
@@ -221,6 +239,12 @@
}
}
+ private void removeUserFromCache(int userId) {
+ synchronized (mLock) {
+ mCurrentDefaultDialerPerUser.remove(userId);
+ }
+ }
+
/**
* registerContentObserver is really hard to mock out, so here is a getter method for the
* content observer for testing instead.
diff --git a/src/com/android/server/telecom/EmergencyCallHelper.java b/src/com/android/server/telecom/EmergencyCallHelper.java
index f30c309..5b4c2fd 100644
--- a/src/com/android/server/telecom/EmergencyCallHelper.java
+++ b/src/com/android/server/telecom/EmergencyCallHelper.java
@@ -75,6 +75,10 @@
}
private boolean shouldGrantTemporaryLocationPermission(Call call) {
+ if (!mContext.getResources().getBoolean(R.bool.grant_location_permission_enabled)) {
+ Log.i(this, "ShouldGrantTemporaryLocationPermission, disabled by config");
+ return false;
+ }
if (call == null) {
Log.i(this, "ShouldGrantTemporaryLocationPermission, no call");
return false;
diff --git a/src/com/android/server/telecom/RespondViaSmsManager.java b/src/com/android/server/telecom/RespondViaSmsManager.java
index a42513d..964f6ad 100644
--- a/src/com/android/server/telecom/RespondViaSmsManager.java
+++ b/src/com/android/server/telecom/RespondViaSmsManager.java
@@ -18,15 +18,12 @@
// TODO: Needed for move to system service: import com.android.internal.R;
import com.android.internal.os.SomeArgs;
-import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.SmsApplication;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
-import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -34,8 +31,8 @@
import android.telecom.Log;
import android.telecom.Response;
import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsManager;
import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
@@ -178,26 +175,28 @@
*/
private void rejectCallWithMessage(Context context, String phoneNumber, String textMessage,
int subId, String contactName) {
- if (!TextUtils.isEmpty(textMessage)) {
- final ComponentName component =
- SmsApplication.getDefaultRespondViaMessageApplication(context,
- true /*updateIfNeeded*/);
- if (component != null) {
- // Build and send the intent
- final Uri uri = Uri.fromParts(Constants.SCHEME_SMSTO, phoneNumber, null);
- final Intent intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE, uri);
- intent.putExtra(Intent.EXTRA_TEXT, textMessage);
- if (SubscriptionManager.isValidSubscriptionId(subId)) {
- intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
- }
+ if (TextUtils.isEmpty(textMessage)) {
+ Log.w(RespondViaSmsManager.this, "Couldn't send SMS message: empty text message. ");
+ return;
+ }
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ Log.w(RespondViaSmsManager.this, "Couldn't send SMS message: Invalid SubId: " +
+ subId);
+ return;
+ }
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = !TextUtils.isEmpty(contactName) ? contactName : phoneNumber;
- args.arg2 = context;
- mHandler.obtainMessage(MSG_SHOW_SENT_TOAST, args).sendToTarget();
- intent.setComponent(component);
- context.startService(intent);
- }
+ SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId);
+ try {
+ smsManager.sendTextMessage(phoneNumber, null, textMessage, null /*sentIntent*/,
+ null /*deliveryIntent*/);
+
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = !TextUtils.isEmpty(contactName) ? contactName : phoneNumber;
+ args.arg2 = context;
+ mHandler.obtainMessage(MSG_SHOW_SENT_TOAST, args).sendToTarget();
+ } catch (IllegalArgumentException e) {
+ Log.w(RespondViaSmsManager.this, "Couldn't send SMS message: " +
+ e.getMessage());
}
}
}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index b1ba744..cbcd41d 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -77,6 +77,7 @@
}
}
+ private static final String TIME_LINE_ARG = "timeline";
private static final int DEFAULT_VIDEO_STATE = -1;
private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() {
@@ -994,6 +995,10 @@
android.Manifest.permission.MANAGE_OWN_CALLS,
"Self-managed phone accounts must have MANAGE_OWN_CALLS " +
"permission.");
+
+ // Self-managed ConnectionServices can ONLY add new incoming calls
+ // using their own PhoneAccounts. The checkPackage(..) app opps
+ // check above ensures this.
}
}
long token = Binder.clearCallingIdentity();
@@ -1084,6 +1089,16 @@
if (isSelfManaged) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_OWN_CALLS,
"Self-managed ConnectionServices require MANAGE_OWN_CALLS permission.");
+
+ if (!callingPackage.equals(
+ phoneAccountHandle.getComponentName().getPackageName())
+ && !canCallPhone(callingPackage,
+ "CALL_PHONE permission required to place calls.")) {
+ // The caller is not allowed to place calls, so we want to ensure that it
+ // can only place calls through itself.
+ throw new SecurityException("Self-managed ConnectionServices can only "
+ + "place calls through their own ConnectionService.");
+ }
} else if (!canCallPhone(callingPackage, "placeCall")) {
throw new SecurityException("Package " + callingPackage
+ " is not allowed to place phone calls");
@@ -1208,6 +1223,7 @@
Analytics.dumpToEncodedProto(writer, args);
return;
}
+ boolean isTimeLineView = (args.length > 0 && TIME_LINE_ARG.equalsIgnoreCase(args[0]));
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
if (mCallsManager != null) {
@@ -1226,8 +1242,11 @@
Analytics.dump(pw);
pw.decreaseIndent();
}
-
- Log.dumpEvents(pw);
+ if (isTimeLineView) {
+ Log.dumpEventsTimeline(pw);
+ } else {
+ Log.dumpEvents(pw);
+ }
}
/**
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index e3d99d9..1ee7de5 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -688,7 +688,8 @@
address, actualAddress);
}
if (actualAddress != null && !connectAudio(actualAddress)) {
- Log.w(LOG_TAG, "Could not connect to %s. Will %s", shouldRetry ? "retry" : "not retry");
+ Log.w(LOG_TAG, "Could not connect to %s. Will %s",
+ actualAddress, shouldRetry ? "retry" : "not retry");
if (shouldRetry) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = Log.createSubsession();
diff --git a/src/com/android/server/telecom/ui/NotificationChannelManager.java b/src/com/android/server/telecom/ui/NotificationChannelManager.java
index 7b64e31..46b6c28 100644
--- a/src/com/android/server/telecom/ui/NotificationChannelManager.java
+++ b/src/com/android/server/telecom/ui/NotificationChannelManager.java
@@ -18,9 +18,13 @@
import android.app.NotificationChannel;
import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.media.AudioAttributes;
import android.net.Uri;
+import android.telecom.Log;
import com.android.server.telecom.R;
@@ -33,18 +37,29 @@
public static final String CHANNEL_ID_MISSED_CALLS = "TelecomMissedCalls";
public static final String CHANNEL_ID_INCOMING_CALLS = "TelecomIncomingCalls";
+ private BroadcastReceiver mLocaleChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(this, "Locale change; recreating channels.");
+ createOrUpdateAll(context);
+ }
+ };
+
public void createChannels(Context context) {
- maybeCreateChannel(context, CHANNEL_ID_MISSED_CALLS);
- maybeCreateChannel(context, CHANNEL_ID_INCOMING_CALLS);
+ context.registerReceiver(mLocaleChangeReceiver,
+ new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
+
+ createOrUpdateAll(context);
}
- private void maybeCreateChannel(Context context, String channelId) {
- NotificationChannel channel = getNotificationManager(context).getNotificationChannel(
- channelId);
- if (channel == null) {
- channel = createChannel(context, channelId);
- getNotificationManager(context).createNotificationChannel(channel);
- }
+ private void createOrUpdateAll(Context context) {
+ createOrUpdateChannel(context, CHANNEL_ID_MISSED_CALLS);
+ createOrUpdateChannel(context, CHANNEL_ID_INCOMING_CALLS);
+ }
+
+ private void createOrUpdateChannel(Context context, String channelId) {
+ NotificationChannel channel = createChannel(context, channelId);
+ getNotificationManager(context).createNotificationChannel(channel);
}
private NotificationChannel createChannel(Context context, String channelId) {
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index 592145b..83f021f 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -25,6 +25,7 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
+ <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
@@ -178,6 +179,16 @@
</intent-filter>
</activity>
+ <activity android:name="com.android.server.telecom.testapps.TestCertActivity"
+ android:label="@string/KeyUiAppLabel"
+ android:launchMode="singleInstance">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
<activity android:name="com.android.server.telecom.testapps.SelfManagedCallingActivity"
android:label="@string/selfManagedCallingActivityLabel"
android:process="com.android.server.telecom.testapps.SelfMangingCallingApp"
diff --git a/testapps/res/layout/key_list.xml b/testapps/res/layout/key_list.xml
new file mode 100644
index 0000000..f56836c
--- /dev/null
+++ b/testapps/res/layout/key_list.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"/>
diff --git a/testapps/res/layout/testcert_main.xml b/testapps/res/layout/testcert_main.xml
new file mode 100644
index 0000000..84ed1e8
--- /dev/null
+++ b/testapps/res/layout/testcert_main.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+ <EditText
+ android:id="@+id/text"
+ android:inputType="text"
+ android:layout_width="200dp"
+ android:layout_height="wrap_content" />
+ <Button
+ android:id="@+id/get_key_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/getKeyButton" />
+ <ListView
+ android:id="@+id/keylist"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+</LinearLayout>
diff --git a/testapps/res/values/donottranslate_strings.xml b/testapps/res/values/donottranslate_strings.xml
index bfe7550..a0485d0 100644
--- a/testapps/res/values/donottranslate_strings.xml
+++ b/testapps/res/values/donottranslate_strings.xml
@@ -64,6 +64,10 @@
<string name="placeUssdButton">Send USSD</string>
+ <string name="KeyUiAppLabel">Get Key UI</string>
+
+ <string name="getKeyButton">Get Key Json</string>
+
<!-- String for button in SelfManagedCallingActivity. -->
<string name="checkIfPermittedBeforeCallingButton">Check if calls permitted before calling</string>
diff --git a/testapps/src/com/android/server/telecom/testapps/TestCertActivity.java b/testapps/src/com/android/server/telecom/testapps/TestCertActivity.java
new file mode 100644
index 0000000..0df836c
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/TestCertActivity.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2017 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.telecom.testapps;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.telephony.ImsiEncryptionInfo;
+import android.text.TextUtils;
+import android.util.Log;
+import android.telephony.TelephonyManager;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import android.app.ProgressDialog;
+import android.os.AsyncTask;
+import android.util.Log;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.ProtocolException;
+import java.net.URL;
+import java.util.ArrayList;
+
+import android.util.Base64;
+
+public class TestCertActivity extends Activity {
+
+ private EditText mCertUrlView;
+ public static final String LOG_TAG = "TestCertActivity";
+
+ private ProgressDialog progressDialog;
+ private ArrayList<String> keyList = new ArrayList<String>();
+
+ // URL to get the json
+ private String mURL = "";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.testcert_main);
+ findViewById(R.id.get_key_button).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ new GetKeys().execute();
+ }
+ });
+
+ mCertUrlView = (EditText) findViewById(R.id.text);
+ mCertUrlView.setText(mURL);
+ }
+
+ public static PublicKey makeKeyObject(byte[] publicKeyBytes) {
+ try {
+ X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(publicKeyBytes);
+ return KeyFactory.getInstance("RSA").generatePublic(pubKeySpec);
+ } catch (InvalidKeySpecException | NoSuchAlgorithmException ex) {
+ Log.e(LOG_TAG, "Error makeKeyObject: unable to convert into PublicKey", ex);
+ }
+ return null;
+ }
+
+ /**
+ * Class to get json by making HTTP call
+ */
+ private class GetKeys extends AsyncTask<Void, Void, Void> {
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ progressDialog = new ProgressDialog(TestCertActivity.this);
+ progressDialog.setMessage("Downloading...");
+ progressDialog.setCancelable(false);
+ progressDialog.show();
+ }
+
+ public String getCertificateList() {
+ String response = null;
+ String mURL = mCertUrlView.getText().toString();
+ try {
+ URL url = new URL(mURL);
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestMethod("GET");
+ // read the response
+ InputStream in = new BufferedInputStream(conn.getInputStream());
+ response = convertToString(in);
+ } catch (ProtocolException e) {
+ Log.e(LOG_TAG, "ProtocolException: " + e.getMessage());
+ } catch (MalformedURLException e) {
+ Log.e(LOG_TAG, "MalformedURLException: " + e.getMessage());
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "IOException: " + e.getMessage());
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Exception: " + e.getMessage());
+ }
+ return response;
+ }
+
+ private String convertToString(InputStream is) {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+ StringBuilder sb = new StringBuilder();
+
+ String line;
+ try {
+ while ((line = reader.readLine()) != null) {
+ sb.append(line).append('\n');
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return sb.toString();
+ }
+
+ private void savePublicKey(String key, int type, String identifier) {
+ byte[] keyBytes = Base64.decode(key.getBytes(), Base64.DEFAULT);
+ PublicKey publicKey = makeKeyObject(keyBytes);
+ Log.i(LOG_TAG, "generated public key: " + publicKey);
+ final TelephonyManager telephonyManager =
+ (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+
+ String mcc = "";
+ String mnc = "";
+ String networkOperator = telephonyManager.getNetworkOperator();
+
+ if (!TextUtils.isEmpty(networkOperator)) {
+ mcc = networkOperator.substring(0, 3);
+ mnc = networkOperator.substring(3);
+ Log.i(LOG_TAG, "using values for mnc, mcc: " + mnc + "," + mcc);
+ }
+
+ ImsiEncryptionInfo imsiEncryptionInfo = new ImsiEncryptionInfo(mcc,
+ mnc, type, identifier, publicKey);
+ telephonyManager.setCarrierInfoForImsiEncryption(imsiEncryptionInfo);
+ keyList.add(imsiEncryptionInfo.getKeyType() + "," +
+ imsiEncryptionInfo.getKeyIdentifier());
+ Log.i(LOG_TAG,"calling telephonymanager complete");
+ }
+
+ @Override
+ protected Void doInBackground(Void... arg0) {
+ // Making a request to url and getting response
+ String jsonStr = getCertificateList();
+ Log.d(LOG_TAG, "Response from url: " + jsonStr);
+
+ if (jsonStr != null) {
+ try {
+ JSONObject jsonObj = new JSONObject(jsonStr);
+ // Getting JSON Array node
+ JSONArray certificates = jsonObj.getJSONArray("certificates");
+
+ // looping through the certificates
+ for (int i = 0; i < certificates.length(); i++) {
+ JSONObject cert = certificates.getJSONObject(i);
+ String key = cert.getString("key");
+ int type = cert.getInt("type");
+ String identifier = cert.getString("identifier");
+ savePublicKey(key, type, identifier);
+ }
+ } catch (final JSONException e) {
+ Log.e(LOG_TAG, "Json parsing error: " + e.getMessage());
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(getApplicationContext(),
+ "Json parsing error: " + e.getMessage(),
+ Toast.LENGTH_LONG)
+ .show();
+ }
+ });
+ }
+ } else {
+ Log.e(LOG_TAG, "Unable to get JSON from server " + mURL);
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(getApplicationContext(),
+ "Unable to get JSON from server!",
+ Toast.LENGTH_LONG)
+ .show();
+ }
+ });
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+
+ super.onPostExecute(result);
+ if (progressDialog.isShowing()) {
+ progressDialog.dismiss();
+ }
+ ListView listView = (ListView) findViewById(R.id.keylist);
+ ArrayAdapter arrayAdapter =
+ new ArrayAdapter(TestCertActivity.this, R.layout.key_list, keyList);
+ listView.setAdapter(arrayAdapter);
+ }
+ }
+}
+
+
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index f053a99..e815b5c 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -117,6 +117,10 @@
}
@Override
+ public void onCreateConnectionComplete(Connection connection) {
+ }
+
+ @Override
public void onConference(Connection cxn1, Connection cxn2) {
if (((FakeConnection) cxn1).getIsConferenceCreated()) {
// Usually, this is implemented by something in Telephony, which does a bunch of
@@ -240,6 +244,11 @@
}
@Override
+ public void createConnectionComplete(String id, Session.Info info) throws RemoteException {
+ mConnectionServiceDelegateAdapter.createConnectionComplete(id, null /*Session.Info*/);
+ }
+
+ @Override
public void createConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount,
String callId, ConnectionRequest request, boolean isIncoming,
Session.Info sessionInfo) throws RemoteException {
diff --git a/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java b/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java
index e73df61..82fee3e 100644
--- a/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java
+++ b/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java
@@ -51,6 +51,7 @@
private DefaultDialerCache mDefaultDialerCache;
private ContentObserver mDefaultDialerSettingObserver;
private BroadcastReceiver mPackageChangeReceiver;
+ private BroadcastReceiver mUserRemovedReceiver;
@Mock private DefaultDialerCache.DefaultDialerManagerAdapter mMockDefaultDialerManager;
@@ -58,17 +59,24 @@
super.setUp();
mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
- ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor<BroadcastReceiver> packageReceiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
mDefaultDialerCache = new DefaultDialerCache(
mContext, mMockDefaultDialerManager, new TelecomSystem.SyncRoot() { });
verify(mContext, times(2)).registerReceiverAsUser(
- receiverCaptor.capture(), eq(UserHandle.ALL), any(IntentFilter.class),
+ packageReceiverCaptor.capture(), eq(UserHandle.ALL), any(IntentFilter.class),
isNull(String.class), isNull(Handler.class));
// Receive the first receiver that was captured, the package change receiver.
- mPackageChangeReceiver = receiverCaptor.getAllValues().get(0);
+ mPackageChangeReceiver = packageReceiverCaptor.getAllValues().get(0);
+
+ ArgumentCaptor<BroadcastReceiver> userRemovedReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ verify(mContext).registerReceiver(
+ userRemovedReceiverCaptor.capture(), any(IntentFilter.class));
+ mUserRemovedReceiver = userRemovedReceiverCaptor.getAllValues().get(0);
+
mDefaultDialerSettingObserver = mDefaultDialerCache.getContentObserver();
when(mMockDefaultDialerManager.getDefaultDialerApplication(any(Context.class), eq(USER0)))
@@ -138,6 +146,24 @@
}
@SmallTest
+ public void testUserRemoved() {
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER0), DIALER1);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER1), DIALER2);
+
+ Intent userRemovalIntent = new Intent(Intent.ACTION_USER_REMOVED);
+ userRemovalIntent.putExtra(Intent.EXTRA_USER_HANDLE, USER0);
+ mUserRemovedReceiver.onReceive(mContext, userRemovalIntent);
+
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER0), DIALER1);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER1), DIALER2);
+
+ verify(mMockDefaultDialerManager, times(2))
+ .getDefaultDialerApplication(any(Context.class), eq(USER0));
+ verify(mMockDefaultDialerManager, times(1))
+ .getDefaultDialerApplication(any(Context.class), eq(USER1));
+ }
+
+ @SmallTest
public void testPackageRemovedWithoutReplace() {
assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER0), DIALER1);
assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER1), DIALER2);
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 33315a1..a522cae 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -712,6 +712,10 @@
// Wait for the callback in ConnectionService#onAdapterAttached to execute.
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+ // Ensure callback to CS on successful creation happened.
+ verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
+ .createConnectionComplete(anyString(), any());
+
assertEquals(startingNumCalls + 1, mInCallServiceFixtureX.mCallById.size());
assertEquals(startingNumCalls + 1, mInCallServiceFixtureY.mCallById.size());
@@ -759,6 +763,12 @@
// Wait for the handler to start the CallerInfo lookup
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+
+ // Ensure callback to CS on successful creation happened.
+ verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
+ .createConnectionComplete(anyString(), any());
+
+
// Process the CallerInfo lookup reply
mCallerInfoAsyncQueryFactoryFixture.mRequests.forEach(
CallerInfoAsyncQueryFactoryFixture.Request::reply);