diff options
Diffstat (limited to 'java/com/android/incallui/answer/impl/hint/EventPayloadLoaderImpl.java')
-rw-r--r-- | java/com/android/incallui/answer/impl/hint/EventPayloadLoaderImpl.java | 118 |
1 files changed, 118 insertions, 0 deletions
diff --git a/java/com/android/incallui/answer/impl/hint/EventPayloadLoaderImpl.java b/java/com/android/incallui/answer/impl/hint/EventPayloadLoaderImpl.java new file mode 100644 index 000000000..bd8d73645 --- /dev/null +++ b/java/com/android/incallui/answer/impl/hint/EventPayloadLoaderImpl.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2016 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.incallui.answer.impl.hint; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build.VERSION_CODES; +import android.preference.PreferenceManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import com.android.dialer.common.Assert; +import com.android.dialer.common.ConfigProvider; +import com.android.dialer.common.ConfigProviderBindings; +import com.android.dialer.common.LogUtil; +import java.io.InputStream; +import java.util.TimeZone; +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; + +/** Decrypt the event payload to be shown if in a specific time range and the key is received. */ +@TargetApi(VERSION_CODES.M) +public final class EventPayloadLoaderImpl implements EventPayloadLoader { + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + static final String CONFIG_EVENT_KEY = "event_key"; + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + static final String CONFIG_EVENT_BINARY = "event_binary"; + + // Time is stored as a UTC UNIX timestamp in milliseconds, but interpreted as local time. + // For example, 946684800 (2000/1/1 00:00:00 @UTC) is the new year midnight at every timezone. + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + static final String CONFIG_EVENT_START_UTC_AS_LOCAL_MILLIS = "event_time_start_millis"; + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + static final String CONFIG_EVENT_TIME_END_UTC_AS_LOCAL_MILLIS = "event_time_end_millis"; + + @Override + @Nullable + public Drawable loadPayload( + @NonNull Context context, long currentTimeUtcMillis, @NonNull TimeZone timeZone) { + Assert.isNotNull(context); + Assert.isNotNull(timeZone); + ConfigProvider configProvider = ConfigProviderBindings.get(context); + + String pbeKey = configProvider.getString(CONFIG_EVENT_KEY, null); + if (pbeKey == null) { + return null; + } + long timeRangeStart = configProvider.getLong(CONFIG_EVENT_START_UTC_AS_LOCAL_MILLIS, 0); + long timeRangeEnd = configProvider.getLong(CONFIG_EVENT_TIME_END_UTC_AS_LOCAL_MILLIS, 0); + + String eventBinary = configProvider.getString(CONFIG_EVENT_BINARY, null); + if (eventBinary == null) { + return null; + } + + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + if (!preferences.getBoolean( + EventSecretCodeListener.EVENT_ENABLED_WITH_SECRET_CODE_KEY, false)) { + long localTimestamp = currentTimeUtcMillis + timeZone.getRawOffset(); + + if (localTimestamp < timeRangeStart) { + return null; + } + + if (localTimestamp > timeRangeEnd) { + return null; + } + } + + // Use openssl aes-128-cbc -in <input> -out <output> -pass <PBEKey> to generate the asset + try (InputStream input = context.getAssets().open(eventBinary)) { + byte[] encryptedFile = new byte[input.available()]; + input.read(encryptedFile); + + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC"); + + byte[] salt = new byte[8]; + System.arraycopy(encryptedFile, 8, salt, 0, 8); + SecretKey key = + SecretKeyFactory.getInstance("PBEWITHMD5AND128BITAES-CBC-OPENSSL", "BC") + .generateSecret(new PBEKeySpec(pbeKey.toCharArray(), salt, 100)); + cipher.init(Cipher.DECRYPT_MODE, key); + + byte[] decryptedFile = cipher.doFinal(encryptedFile, 16, encryptedFile.length - 16); + + return new BitmapDrawable( + context.getResources(), + BitmapFactory.decodeByteArray(decryptedFile, 0, decryptedFile.length)); + } catch (Exception e) { + // Avoid crashing dialer for any reason. + LogUtil.e("EventPayloadLoader.loadPayload", "error decrypting payload:", e); + return null; + } + } +} |