WidgetCell.java revision 2e6da1539bc7286336b3c24d96ab76434939ce4d
129d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung/* 23f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song * Copyright (C) 2015 The Android Open Source Project 329d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung * 429d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung * Licensed under the Apache License, Version 2.0 (the "License"); 529d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung * you may not use this file except in compliance with the License. 629d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung * You may obtain a copy of the License at 729d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung * 829d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung * http://www.apache.org/licenses/LICENSE-2.0 929d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung * 1029d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung * Unless required by applicable law or agreed to in writing, software 1129d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung * distributed under the License is distributed on an "AS IS" BASIS, 1229d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1329d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung * See the License for the specific language governing permissions and 1429d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung * limitations under the License. 1529d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung */ 1629d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung 173f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Songpackage com.android.launcher3.widget; 1829d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung 1929d6fea296ebecb607525c8245a54696ad7c5db7Winson Chungimport android.content.Context; 2029d6fea296ebecb607525c8245a54696ad7c5db7Winson Chungimport android.content.pm.PackageManager; 2129d6fea296ebecb607525c8245a54696ad7c5db7Winson Chungimport android.content.pm.ResolveInfo; 2259e1f9a07eef87b1d287956d21b8d9c5b27faf9cWinson Chungimport android.content.res.Resources; 235b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyalimport android.graphics.Bitmap; 2429d6fea296ebecb607525c8245a54696ad7c5db7Winson Chungimport android.util.AttributeSet; 253f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Songimport android.util.Log; 26ed66b2bac7447febe2e405b4ce725cae4f6b5988Adam Cohenimport android.view.View; 275b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyalimport android.view.View.OnLayoutChangeListener; 2829d6fea296ebecb607525c8245a54696ad7c5db7Winson Chungimport android.widget.LinearLayout; 2929d6fea296ebecb607525c8245a54696ad7c5db7Winson Chungimport android.widget.TextView; 3029d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung 313f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Songimport com.android.launcher3.DeviceProfile; 322e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohenimport com.android.launcher3.InvariantDeviceProfile; 333f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Songimport com.android.launcher3.ItemInfo; 342e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohenimport com.android.launcher3.Launcher; 353f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Songimport com.android.launcher3.LauncherAppState; 363f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Songimport com.android.launcher3.LauncherAppWidgetProviderInfo; 373f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Songimport com.android.launcher3.R; 383f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Songimport com.android.launcher3.WidgetPreviewLoader; 395b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyalimport com.android.launcher3.WidgetPreviewLoader.PreviewLoadRequest; 40ffe83f13319cdb833a25596d47cf1af098cc4c60Sunny Goyalimport com.android.launcher3.compat.AppWidgetManagerCompat; 41ffe83f13319cdb833a25596d47cf1af098cc4c60Sunny Goyal 4229d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung/** 43f52abd7758b8f245522936e941336e574debabf5Sunny Goyal * Represents the individual cell of the widget inside the widget tray. The preview is drawn 44f52abd7758b8f245522936e941336e574debabf5Sunny Goyal * horizontally centered, and scaled down if needed. 45f52abd7758b8f245522936e941336e574debabf5Sunny Goyal * 46f52abd7758b8f245522936e941336e574debabf5Sunny Goyal * This view does not support padding. Since the image is scaled down to fit the view, padding will 47f52abd7758b8f245522936e941336e574debabf5Sunny Goyal * further decrease the scaling factor. Drag-n-drop uses the view bounds for showing a smooth 48f52abd7758b8f245522936e941336e574debabf5Sunny Goyal * transition from the view to drag view, so when adding padding support, DnD would need to 49f52abd7758b8f245522936e941336e574debabf5Sunny Goyal * consider the appropriate scaling factor. 5029d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung */ 513f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Songpublic class WidgetCell extends LinearLayout implements OnLayoutChangeListener { 5229d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung 534e8fb91cf19b7d621de8cbed2bde2c8dac734121Hyunyoung Song private static final String TAG = "WidgetCell"; 543f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song private static final boolean DEBUG = false; 553f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song 56f17a1c9ad99d46a65de797422fd439382114b348Hyunyoung Song private static final int FADE_IN_DURATION_MS = 90; 574b69f2ca91d63a59dfa921ba965b92de7f604f5aHyunyoung Song 584b69f2ca91d63a59dfa921ba965b92de7f604f5aHyunyoung Song /** Widget cell width is calculated by multiplying this factor to grid cell width. */ 595cd1d92f6c40b47eff12859d4e13b6431aa778c0Hyunyoung Song private static final float WIDTH_SCALE = 2.6f; 604b69f2ca91d63a59dfa921ba965b92de7f604f5aHyunyoung Song 614b69f2ca91d63a59dfa921ba965b92de7f604f5aHyunyoung Song /** Widget preview width is calculated by multiplying this factor to the widget cell width. */ 625cd1d92f6c40b47eff12859d4e13b6431aa778c0Hyunyoung Song private static final float PREVIEW_SCALE = 0.8f; 634b69f2ca91d63a59dfa921ba965b92de7f604f5aHyunyoung Song 645cd1d92f6c40b47eff12859d4e13b6431aa778c0Hyunyoung Song private int mPresetPreviewSize; 655cd1d92f6c40b47eff12859d4e13b6431aa778c0Hyunyoung Song int cellSize; 663f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song 67f52abd7758b8f245522936e941336e574debabf5Sunny Goyal private WidgetImageView mWidgetImage; 68b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song private TextView mWidgetName; 69b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song private TextView mWidgetDims; 705b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal 715b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal private String mDimensionsFormatString; 7205713af127d765cc28a8b2fd548a90347c90d6cbMichael Jurka private Object mInfo; 735b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal 743f4e070aa58d51dd136885b4d3e2e6c5d9f93ea0Michael Jurka private WidgetPreviewLoader mWidgetPreviewLoader; 755b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal private PreviewLoadRequest mActiveRequest; 760499834db3f9dc6fb0f5f57b5876b8503bce5189Winson Chung 772e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen private Launcher mLauncher; 782e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen 793f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song public WidgetCell(Context context) { 8029d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung this(context, null); 8129d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung } 8229d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung 833f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song public WidgetCell(Context context, AttributeSet attrs) { 8429d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung this(context, attrs, 0); 8529d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung } 8629d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung 873f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song public WidgetCell(Context context, AttributeSet attrs, int defStyle) { 8829d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung super(context, attrs, defStyle); 8929d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung 9059e1f9a07eef87b1d287956d21b8d9c5b27faf9cWinson Chung final Resources r = context.getResources(); 912e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen mLauncher = (Launcher) context; 9259e1f9a07eef87b1d287956d21b8d9c5b27faf9cWinson Chung 932e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen mDimensionsFormatString = r.getString(R.string.widget_dims_format); 944b69f2ca91d63a59dfa921ba965b92de7f604f5aHyunyoung Song setContainerWidth(); 9529d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung setWillNotDraw(false); 9629d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung setClipToPadding(false); 9771b5c0b988a64b3a0613ded5403749bc537ee8a5Sunny Goyal setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate()); 984b69f2ca91d63a59dfa921ba965b92de7f604f5aHyunyoung Song } 993f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song 1004b69f2ca91d63a59dfa921ba965b92de7f604f5aHyunyoung Song private void setContainerWidth() { 1012e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen DeviceProfile profile = mLauncher.getDeviceProfile(); 1025cd1d92f6c40b47eff12859d4e13b6431aa778c0Hyunyoung Song cellSize = (int) (profile.cellWidthPx * WIDTH_SCALE); 1032e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen mPresetPreviewSize = (int) (cellSize * PREVIEW_SCALE); 10429d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung } 10529d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung 106c93e5ae12018bb214099ff88a48cc21580aec4c3Winson Chung @Override 107c93e5ae12018bb214099ff88a48cc21580aec4c3Winson Chung protected void onFinishInflate() { 108c93e5ae12018bb214099ff88a48cc21580aec4c3Winson Chung super.onFinishInflate(); 109c93e5ae12018bb214099ff88a48cc21580aec4c3Winson Chung 110f52abd7758b8f245522936e941336e574debabf5Sunny Goyal mWidgetImage = (WidgetImageView) findViewById(R.id.widget_preview); 111b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song mWidgetName = ((TextView) findViewById(R.id.widget_name)); 112b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song mWidgetDims = ((TextView) findViewById(R.id.widget_dims)); 1134e8fb91cf19b7d621de8cbed2bde2c8dac734121Hyunyoung Song } 1144e8fb91cf19b7d621de8cbed2bde2c8dac734121Hyunyoung Song 115559d90d0dafbac1d97a1e6f18062309831a25d51Hyunyoung Song /** 116559d90d0dafbac1d97a1e6f18062309831a25d51Hyunyoung Song * Called to clear the view and free attached resources. (e.g., {@link Bitmap} 117559d90d0dafbac1d97a1e6f18062309831a25d51Hyunyoung Song */ 118559d90d0dafbac1d97a1e6f18062309831a25d51Hyunyoung Song public void clear() { 119559d90d0dafbac1d97a1e6f18062309831a25d51Hyunyoung Song if (DEBUG) { 120559d90d0dafbac1d97a1e6f18062309831a25d51Hyunyoung Song Log.d(TAG, "reset called on:" + mWidgetName.getText()); 121559d90d0dafbac1d97a1e6f18062309831a25d51Hyunyoung Song } 1222d58471e8d3da621e209cec58a43d159778f8a0eWinson Chung mWidgetImage.animate().cancel(); 123f52abd7758b8f245522936e941336e574debabf5Sunny Goyal mWidgetImage.setBitmap(null); 124b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song mWidgetName.setText(null); 125b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song mWidgetDims.setText(null); 126f17a1c9ad99d46a65de797422fd439382114b348Hyunyoung Song 127559d90d0dafbac1d97a1e6f18062309831a25d51Hyunyoung Song if (mActiveRequest != null) { 128559d90d0dafbac1d97a1e6f18062309831a25d51Hyunyoung Song mActiveRequest.cleanup(); 129559d90d0dafbac1d97a1e6f18062309831a25d51Hyunyoung Song mActiveRequest = null; 130559d90d0dafbac1d97a1e6f18062309831a25d51Hyunyoung Song } 131099459377a737d885bbd8ac4d52e6884a103b1c7Winson Chung } 132099459377a737d885bbd8ac4d52e6884a103b1c7Winson Chung 133b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song /** 134b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song * Apply the widget provider info to the view. 135b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song */ 1365940042d39b576553c2499bcf3d0641281e6ad52Adam Cohen public void applyFromAppWidgetProviderInfo(LauncherAppWidgetProviderInfo info, 137f52abd7758b8f245522936e941336e574debabf5Sunny Goyal WidgetPreviewLoader loader) { 138892c74d460ad98c6306420e1127c9aa3e505ba25Winson Chung 1392e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen InvariantDeviceProfile profile = 1402e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen LauncherAppState.getInstance().getInvariantDeviceProfile(); 14105713af127d765cc28a8b2fd548a90347c90d6cbMichael Jurka mInfo = info; 142b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song // TODO(hyunyoungs): setup a cache for these labels. 143b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song mWidgetName.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info)); 1442e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen int hSpan = Math.min(info.getSpanX(mLauncher), profile.numColumns); 1452e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen int vSpan = Math.min(info.getSpanY(mLauncher), profile.numRows); 146b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song mWidgetDims.setText(String.format(mDimensionsFormatString, hSpan, vSpan)); 1473f4e070aa58d51dd136885b4d3e2e6c5d9f93ea0Michael Jurka mWidgetPreviewLoader = loader; 14829d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung } 14929d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung 150b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song /** 151b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song * Apply the resolve info to the view. 152b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song */ 1533f4e070aa58d51dd136885b4d3e2e6c5d9f93ea0Michael Jurka public void applyFromResolveInfo( 1543f4e070aa58d51dd136885b4d3e2e6c5d9f93ea0Michael Jurka PackageManager pm, ResolveInfo info, WidgetPreviewLoader loader) { 15505713af127d765cc28a8b2fd548a90347c90d6cbMichael Jurka mInfo = info; 1566a0f57dfafced837a2a282d8feec28d5418be3b9Winson Chung CharSequence label = info.loadLabel(pm); 157b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song mWidgetName.setText(label); 158b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song mWidgetDims.setText(String.format(mDimensionsFormatString, 1, 1)); 1593f4e070aa58d51dd136885b4d3e2e6c5d9f93ea0Michael Jurka mWidgetPreviewLoader = loader; 160b44b52439d155f570db7d6d0b80fdd3350e35685Winson Chung } 1610499834db3f9dc6fb0f5f57b5876b8503bce5189Winson Chung 162038f9d8bfb53288e7cf5812f62ec3d5b25fec965Michael Jurka public int[] getPreviewSize() { 163038f9d8bfb53288e7cf5812f62ec3d5b25fec965Michael Jurka int[] maxSize = new int[2]; 1646babf2e27e6260f02751413b8f7a55e46659af27Hyunyoung Song 1653f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song maxSize[0] = mPresetPreviewSize; 1663f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song maxSize[1] = mPresetPreviewSize; 167038f9d8bfb53288e7cf5812f62ec3d5b25fec965Michael Jurka return maxSize; 168038f9d8bfb53288e7cf5812f62ec3d5b25fec965Michael Jurka } 169038f9d8bfb53288e7cf5812f62ec3d5b25fec965Michael Jurka 1705b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal public void applyPreview(Bitmap bitmap) { 171f52abd7758b8f245522936e941336e574debabf5Sunny Goyal if (bitmap != null) { 172f52abd7758b8f245522936e941336e574debabf5Sunny Goyal mWidgetImage.setBitmap(bitmap); 173b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song mWidgetImage.setAlpha(0f); 174b99ff3e83270e113f6182e337c4f7b0223bad92bHyunyoung Song mWidgetImage.animate().alpha(1.0f).setDuration(FADE_IN_DURATION_MS); 175ed66b2bac7447febe2e405b4ce725cae4f6b5988Adam Cohen } 176ed66b2bac7447febe2e405b4ce725cae4f6b5988Adam Cohen } 177ed66b2bac7447febe2e405b4ce725cae4f6b5988Adam Cohen 1785b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal public void ensurePreview() { 1795b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal if (mActiveRequest != null) { 1805b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal return; 1815b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal } 1825b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal int[] size = getPreviewSize(); 1833f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song if (DEBUG) { 1843f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song Log.d(TAG, String.format("[tag=%s] ensurePreview (%d, %d):", 1853f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song getTagToString(), size[0], size[1])); 1863f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song } 18778b19667dad912ee6aadb9699c21e33c39ec550fWinson Chung mActiveRequest = mWidgetPreviewLoader.getPreview(mInfo, size[0], size[1], this); 1885b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal } 1895b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal 1905b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal @Override 1915b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 1925b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal int oldTop, int oldRight, int oldBottom) { 1935b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal removeOnLayoutChangeListener(this); 1945b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal ensurePreview(); 1955b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal } 1965b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal 1975b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal public int getActualItemWidth() { 1985b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal ItemInfo info = (ItemInfo) getTag(); 1995b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal int[] size = getPreviewSize(); 2002e6da1539bc7286336b3c24d96ab76434939ce4dAdam Cohen int cellWidth = mLauncher.getDeviceProfile().cellWidthPx; 2015b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal 2025b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal return Math.min(size[0], info.spanX * cellWidth); 2035b0e669169ea2c951bf2f6f71faf793b24db3c23Sunny Goyal } 2043f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song 2053f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song /** 2063f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song * Helper method to get the string info of the tag. 2073f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song */ 2083f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song private String getTagToString() { 20982b016cb56540fe26213e817dd0dd668099c8e20Winson Chung if (getTag() instanceof PendingAddWidgetInfo || 21082b016cb56540fe26213e817dd0dd668099c8e20Winson Chung getTag() instanceof PendingAddShortcutInfo) { 21182b016cb56540fe26213e817dd0dd668099c8e20Winson Chung return getTag().toString(); 2123f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song } 2133f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song return ""; 2143f471440a8b6b71d4c15501a96befd3b715c9e8fHyunyoung Song } 21529d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung} 216