

<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Google &#8211; Max的程式語言筆記</title>
	<atom:link href="https://stackoverflow.max-everyday.com/tag/google/feed/" rel="self" type="application/rss+xml" />
	<link>https://stackoverflow.max-everyday.com</link>
	<description>我要當一個豬頭，快樂過每一天</description>
	<lastBuildDate>Fri, 21 Nov 2025 11:02:29 +0000</lastBuildDate>
	<language>zh-TW</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.1</generator>

<image>
	<url>https://stackoverflow.max-everyday.com/wp-content/uploads/2017/02/max-stackoverflow-256.png</url>
	<title>Google &#8211; Max的程式語言筆記</title>
	<link>https://stackoverflow.max-everyday.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>如何讓 google slide 裡的圖片產生圓角效果, 並加入指定顏色的框線</title>
		<link>https://stackoverflow.max-everyday.com/2025/11/google-slide-image-border/</link>
					<comments>https://stackoverflow.max-everyday.com/2025/11/google-slide-image-border/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Fri, 21 Nov 2025 11:02:24 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<category><![CDATA[Google]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=7228</guid>

					<description><![CDATA[在 Google Slides (簡報) 中，無...]]></description>
										<content:encoded><![CDATA[
<p>在 Google Slides (簡報) 中，無法直接像 CSS 那樣設定「圓角半徑」，但你可以透過「將圖片裁剪為圖形」的功能來達成這個效果。</p>



<p>以下是詳細的操作步驟：</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">第一階段：製作圓角效果 (圖片遮罩)</h3>



<ol start="1" class="wp-block-list">
<li><strong>點選圖片</strong>：按一下你要編輯的圖片。</li>



<li><strong>開啟裁剪選單</strong>：在工具列上找到「裁剪圖片」圖示旁邊的小箭頭 <strong>▼</strong> (滑鼠移上去會顯示「遮罩圖片」)。</li>



<li><strong>選擇圓角矩形</strong>：
<ul class="wp-block-list">
<li>從選單中選擇 <strong>圖形 (Shapes)</strong>。</li>



<li>選擇 <strong>圓角矩形</strong> (通常在第二個位置)。</li>
</ul>
</li>



<li><strong>調整圓角弧度 (重要)</strong>：
<ul class="wp-block-list">
<li>圖片現在變成了圓角。你會看到圖片邊緣有一個小小的 <strong>黃色菱形 ◇ 控制點</strong> (通常在左上角)。</li>



<li>拖曳這個黃色菱形，可以調整圓角的弧度大小 (越往中間拉越圓)。</li>
</ul>
</li>
</ol>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="593" height="622" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2025/11/2025-11-21_18-36_ex.jpg?v=1763722268" alt="" class="wp-image-7229" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2025/11/2025-11-21_18-36_ex.jpg?v=1763722268 593w, https://stackoverflow.max-everyday.com/wp-content/uploads/2025/11/2025-11-21_18-36_ex-572x600.jpg?v=1763722268 572w" sizes="(max-width: 593px) 100vw, 593px" /></figure>



<p>說明: 左上角有黃色的小點, 用來調整圓角長度.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">第二階段：加入 #FA7070 顏色外框</h3>



<ol start="1" class="wp-block-list">
<li><strong>開啟框線顏色</strong>：
<ul class="wp-block-list">
<li>保持圖片選取狀態，點擊工具列上的 <strong>框線顏色</strong> 圖示 (看起來像一支筆 )。</li>
</ul>
</li>



<li><strong>新增自訂顏色</strong>：
<ul class="wp-block-list">
<li>在顏色選單中，點擊 <strong>「+」</strong> 號 (在「自訂」標籤下方)。</li>
</ul>
</li>



<li><strong>輸入色碼</strong>：
<ul class="wp-block-list">
<li>在跳出的視窗上方欄位，輸入色碼 <code>FA7070</code>。</li>



<li>按下 <strong>確定</strong>。</li>
</ul>
</li>



<li><strong>設定框線粗細</strong>：
<ul class="wp-block-list">
<li>此時框線可能太細看不清楚。點擊工具列上的 <strong>框線粗細</strong> 圖示 (三條線的圖示)。</li>



<li>建議選擇 <strong>3px</strong> 或 <strong>4px</strong> 以上，這樣你的 <code>#FA7070</code> 顏色才會明顯。</li>
</ul>
</li>
</ol>



<figure class="wp-block-image size-full"><img decoding="async" width="642" height="752" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2025/11/2025-11-21_18-46_ez.jpg?v=1763722889" alt="" class="wp-image-7230" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2025/11/2025-11-21_18-46_ez.jpg?v=1763722889 642w, https://stackoverflow.max-everyday.com/wp-content/uploads/2025/11/2025-11-21_18-46_ez-512x600.jpg?v=1763722889 512w" sizes="(max-width: 642px) 100vw, 642px" /></figure>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>快速套用格式：</p>



<p>如果你有多張圖片都要做一樣的處理，設定好第一張後，可以使用 複製格式 (Paint Format) 工具 (像油漆滾筒的圖示)，點一下第一張圖，再點第二張圖，就能瞬間套用圓角和粉紅框線了。</p>
</blockquote>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2025/11/google-slide-image-border/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>App 違反不當廣告政策</title>
		<link>https://stackoverflow.max-everyday.com/2018/06/max_ad_content_rating/</link>
					<comments>https://stackoverflow.max-everyday.com/2018/06/max_ad_content_rating/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 05 Jun 2018 15:36:56 +0000</pubDate>
				<category><![CDATA[Android筆記]]></category>
		<category><![CDATA[iOS筆記]]></category>
		<category><![CDATA[admob]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[iOS]]></category>
		<guid isPermaLink="false">http://stackoverflow.max-everyday.com/?p=2408</guid>

					<description><![CDATA[上架App 被退，原因是： 問題：違反不當廣告政...]]></description>
										<content:encoded><![CDATA[<p>上架App 被退，原因是：</p>
<blockquote><p><b>問題：違反不當廣告政策</b></p>
<p>您的應用程式內顯示的廣告除了必須符合我們的政策規定，<wbr />還必須針對目標對象提供適當內容。舉例來說，<wbr />如果應用程式的內容分級適用對象為未成年人，<wbr />則不可放送含有兒童不宜內容或服務的廣告。</p></blockquote>
<hr />
<p><img decoding="async" class="alignnone size-full wp-image-2409" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2018/06/Screenshot-2018-06-05-23.03.19.jpg" alt="" width="1024" height="406" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2018/06/Screenshot-2018-06-05-23.03.19.jpg 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2018/06/Screenshot-2018-06-05-23.03.19-600x238.jpg 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2018/06/Screenshot-2018-06-05-23.03.19-768x305.jpg 768w" sizes="(max-width: 1024px) 100vw, 1024px" /></p>
<hr />
<p>解法：</p>
<p><a href="https://support.google.com/admob/answer/7562142?hl=zh-Hant&amp;ref_topic=7384665">https://support.google.com/admob/answer/7562142?hl=zh-Hant&amp;ref_topic=7384665</a></p>
<h2>數位內容標籤</h2>
<p>AdMob 會使用數位內容標籤，就廣告對不同目標對象的適合程度進行分級。這類標籤類似於其他機構組織為電影或電玩遊戲所設定的內容分級。</p>
<p>數位內容標籤依定義分為下列幾種：</p>
<ul class="spaced-list">
<li><strong>G</strong>：內容適合一般觀眾 (包括闔家皆宜的內容)</li>
<li><strong>PG</strong>：內容適合大多數的觀眾 (需有家長監護)</li>
<li><strong>T</strong>：內容適合青少年及較年長的觀眾</li>
<li><strong>MA</strong>：內容只適合成年觀眾</li>
</ul>
<p>Google Play 和 Apple App Store 各有專屬的分級和標籤套用方式，用以判定內容對不同年齡層的適合程度。請參閱下表，瞭解您應用程式目標對象所對應的數位內容標籤。</p>
<table class="borders spaced-table">
<thead>
<tr>
<th>數位內容標籤</th>
<th>Google Play (Android)</th>
<th>App Store (iOS)</th>
</tr>
</thead>
<tbody>
<tr>
<td class="align-center">G</td>
<td class="align-center">3+</td>
<td class="align-center">4+</td>
</tr>
<tr>
<td class="align-center">PG</td>
<td class="align-center">7+</td>
<td class="align-center">9+</td>
</tr>
<tr>
<td class="align-center">T</td>
<td class="align-center">12+</td>
<td class="align-center">12+</td>
</tr>
<tr>
<td class="align-center">MA</td>
<td class="align-center">16+、18+</td>
<td class="align-center">17+</td>
</tr>
</tbody>
</table>
<hr />
<p>程式：<br />
provide targeting information to an ad request.</p>
<p><a href="https://developers.google.com/admob/android/targeting#child-directed_setting">https://developers.google.com/admob/android/targeting#child-directed_setting</a></p>
<blockquote>
<pre class="prettyprint"><span class="typ">Bundle</span><span class="pln"> extras </span><span class="pun">=</span> <span class="kwd">new</span> <span class="typ">Bundle</span><span class="pun">();</span><span class="pln">
extras</span><span class="pun">.</span><span class="pln">putString</span><span class="pun">(</span><span class="str">"max_ad_content_rating"</span><span class="pun">,</span> <span class="str">"MA"</span><span class="pun">);</span>

<span class="typ">AdRequest</span><span class="pln"> request </span><span class="pun">=</span> <span class="kwd">new</span> <span class="typ">AdRequest</span><span class="pun">.</span><span class="typ">Builder</span><span class="pun">()</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">addNetworkExtrasBundle</span><span class="pun">(</span><span class="typ">AdMobAdapter</span><span class="pun">.</span><span class="kwd">class</span><span class="pun">,</span><span class="pln"> extras</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">build</span><span class="pun">();</span></pre>
<p>&nbsp;</p></blockquote>
<hr />
<p>完整範例：<br />
<a href="https://github.com/googleads/googleads-mobile-android-examples/blob/master/java/advanced/APIDemo/app/src/main/java/com/google/android/gms/example/apidemo/AdMobAdTargetingFragment.java">https://github.com/googleads/googleads-mobile-android-examples/blob/master/java/advanced/APIDemo/app/src/main/java/com/google/android/gms/example/apidemo/AdMobAdTargetingFragment.java</a></p>
<p>&nbsp;</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2018/06/max_ad_content_rating/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AdMob EU Consent Error: consent form can be used with custom provider selection only</title>
		<link>https://stackoverflow.max-everyday.com/2018/05/admob-eu-consent-error-consent-form-can-be-used-with-custom-provider-selection-only/</link>
					<comments>https://stackoverflow.max-everyday.com/2018/05/admob-eu-consent-error-consent-form-can-be-used-with-custom-provider-selection-only/#comments</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 29 May 2018 09:38:58 +0000</pubDate>
				<category><![CDATA[Android筆記]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[Debug]]></category>
		<category><![CDATA[Google]]></category>
		<guid isPermaLink="false">http://stackoverflow.max-everyday.com/?p=2384</guid>

					<description><![CDATA[I want to update my app ...]]></description>
										<content:encoded><![CDATA[<p>I want to update my app due to Google EU User Consent Policy (<a href="https://developers.google.com/mobile-ads-sdk/docs/dfp/android/eu-consent">https://developers.google.com/mobile-ads-sdk/docs/dfp/android/eu-consent</a>). 所以增加了 Google Consent SDK for GDPR 到 android studio project，</p>
<p>App執行時遇到 Error:</p>
<blockquote>
<pre>consent form can be used with custom provider selection only.</pre>
</blockquote>
<p>或 Error:</p>
<blockquote>
<pre>consent form can be used with custom provider selection only.</pre>
</blockquote>
<p>這 2個都是因為選擇了 &#8220;Commonly used set of ad technology providers&#8221;.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-2385" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2018/05/AREpX.png" alt="" width="959" height="347" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2018/05/AREpX.png 959w, https://stackoverflow.max-everyday.com/wp-content/uploads/2018/05/AREpX-600x217.png 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2018/05/AREpX-768x278.png 768w" sizes="(max-width: 959px) 100vw, 959px" /></p>
<p>According to documentation on Google Developers site:</p>
<blockquote><p>Important: The Google-rendered consent form is not supported if any of your publisher IDs use the commonly used set of ad technology providers. Attempting to load the Google-rendered consent form will always fail in this case.</p></blockquote>
<p>結論：the only case you can make use of Google-rendered form is use &#8220;Custom set of ad technology providers&#8221;.</p>
<hr />
<p>解法 1號，選成 Custom 去挑 12個就可以了。</p>
<p>解法 2號，使用自己畫的 dialog, 這個解決會比較好，比較簡單，也可以解決  Consent SDK 翻譯的問題：</p>
<p>The <a href="https://developers.google.com/admob/android/eu-consent" rel="nofollow noreferrer">Consent SDK</a> allows to show a consent form which, however, it is currently in English only (version 1.0.3 of the SDK). SDK page says:</p>
<blockquote><p>To update consent text of the Google-rendered consent form, modify the consentform.html file included in the Consent SDK as required.</p></blockquote>
<hr />
<p>解法 2號 URL:</p>
<p><a href="https://stackoverflow.com/questions/50540538/gdpr-consent-sdk-consent-form-translation/50556255#50556255">https://stackoverflow.com/questions/50540538/gdpr-consent-sdk-consent-form-translation/50556255#50556255</a></p>
<p>解法 2號執行畫面：</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-2386" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2018/05/enQlS.png" alt="" width="1080" height="1920" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2018/05/enQlS.png 1080w, https://stackoverflow.max-everyday.com/wp-content/uploads/2018/05/enQlS-338x600.png 338w, https://stackoverflow.max-everyday.com/wp-content/uploads/2018/05/enQlS-768x1365.png 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2018/05/enQlS-576x1024.png 576w" sizes="(max-width: 1080px) 100vw, 1080px" /></p>
<p>&nbsp;</p>
<p>原作者表示，可以使用他提供的 source code:</p>
<p><strong>You are free to use my code, however consult your legal advisor, if the text is appropriate for you. I cannot provide legal advice on the consent text that is appropriate for you.</strong></p>
<p>Add to your gradle file:</p>
<pre><code>implementation 'com.google.android.ads.consent:consent-library:1.0.3'
</code></pre>
<p>Add member variables:</p>
<pre><code>public boolean mShowNonPersonalizedAdRequests = false;
private AlertDialog mEuDialog;
</code></pre>
<p>In <code>onCreate()</code> call <code>checkConsentStatus()</code>:</p>
<pre><code>@Override
protected void onCreate(Bundle savedInstanceState) {
    // ...
    checkConsentStatus();
    // ...   
}
</code></pre>
<p>Add <code>checkConsentStatus()</code> method which uses Google&#8217;s Consent SDK:</p>
<pre><code>// https://developers.google.com/admob/android/eu-consent
private void checkConsentStatus(){
    ConsentInformation consentInformation = ConsentInformation.getInstance(this);
    ConsentInformation.getInstance(this).addTestDevice("YOUR-DEVICE-ID"); // enter your device id, if you need it for testing

    String[] publisherIds = {"pub-YOUR-ADMOB-PUB-ID"}; // enter your admob pub-id
    consentInformation.requestConsentInfoUpdate(publisherIds, new ConsentInfoUpdateListener() {
        @Override
        public void onConsentInfoUpdated(ConsentStatus consentStatus) {
            log("User's consent status successfully updated: " +consentStatus);

            if (ConsentInformation.getInstance(MainActivity.this).isRequestLocationInEeaOrUnknown()){
                log("User is from EU");

                /////////////////////////////
                // TESTING - reset the choice
                //ConsentInformation.getInstance(MainActivity.this).setConsentStatus(ConsentStatus.UNKNOWN);
                /////////////////////////////

                // If the returned ConsentStatus is UNKNOWN, collect user's consent.
                if (consentStatus == ConsentStatus.UNKNOWN) {
                    showMyConsentDialog(false);
                }

                // If the returned ConsentStatus is PERSONALIZED or NON_PERSONALIZED
                // the user has already provided consent. Forward consent to the Google Mobile Ads SDK.
                else if (consentStatus == ConsentStatus.NON_PERSONALIZED) {

                    mShowNonPersonalizedAdRequests = true;

                    // The default behavior of the Google Mobile Ads SDK is to serve personalized ads.
                    // If a user has consented to receive only non-personalized ads, you can configure
                    // an AdRequest object with the following code to specify that only non-personalized
                    // ads should be returned.

                }


            } else {
                log("User is NOT from EU");
                // we don't have to do anything
            }

        }

        @Override
        public void onFailedToUpdateConsentInfo(String errorDescription) {
            log("User's consent status failed to update: " +errorDescription);
        }
    });
}
</code></pre>
<p>Add <code>showMyConsentDialog()</code> method:</p>
<pre><code>public void showMyConsentDialog(boolean showCancel) {

    AlertDialog.Builder alertDialog = new AlertDialog.Builder(MainActivity.this, R.style.MyAlertDialogStyle);
    LayoutInflater inflater = getLayoutInflater();
    View eu_consent_dialog = inflater.inflate(R.layout.eu_consent, null);

    alertDialog.setView(eu_consent_dialog)
               .setCancelable(false);

    if (showCancel) alertDialog.setPositiveButton(R.string.dialog_close, null);

    mEuDialog = alertDialog.create();
    mEuDialog.show();

    Button btn_eu_consent_yes = eu_consent_dialog.findViewById(R.id.btn_eu_consent_yes);
    Button btn_eu_consent_no = eu_consent_dialog.findViewById(R.id.btn_eu_consent_no);
    Button btn_eu_consent_remove_ads = eu_consent_dialog.findViewById(R.id.btn_eu_consent_remove_ads);
    btn_eu_consent_yes.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mEuDialog.cancel();
            toast(getString(R.string.thank_you), MainActivity.this);
            ConsentInformation.getInstance(MainActivity.this).setConsentStatus(ConsentStatus.PERSONALIZED);
            mShowNonPersonalizedAdRequests = false;
        }
    });
    btn_eu_consent_no.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mEuDialog.cancel();
            toast(getString(R.string.thank_you), MainActivity.this);
            ConsentInformation.getInstance(MainActivity.this).setConsentStatus(ConsentStatus.NON_PERSONALIZED);
            mShowNonPersonalizedAdRequests = true;
        }
    });
    btn_eu_consent_remove_ads.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mEuDialog.cancel();
            IAP_buyAdsFree(); // YOUR REMOVE ADS METHOD
            }
        });

    TextView tv_eu_learn_more = eu_consent_dialog.findViewById(R.id.tv_eu_learn_more);
    tv_eu_learn_more.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            euMoreInfoDialog();
        }
    });  
}
</code></pre>
<p>This is the consent layout, save to <code>eu_consent.xml</code>:</p>
<pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    &gt;

    &lt;LinearLayout
        android:id="@+id/ll_eu_consent"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="@dimen/activity_horizontal_margin"
        &gt;

        &lt;TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/eu_consent_text"
            android:textSize="14sp"
            android:paddingBottom="6dp"
            /&gt;

        &lt;TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/eu_consent_question"
            android:textSize="14sp"
            android:paddingBottom="6dp"
            android:textStyle="bold"
        /&gt;

        &lt;Button
            android:id="@+id/btn_eu_consent_yes"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/eu_consent_yes"
            android:textSize="13sp"
            /&gt;

        &lt;Button
            android:id="@+id/btn_eu_consent_no"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/eu_consent_no"
            android:textSize="13sp"
            android:layout_marginTop="6dp"
            android:layout_marginBottom="6dp"
            /&gt;

        &lt;Button
            android:id="@+id/btn_eu_consent_remove_ads"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/action_remove_ads"
            android:textSize="13sp"
            /&gt;

        &lt;TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/eu_consent_change_setting"
            android:textSize="14sp"
            android:paddingTop="6dp"
            android:paddingBottom="6dp"
            /&gt;

        &lt;TextView
            android:id="@+id/tv_eu_learn_more"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/learn_more"
            android:textSize="14sp"
            android:ellipsize="marquee"
            android:fadingEdge="horizontal"
            android:paddingTop="6dp"
            android:paddingBottom="6dp"
            android:textColor="@color/blue"
            style="@style/SelectableItem"
            /&gt;

    &lt;/LinearLayout&gt;

&lt;/ScrollView&gt;
</code></pre>
<p>Add <code>euMoreInfoDialog()</code>:</p>
<pre><code>private void euMoreInfoDialog(){

    AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this, R.style.MyAlertDialogStyle);

    ScrollView sv = new ScrollView(this);
    LinearLayout ll = new LinearLayout(this);
    ll.setOrientation(LinearLayout.VERTICAL);

    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.WRAP_CONTENT);
    params.setMargins(40, 20, 40, 20);

    TextView tv_my_privacy_policy = new TextView(this);
    String link = "&lt;a href="+PRIVACY_URL+"&gt;"+getResources().getString(R.string.app_name)+"&lt;/a&gt;";
    tv_my_privacy_policy.setText(Html.fromHtml(link));
    tv_my_privacy_policy.setMovementMethod(LinkMovementMethod.getInstance());
    ll.addView(tv_my_privacy_policy, params);

    TextView tv_google_partners = new TextView(this);
    tv_google_partners.setText(R.string.google_partners);
    tv_google_partners.setPadding(40,40,40,20);
    ll.addView(tv_google_partners);

    List&lt;AdProvider&gt; adProviders = ConsentInformation.getInstance(this).getAdProviders();
    for (AdProvider adProvider : adProviders) {
        //log("adProvider: " +adProvider.getName()+ " " +adProvider.getPrivacyPolicyUrlString());
        link = "&lt;a href="+adProvider.getPrivacyPolicyUrlString()+"&gt;"+adProvider.getName()+"&lt;/a&gt;";
        TextView tv_adprovider = new TextView(this);
        tv_adprovider.setText(Html.fromHtml(link));
        tv_adprovider.setMovementMethod(LinkMovementMethod.getInstance());
        ll.addView(tv_adprovider, params);
    }
    sv.addView(ll);

    builder.setTitle(R.string.privacy_policy)
           .setView(sv)
           .setPositiveButton(R.string.dialog_close, null);

    final AlertDialog createDialog = builder.create();
    createDialog.show();

}
</code></pre>
<p>In your AdMob web interface select the ad technology providers you wish to use. I suggest you don&#8217;t select more than 20 (or so), because I assume the <code>euMoreInfoDialog()</code> will become very slow if you select too many providers.</p>
<p>Add to <code>onDestroy()</code> to prevent errors on screen rotate:</p>
<pre><code>@Override
public void onDestroy(){
    // ...
    if (mEuDialog != null &amp;&amp; mEuDialog.isShowing()) mEuDialog.cancel();
    // ...
    super.onDestroy();
}
</code></pre>
<p>When you make an ad request, check the value of <code>mShowNonPersonalizedAdRequests</code> and add <code>"npa"</code> to the request if necessary:</p>
<pre><code>Bundle extras = new Bundle();
if (mShowNonPersonalizedAdRequests)
    extras.putString("npa", "1");

AdRequest adRequest = new AdRequest.Builder()
    .addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
    .addTestDevice("YOUR-DEVICE-ID-GOES-HERE") // insert your device id
    .addNetworkExtrasBundle(AdMobAdapter.class, extras)
    .build();
</code></pre>
<p>And lastly, add strings for all your languages to <code>strings.xml</code>:</p>
<pre><code>&lt;!-- EU GDPR Consent texts --&gt;
&lt;string name="privacy_policy"&gt;Privacy Policy&lt;/string&gt;
&lt;string name="dialog_close"&gt;Close&lt;/string&gt;
&lt;string name="action_remove_ads"&gt;Remove ADs&lt;/string&gt;
&lt;string name="eu_consent_text"&gt;Dear user!\n\nWe use Google Admob to show ads. Ads support our work, and enable further development of this app. In line with the new European Data Protection Regulation (GDPR), we need your consent to serve ads tailored for you.&lt;/string&gt;
&lt;string name="eu_consent_question"&gt;Can your data be used to show ads tailored for you?&lt;/string&gt;
&lt;string name="learn_more"&gt;Learn how your data is used&lt;/string&gt;
&lt;string name="google_partners"&gt;Google and its partners:&lt;/string&gt;
&lt;string name="eu_consent_yes"&gt;Yes, continue to show relevant ads&lt;/string&gt;
&lt;string name="eu_consent_no"&gt;No, show ads that are irrelevant&lt;/string&gt;
&lt;string name="eu_consent_change_setting"&gt;You can change this setting anytime in the \"About\" window.&lt;/string&gt;
&lt;string name="thank_you"&gt;Thank you!&lt;/string&gt;
</code></pre>
<p>That&#8217;s it!</p>
<p>(Note: <code>log()</code> and <code>toast()</code> are my methods, replace them with your own. <code>PRIVACY_URL</code> is your <code>String</code> url to your privacy policy.)</p>
<p>&nbsp;</p>
<p>toast() 可以試看看這一組：</p>
<blockquote>
<pre>    public void toast(String text, Context context) {
        Toast.makeText(context, text, Toast.LENGTH_LONG).show();
    }
</pre>
</blockquote>
<hr />
<p>作者漏掉的 style</p>
<pre>&lt;style name="SelectableItem" parent="@android:style/Theme.Material.Light"&gt;
    &lt;item name="android:background"&gt;?android:attr/selectableItemBackground&lt;/item&gt;
&lt;/style&gt;</pre>
<hr />
<p>Color:</p>
<pre>&lt;color name="red"&gt;#FF0000&lt;/color&gt;
&lt;color name="green"&gt;#00FF00&lt;/color&gt;
&lt;color name="blue"&gt;#0000FF&lt;/color&gt;</pre>
<hr />
<h4>相關範例：</h4>
<ul>
<li><a href="https://github.com/felixb/callmeter/blob/a51a0fe1a6bfcc4b72679f6add8c5ea7b51eae18/CallMeter3G/src/main/java/de/ub0r/android/callmeter/ConsentManager.java">https://github.com/felixb/callmeter/blob/a51a0fe1a6bfcc4b72679f6add8c5ea7b51eae18/CallMeter3G/src/main/java/de/ub0r/android/callmeter/ConsentManager.java</a></li>
</ul>
<p>&nbsp;</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2018/05/admob-eu-consent-error-consent-form-can-be-used-with-custom-provider-selection-only/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Command-line translator using Google Translate</title>
		<link>https://stackoverflow.max-everyday.com/2017/07/command-line-translator-using-google-translate/</link>
					<comments>https://stackoverflow.max-everyday.com/2017/07/command-line-translator-using-google-translate/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Sat, 08 Jul 2017 09:20:40 +0000</pubDate>
				<category><![CDATA[Android筆記]]></category>
		<category><![CDATA[電腦相關應用]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[Google]]></category>
		<guid isPermaLink="false">http://stackoverflow.max-everyday.com/?p=951</guid>

					<description><![CDATA[今天更新Google Play 商店裡的App ...]]></description>
										<content:encoded><![CDATA[<p>今天更新Google Play 商店裡的App 顯示下面的訊息，叫我要把其他語言裡更新了什麼訊息都補齊，這個請「千萬」不要使用人工手動去翻，純手工去翻是生產力低、無聊又花時間的行為，建議的解法是要shell script ＋<a href="https://github.com/soimort/translate-shell">translate-shell</a>。</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-952" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2017/07/Screenshot-2017-07-08-16.27.59.jpg" alt="" width="1024" height="565" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2017/07/Screenshot-2017-07-08-16.27.59.jpg 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2017/07/Screenshot-2017-07-08-16.27.59-600x331.jpg 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2017/07/Screenshot-2017-07-08-16.27.59-768x424.jpg 768w" sizes="(max-width: 1024px) 100vw, 1024px" /></p>
<p>&nbsp;</p>
<p>shell script 很多，除了python 好用之外，常見的還有 unix 內建的bash shell script. 這次我選擇的是內建的，要用內建需要先學一下 bash shell 的 array 還有 loop 怎麼使用。</p>
<p>鳥哥loop 用法教學：<br />
<a href="http://linux.vbird.org/linux_basic/0340bashshell-scripts.php#for">http://linux.vbird.org/linux_basic/0340bashshell-scripts.php#for</a></p>
<p>鳥哥array 用法教學：<br />
<a href="http://linux.vbird.org/linux_basic/0340bashshell-scripts.php#array">http://linux.vbird.org/linux_basic/0340bashshell-scripts.php#array</a></p>
<hr />
<p>接下來安裝 translate-shell<br />
<a href="https://github.com/soimort/translate-shell">https://github.com/soimort/translate-shell</a></p>
<p>我是使用 Mac OS X, 需要另外再按裝 gawk, 只要使用指令：</p>
<pre>brew install gawk</pre>
<hr />
<p>最後我的script 長醬子：</p>
<pre>#!/bin/bash
filename="play_app_output.txt"
english="play_app_english.txt"
echo "" &gt; $filename
cat $english &gt;&gt; $filename
echo "" &gt;&gt; $filename

function printit(){
	lang=${1}
	echo "&lt;$lang&gt;" &gt;&gt; $filename
	trans -b :$lang file://$english &gt;&gt; $filename
	echo "&lt;/$lang&gt;" &gt;&gt; $filename
}

eat[1]="ar"
eat[2]="be"
max_nu=2

for (( i=1; i&lt;=${max_nu}; i=i+1 ))
do
	printit ${eat[$i]}
done

</pre>
<hr />
<p>要被翻的英文：</p>
<p>allow login facebook account by browser.</p>
<hr />
<p>script 執行完的結果：</p>
<blockquote>
<p class="p1"><span class="s1">&lt;en-US&gt;</span></p>
<p class="p1"><span class="s1">allow login facebook account by browser.</span></p>
<p class="p1"><span class="s1">&lt;/en-US&gt;</span></p>
<p class="p1"><span class="s1">&lt;ar&gt;</span></p>
<p class="p2" dir="rtl"><span class="s1">.حفصتملا قيرط نع كوبسيفلا باسح لوخدلا ليجست حامسلا </span></p>
<p class="p1"><span class="s1">&lt;/ar&gt;</span></p>
<p class="p1"><span class="s1">&lt;be&gt;</span></p>
<p class="p1"><span class="s1">дазволіць уліковы запіс Увайсці facebook браўзэра.</span></p>
<p class="p1"><span class="s1">&lt;/be&gt;</span></p>
</blockquote>
<hr />
<p>後記：</p>
<p>bash shell script 寫起來是很快，也可以完成「大部份」的需求，但後來想用來解決 android strings 裡裡翻譯，bash shell script用起來綁手綁腳，限制太多，最後還是改寫成python&#8230;, 所以一開始就應該用python 寫，才省時間。</p>
<p>改用python，對字串和對檔案的處理就變的很簡單。執行外部指令可以使用：</p>
<pre>from subprocess import call
tmp_input_file = "android_trans_input.tmp"
tmp_output_file = "android_trans_output.tmp"
trans_para_pattern = "trans -b -s en :%s -i %s -o %s" % (lang, tmp_input_file, tmp_output_file)
call(trans_para_pattern, shell=True)
</pre>
<hr />
<p>我使用的副程式：</p>
<p>&nbsp;</p>
<pre>def google_trans(string_value, lang):
 tmp_input_file = "android_trans_input.tmp"
 outfile = open(tmp_input_file, 'w')
 outfile.write(string_value)
 outfile.close()

from subprocess import call
 tmp_output_file = "android_trans_output.tmp"
 trans_para_pattern = "trans -b -s en :%s -i %s -o %s" % (lang, tmp_input_file, tmp_output_file)
 #call(["trans", trans_para_pattern])
 call(trans_para_pattern, shell=True)
 #call(["ls", "-al"])
 #call("ls android_t*", shell=True)
 #call(["pwd"])

infile = open(tmp_output_file,"r")
 total_line = ""
 for in_line in infile:
 in_line = in_line.strip()
 total_line += in_line
 infile.close()

call("rm android*.tmp", shell=True) 
 return total_line</pre>
<hr />
<pre>def convert_trans(filename, lang):
 #filename = "Localizable.strings"
 filename_ext = splitext(filename)[1]
 #filename_output = "%s%s" % (splitext(filename)[0], filename_ext)
 filename_output = filename
 directory_name = "./values-%s/" % (lang)
 directory_name = directory_name.replace("zh-", "zh-r")
 _createFolder(directory_name )

outfile = open(directory_name + filename_output, 'w')
 infile = open(filename)

total_line = ""

process_mode = "ios"
 if filename_ext == ".xml":
 process_mode = "android"

delimiter = "="
 if process_mode == "android":
 delimiter = "&gt;"

string_pattern = '%s = "%s";\n'

for in_line in infile:
 out_line = in_line
 
 if is_translata_target(in_line, process_mode):
 # for translate item.
 if process_mode == "android":
 string_android_tag_name="string"
 if '&lt;/item&gt;' in in_line and '&lt;item' in in_line:
 string_android_tag_name="item"
 string_pattern = '%s&gt;%s&lt;/'+ string_android_tag_name +'&gt;\n'

string_key = in_line.split(delimiter)[0]
 string_value = find_between(in_line,"&gt;","&lt;/")
 string_value = encrypt_escape_word(string_value, process_mode)
 string_trans = google_trans(string_value, lang)
 string_trans = decrypt_escape_word(string_trans, process_mode, lang)
 out_line = string_pattern % (string_key.rstrip(),string_trans)
 #print out_line

if not is_skip_translata_key(in_line, process_mode):
 outfile.write(out_line)
 #total_line = u'%s%s' % (total_line,out_line)
 total_line += out_line
 infile.close()
 outfile.close()</pre>
<hr />
<p>心得是效果滿好的，還可以調整那些規則下的文字不要去翻譯。</p>
<p>&nbsp;</p>
<p>如果你會處理到 lang=&#8221;ar&#8221; 或 lang=&#8221;fa&#8221;，記得要安裝一下 FirBiDi:</p>
<p>指令：</p>
<p>brew install fribidi</p>
<p>資料來源：</p>
<p><a href="http://macappstore.org/fribidi/">http://macappstore.org/fribidi/</a></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2017/07/command-line-translator-using-google-translate/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>[iOS] Google Mobile Ads SDK</title>
		<link>https://stackoverflow.max-everyday.com/2017/04/google-mobile-ads-sdk-on-ios/</link>
					<comments>https://stackoverflow.max-everyday.com/2017/04/google-mobile-ads-sdk-on-ios/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Sun, 30 Apr 2017 14:46:31 +0000</pubDate>
				<category><![CDATA[iOS筆記]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[iOS]]></category>
		<guid isPermaLink="false">http://stackoverflow.max-everyday.com/?p=594</guid>

					<description><![CDATA[今天要來練習怎麼在iOS 的世界裡放Google...]]></description>
										<content:encoded><![CDATA[<p>今天要來練習怎麼在iOS 的世界裡放Google ADs賺廣告費。下面這二篇Google寫文章寫的滿簡單的，照著做一下子就完成了：</p>
<ul>
<li><a href="https://firebase.google.com/docs/admob/ios/quick-start">https://firebase.google.com/docs/admob/ios/quick-start</a></li>
<li><a href="https://developers.google.com/mobile-ads-sdk/docs/admob/ios/quick-start?hl=zh-tw">https://developers.google.com/mobile-ads-sdk/docs/admob/ios/quick-start?hl=zh-tw</a></li>
</ul>
<p>教學文章裡建議我們安裝CocoaPods, 安裝方法：</p>
<pre><code class="highlight shell"><span class="gp">$ </span>sudo gem install cocoapods</code></pre>
<p>好像一行指令就結束了。</p>
<hr />
<p>接下來去 Admob 建立一個 Project, 不到一分鐘就建立完了，比我打字還快！</p>
<p><a href="https://apps.admob.com/">https://apps.admob.com/</a></p>
<hr />
<p>接下來要加入 Google Adsense</p>
<h4 id="create_the_podfile">建立 Podfile</h4>
<p>在與 <code>BannerExample.xcodeproj</code> 檔案相同的目錄中，建立一個檔案並將名稱設為 <code>Podfile</code>，其中包含以下項目：</p>
<div class="devsite-code-button-wrapper"></div>
<pre class="prettyprint notranslate" translate="no">source 'https://github.com/CocoaPods/Specs.git'

platform :ios, '7.0'

target 'BannerExample' do
 pod 'Google-Mobile-Ads-SDK', '~&gt; 7.0'
end</pre>
<hr />
<p>Run <code>pod update</code> from the terminal.</p>
<p>執行完成後，關閉 BannerExample.xcodeproj 並開啟 BannerExample.<span style="color: #ff0000;"><strong>xcworkspace</strong></span>。</p>
<p>標註成紅色，因為我開錯了，要開紅色這組。</p>
<hr />
<p>發現無法Build, 有 Error, 所以對 BannerExample 專案按一下滑鼠右鍵，然後選取 [Add Files To &#8220;BannerExample&#8221;]。</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-599 size-full" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/ios-quickstart-05.png" alt="" width="1554" height="1350" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/ios-quickstart-05.png 1554w, https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/ios-quickstart-05-600x521.png 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/ios-quickstart-05-768x667.png 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/ios-quickstart-05-1024x890.png 1024w" sizes="(max-width: 1554px) 100vw, 1554px" /></p>
<p>然後再手動加入 SDK 需要的其他架構，包括：</p>
<p>AdSupport<br />
AudioToolbox<br />
AVFoundation<br />
CoreGraphics<br />
CoreTelephony<br />
EventKit<br />
EventKitUI<br />
MessageUI<br />
StoreKit<br />
SystemConfiguration</p>
<p>為什麼需要自己手動加入這些 framework, 因為我開啟錯檔案，要開啟 .xcworkspace.</p>
<p>&nbsp;</p>
<hr />
<p>首先，我們要來拉 UI ，在 Storyboard。</p>
<h2 id="your_first_banner_request">Your first banner request</h2>
<p>Now that you have a project with the SDK referenced, it&#8217;s time to put banner ads into it.</p>
<p>A <code>GADBannerView</code> can be created from storyboard or from code. Since layouts are generally defined in storyboard, this guide shows the storyboard method.</p>
<h3 id="add_a_gadbannerview_in_storyboard">Add a GADBannerView in storyboard</h3>
<p>Click the <strong>Main.storyboard</strong> tab. In the bottom-right corner, select a UIView element and drag it into your view controller. Then in the Custom Class section in the top-right corner, select the custom class <code>GADBannerView</code> as the <strong>Class</strong> for this view.</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-598 size-full" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-30-22.01.59.jpg" alt="" width="1024" height="655" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-30-22.01.59.jpg 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-30-22.01.59-600x384.jpg 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-30-22.01.59-768x491.jpg 768w" sizes="(max-width: 1024px) 100vw, 1024px" /></p>
<p>已經1年半沒寫iOS完全忘記怎麼插入View，紅色框框處是我找了一下終於找到的關鍵。</p>
<hr />
<h4 id="add_constraints_on_the_gadbannerview">Add constraints on the GADBannerView</h4>
<p>Set the following constraints on the <code>GADBannerView</code>:</p>
<ul>
<li>Locate it at the bottom of the screen.</li>
<li>Set the size of the view to <code>320</code> wide by <code>50</code> high.</li>
<li>Center it.</li>
</ul>
<p>Make sure the view is selected, and click the <strong>Pin</strong> icon at the bottom of the screen. Add a <strong>Spacing to nearest neighbor</strong> constraint on the bottom of the banner with the value of <code>0</code>. This pins the view to the bottom of the screen.</p>
<p>Also check the width and height constraints, and set the values to <code>320</code> and <code>50</code>, respectively, to set the size of the view.</p>
<p>為什麼這裡是  320 x 50 px, 請參考看看 Google Ads 官方說明：<br />
橫幅廣告大小<br />
<a href="https://developers.google.com/mobile-ads-sdk/docs/admob/ios/banner?hl=zh-tw">https://developers.google.com/mobile-ads-sdk/docs/admob/ios/banner?hl=zh-tw</a></p>
<p>其實，高度應該設90 或 100, 效果會比較好，預設的「智慧型橫幅廣告」可以套用到廣告比較多。</p>
<hr />
<h4 id="adding_a_reference_to_your_gadbannerview_in_code">Adding a reference to your GADBannerView in code</h4>
<p>The <code>GADBannerView</code> needs a reference in code to load ads into it. Open up the Assistant Editor by navigating to <strong>View &gt; Assistant Editor &gt; Show Assistant Editor</strong>. Make sure the ViewController.h file is showing in the Assistant Editor (the right pane of the screen). Next, holding the control key, click the <code>GADBannerView</code> (in the center pane), and drag your cursor over to ViewController.h (as indicated by the blue line going from the center pane to the right pane).</p>
<hr />
<p><img loading="lazy" decoding="async" class="alignnone wp-image-600 size-full" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-30-22.25.55.jpg" alt="" width="1024" height="657" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-30-22.25.55.jpg 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-30-22.25.55-600x385.jpg 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-30-22.25.55-768x493.jpg 768w" sizes="(max-width: 1024px) 100vw, 1024px" /></p>
<p>只記得要按Ctrl 去拉，但切到這個畫面預設是在 ViewController.m 拉半天都無法  Connect，原來是要再切到 ViewController.h.</p>
<p>似乎可以拉到 .m 檔案。</p>
<p>&nbsp;</p>
<hr />
<p>To resolve a compilation error, add <code>@import GoogleMobileAds</code> to ViewController.h or <code>import GoogleMobileAds</code> to ViewController.swift so the compiler knows that <code>GADBannerView</code> is a valid class.</p>
<p>&nbsp;</p>
<hr />
<h3 id="initialize_the_google_mobile_ads_sdk">Initialize the Google Mobile Ads SDK</h3>
<p>At app launch, initialize the Google Mobile Ads SDK by calling <code>configureWithApplicationID:</code> in the <code>application:didFinishLaunchingWithOptions:</code>method of <code>AppDelegate.m</code> or <code>AppDelegate.swift</code>.</p>
<pre class="prettyprint"><span class="oldcode"><span class="pun">-</span> <span class="pun">(</span><span class="pln">BOOL</span><span class="pun">)</span><span class="pln">application</span><span class="pun">:(</span><span class="typ">UIApplication</span> <span class="pun">*)</span><span class="pln">application
    didFinishLaunchingWithOptions</span><span class="pun">:(</span><span class="typ">NSDictionary</span> <span class="pun">*)</span><span class="pln">launchOptions </span><span class="pun">{</span><span class="pln">

  </span><span class="com">// Use Firebase library to configure APIs</span><span class="pln">
  </span><span class="pun">[</span><span class="typ">FIRApp</span><span class="pln"> configure</span><span class="pun">];</span><span class="pln">
  </span></span><span class="newcode"><span class="pun">[</span><span class="typ">GADMobileAds</span><span class="pln"> configureWithApplicationID</span><span class="pun">:@</span><span class="str">"ca-app-pub-3940256099942544~1458002511"</span><span class="pun">];</span><span class="pln">
  </span></span><span class="oldcode"><span class="kwd">return</span><span class="pln"> YES</span><span class="pun">;</span>
<span class="pun">}
</span></span></pre>
<p>這裡用的是Google Ads 給的第1組序號「應用程式 ID」，在 ViewController 裡用的是另一組「廣告單元 ID」。</p>
<hr />
<p>修改 ViewController.m</p>
<pre class="prettyprint"><span class="oldcode"><span class="pun">-</span> <span class="pun">(</span><span class="kwd">void</span><span class="pun">)</span><span class="pln">viewDidLoad </span><span class="pun">{</span><span class="pln">
  </span><span class="pun">[</span><span class="kwd">super</span><span class="pln"> viewDidLoad</span><span class="pun">];</span><span class="pln">

  </span><span class="typ">NSLog</span><span class="pun">(@</span><span class="str">"Google Mobile Ads SDK version: %@"</span><span class="pun">,</span> <span class="pun">[</span><span class="typ">GADRequest</span><span class="pln"> sdkVersion</span><span class="pun">]);</span><span class="pln">
  </span></span><span class="newcode"><span class="kwd">self</span><span class="pun">.</span><span class="pln">bannerView</span><span class="pun">.</span><span class="pln">adUnitID </span><span class="pun">=</span> <span class="pun">@</span><span class="str">"ca-app-pub-3940256099942544/2934735716"</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">self</span><span class="pun">.</span><span class="pln">bannerView</span><span class="pun">.</span><span class="pln">rootViewController </span><span class="pun">=</span> <span class="kwd">self</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">[</span><span class="kwd">self</span><span class="pun">.</span><span class="pln">bannerView loadRequest</span><span class="pun">:[</span><span class="typ">GADRequest</span><span class="pln"> request</span><span class="pun">]];</span></span><span class="oldcode">
<span class="pun">}</span></span></pre>
<p>加好之後，按 Run 就成功了，廣告有出來。</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-601 size-full" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/S__4194306.jpg" alt="" width="1024" height="768" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/S__4194306.jpg 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/S__4194306-600x450.jpg 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/S__4194306-768x576.jpg 768w" sizes="(max-width: 1024px) 100vw, 1024px" /></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2017/04/google-mobile-ads-sdk-on-ios/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Google Analtyics 超強分析統計工具，關鍵字、流量來源一手包辦</title>
		<link>https://stackoverflow.max-everyday.com/2017/04/google-analytics-for-wordpress-by-monsterinsights/</link>
					<comments>https://stackoverflow.max-everyday.com/2017/04/google-analytics-for-wordpress-by-monsterinsights/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Sat, 15 Apr 2017 09:51:51 +0000</pubDate>
				<category><![CDATA[WordPress筆記]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[wordpress]]></category>
		<guid isPermaLink="false">http://stackoverflow.max-everyday.com/?p=500</guid>

					<description><![CDATA[經營網站（Web站台）一定要使用Google A...]]></description>
										<content:encoded><![CDATA[<p>經營網站（Web站台）一定要使用Google Analytics 分析服務！</p>
<p>Google Analytics可以幫我們網站分析：關鍵字、流量來源、瀏覽頁次，當我們有了這些數據，我們可以分析訪客習性、讀者喜歡看的資訊，透過有效的分析比對，可以幫助網站讓客人們更喜歡，而且這個Google Analytics 分析工具是完全免費的。</p>
<p>&nbsp;</p>
<p>設定加入 Google Search Console 追蹤您的網站搜尋排名，並瀏覽更多網站管理員資源。<br />
<a href="https://www.google.com/webmasters/">https://www.google.com/webmasters/</a></p>
<p>加入Google Search Console「可能」可以提高一點點在 Google Search 裡的排名，Google 在驗證 domain name 的擁有者時，有可能需要去設定一下自己的 DNS 增加一筆 TXT 的記錄，裡面放google 指定給我們的字串，用來證明我們是網域的擁有者。</p>
<p>上面這2個設定大約3分鐘就可以設好。</p>
<p>&nbsp;</p>
<p>接下來要加入 Google Analytics (Google 分析)<br />
<a href="https://www.google.com/intl/zh-TW_ALL/analytics/index.html">https://www.google.com/intl/zh-TW_ALL/analytics/index.html</a></p>
<p>Google 分析畫面做的很怪，有點難以理解。要先點最左下角的齒輪（設定）然後在帳戶點下拉框，再「建立新帳戶」。</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-509 size-full" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-14-19.02.38_result.jpg" alt="" width="763" height="1024" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-14-19.02.38_result.jpg 763w, https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-14-19.02.38_result-477x640.jpg 477w" sizes="(max-width: 763px) 100vw, 763px" /></p>
<p>&nbsp;</p>
<p>下一個步驟沒什麼難度，大約15秒就填完，Google 的設定就結束了。</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-510 size-full" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-14-19.04.04_result.jpg" alt="" width="849" height="1024" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-14-19.04.04_result.jpg 849w, https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-14-19.04.04_result-531x640.jpg 531w, https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-14-19.04.04_result-768x926.jpg 768w" sizes="(max-width: 849px) 100vw, 849px" /></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>回到 WordPress 裡，Google Analytics在WordPress官方外掛目錄中也是有非常多的應用外掛，我用的是：<br />
Google Analytics for WordPress by MonsterInsights<br />
<a href="https://srd.wordpress.org/plugins/google-analytics-for-wordpress/">https://srd.wordpress.org/plugins/google-analytics-for-wordpress/</a></p>
<blockquote class="wp-embedded-content" data-secret="6pYTYqFnCm"><p><a href="https://srd.wordpress.org/plugins/google-analytics-for-wordpress/">MonsterInsights &#8211; Google Analytics Dashboard for WordPress (Website Stats Made Easy)</a></p></blockquote>
<p><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;MonsterInsights &#8211; Google Analytics Dashboard for WordPress (Website Stats Made Easy)&#8221; &#8212; Plugin Directory" src="https://srd.wordpress.org/plugins/google-analytics-for-wordpress/embed/#?secret=1NodcjX8hd#?secret=6pYTYqFnCm" data-secret="6pYTYqFnCm" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe></p>
<p>&nbsp;</p>
<p>當然你也可以完全不去使用外掛，把 Google Analytics 提供的 javascript 放到自己的 html 裡就結束了，用這個  MonsterInsights 外掛有2個好處：</p>
<p>好處1: 自動幫我們取得我們Google Analytics  裡的設定值(Profile)，而且自動幫我們把 javascript 放到網頁裡。</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-511 size-full" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-14-19.05.34_result.jpg" alt="" width="718" height="1024" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-14-19.05.34_result.jpg 718w, https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-14-19.05.34_result-449x640.jpg 449w" sizes="(max-width: 718px) 100vw, 718px" /></p>
<p>這是 MonsterInsights 自動取得我們Profile 的介面。</p>
<p>好處2: 在我們的自己後台可以看到流量（PAGE VIEWS）、熱門頁面（TOP POSTS AND PAGES）、流量來源（TOP TRAFFIC SOURCES），訪客地區別（TOP COUNTRIES）。</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-512 size-full" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-15-17.16.15_result.jpg" alt="" width="1024" height="577" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-15-17.16.15_result.jpg 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-15-17.16.15_result-640x361.jpg 640w, https://stackoverflow.max-everyday.com/wp-content/uploads/2017/04/Screenshot-2017-04-15-17.16.15_result-768x433.jpg 768w" sizes="(max-width: 1024px) 100vw, 1024px" /></p>
<p>流量在第1天是空白的，要過1天，才會跑出第1個小點點。</p>
<p>所有的設定5~10分鐘就設完了，WordPress 外掛下載和設定用不到1分鐘就設完了。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2017/04/google-analytics-for-wordpress-by-monsterinsights/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Gracefully handling application exceptions in a Tornado application</title>
		<link>https://stackoverflow.max-everyday.com/2017/02/handling-exceptions-tornado/</link>
					<comments>https://stackoverflow.max-everyday.com/2017/02/handling-exceptions-tornado/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Sun, 05 Feb 2017 19:40:00 +0000</pubDate>
				<category><![CDATA[Python筆記]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[letsencrypt]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[tornado]]></category>
		<guid isPermaLink="false">http://stackoverflow.max-everyday.com/?p=93</guid>

					<description><![CDATA[之前在DigitalOcean 的主機上用wor...]]></description>
										<content:encoded><![CDATA[<p>之前在DigitalOcean 的主機上用wordpress架了一個web site站台，只使用了 port 80, 後來在 443 port 架了 tornado, 滿神奇的，tornado 可以直接使用 letsencrypt 的憑證檔案。</p>
<p>後來發現 googlebot 會來parse 443 port 裡的資料。所以我希望 404 錯誤的資料就從80 port 裡重新取得一次，丟給Google.</p>
<hr />
<h4>取得 ssl 憑證</h4>
<p>首先，是 letsencrypt 的 certbot 的官方說明：<br />
<a href="https://certbot.eff.org/#ubuntuxenial-other">https://certbot.eff.org/#ubuntuxenial-other</a></p>
<p>照上面教學的指令下，只用一行就可以產生憑證。知道網站的根目錄用這行（指令1號）：</p>
<pre>letsencrypt certonly --webroot -w /var/www/example -d example.com
</pre>
<hr />
<p>如果你完全沒有網站，可以使用這行（指令2號）：</p>
<pre>letsencrypt certonly --standalone -d example.com -d www.example.com</pre>
<p>指令1號，letsencrypt 會放東西在 webroot 目錄裡，然後letsencrypt用 80 port 連回我們伺服器進去測試。</p>
<p>指令2號，會自動產生一個 80 port 的 service，然後letsencrypt用 80 port 連回我們伺服器進去測試。</p>
<p>這2個指令，都會因此去使用 443 port，實際上原理不需要了解太多，certbot 寫的滿聰明的，會在畫面上有提醒。產生完，手動測一下 renew，沒問題的把把 renew 指令放進排程裡。</p>
<pre>letsencrypt renew --dry-run --agree-tos</pre>
<p>成功後就會在ubuntu的/etc/letsencrypt/archive/ 下面看到憑證了。</p>
<p>&nbsp;</p>
<p>在 80和443 port 都已經被其他程式使用中的請況下，需要更進階的設定，參考看看這一篇：</p>
<p>apache AH00171: Graceful restart requested, doing restart<br />
<a href="https://stackoverflow.max-everyday.com/2017/02/apache-graceful-restart/">https://stackoverflow.max-everyday.com/2017/02/apache-graceful-restart/</a></p>
<p>例如：</p>
<pre>letsencrypt certonly --standalone -d example.com --tls-sni-01-port 445 --http-01-port 82</pre>
<hr />
<h4>修改Tornado的 ssl_options</h4>
<p>tornado改程式，須追加此兩個檔案的路徑在ssl_options下面，並注意檔案權限要能被讀。</p>
<p>http_server = tornado.httpserver.HTTPServer(Application(),<br />
ssl_options={<br />
&#8220;certfile&#8221;: &#8220;cert.pem&#8221;, #自行填好路徑<br />
&#8220;keyfile&#8221;: &#8220;privkey.pem&#8221;, #自行填好路徑<br />
})</p>
<p>完成之後，研究一下 tornado 的官方文件：<br />
<a href="http://www.tornadoweb.org/en/stable/web.html#tornado.web.Application.settings">http://www.tornadoweb.org/en/stable/web.html#tornado.web.Application.settings</a></p>
<hr />
<p>要使用下面的這組設定值來完成自動轉發：</p>
<ul class="simple">
<li><code class="docutils literal"><span class="pre">default_handler_class</span></code> and <code class="docutils literal"><span class="pre">default_handler_args</span></code>: This handler will be used if no other match is found; use this to implement custom 404 pages (new in Tornado 3.2).</li>
</ul>
<p>範例寫法：</p>
<pre class="lang-python prettyprint prettyprinted"><code><span class="kwd">import</span><span class="pln"> tornado</span><span class="pun">.</span><span class="pln">web


</span><span class="kwd">class</span> <span class="typ">BaseHandler</span><span class="pun">(</span><span class="pln">tornado</span><span class="pun">.</span><span class="pln">web</span><span class="pun">.</span><span class="typ">RequestHandler</span><span class="pun">):</span>
    <span class="str">"""
    Base handler gonna to be used instead of RequestHandler
    """</span>
    <span class="kwd">def</span><span class="pln"> write_error</span><span class="pun">(</span><span class="pln">self</span><span class="pun">,</span><span class="pln"> status_code</span><span class="pun">,</span> <span class="pun">**</span><span class="pln">kwargs</span><span class="pun">):</span>
        <span class="kwd">if</span><span class="pln"> status_code </span><span class="kwd">in</span> <span class="pun">[</span><span class="lit">403</span><span class="pun">,</span> <span class="lit">404</span><span class="pun">,</span> <span class="lit">500</span><span class="pun">,</span> <span class="lit">503</span><span class="pun">]:</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="str">'Error %s'</span> <span class="pun">%</span><span class="pln"> status_code</span><span class="pun">)</span>
        <span class="kwd">else</span><span class="pun">:</span><span class="pln">
            self</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="str">'BOOM!'</span><span class="pun">)</span>


<span class="kwd">class</span> <span class="typ">ErrorHandler</span><span class="pun">(</span><span class="pln">tornado</span><span class="pun">.</span><span class="pln">web</span><span class="pun">.</span><span class="typ">ErrorHandler</span><span class="pun">,</span> <span class="typ">BaseHandler</span><span class="pun">):</span>
    <span class="str">"""
    Default handler gonna to be used in case of 404 error
    """</span>
    <span class="kwd">pass</span>


<span class="kwd">class</span> <span class="typ">MainHandler</span><span class="pun">(</span><span class="typ">BaseHandler</span><span class="pun">):</span>
    <span class="str">"""
    Main handler
    """</span>
    <span class="kwd">def</span><span class="pln"> get</span><span class="pun">(</span><span class="pln">self</span><span class="pun">):</span><span class="pln">
        self</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="str">'Hello world!'</span><span class="pun">)</span></code></pre>
<ol start="2">
<li>Settings. We need to define <code>default_handler_class</code> and <code>default_handler_args</code> as well</li>
</ol>
<pre class="lang-python prettyprint prettyprinted"><code><span class="pln">settings </span><span class="pun">=</span> <span class="pun">{</span>
    <span class="str">'default_handler_class'</span><span class="pun">:</span> <span class="typ">ErrorHandler</span><span class="pun">,</span>
    <span class="str">'default_handler_args'</span><span class="pun">:</span><span class="pln"> dict</span><span class="pun">(</span><span class="pln">status_code</span><span class="pun">=</span><span class="lit">404</span><span class="pun">)</span>
<span class="pun">}</span></code></pre>
<ol start="3">
<li>Application.</li>
</ol>
<pre class="lang-python prettyprint prettyprinted"><code><span class="pln">application </span><span class="pun">=</span><span class="pln"> tornado</span><span class="pun">.</span><span class="pln">web</span><span class="pun">.</span><span class="typ">Application</span><span class="pun">([</span>
    <span class="pun">(</span><span class="pln">r</span><span class="str">"/"</span><span class="pun">,</span> <span class="typ">MainHandler</span><span class="pun">)</span>
<span class="pun">],</span> <span class="pun">**</span><span class="pln">settings</span><span class="pun">)</span></code></pre>
<p>as result. all errors except 404 gonna be handled by BaseHandler. 404 &#8211; ErrorHandler. that&#8217;s it <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<hr />
<p>我的轉發的 code:</p>
<pre>def write_error(self, status_code, **kwargs):
    if status_code == 404:
        from lib import libHttp
        http_obj = libHttp.Http()
        http_url = "http://%s%s" % (self.request.host,self.request.uri)
        (new_html_string, http_code) = http_obj.get_http_response(http_url)
        self.set_status(http_code)
        if not new_html_string is None:
            self.write(new_html_string)
        return
    return super(BaseHandler, self).write_error(status_code, **kwargs)

</pre>
<hr />
<p>我最後放棄這個作法，改安裝 Nginx（發音同engine x）是一個網頁伺服器，它能反向代理HTTP, HTTPS, SMTP, POP3, IMAP的協議鏈接，以及一個負載均衡器和一個HTTP緩存。</p>
<p><a href="https://www.nginx.com/resources/wiki/start/topics/tutorials/install/">https://www.nginx.com/resources/wiki/start/topics/tutorials/install/</a></p>
<p>範例：</p>
<p>nginx 基礎設定教學<br />
<a href="http://blog.hellojcc.tw/2015/12/07/nginx-beginner-tutorial/">http://blog.hellojcc.tw/2015/12/07/nginx-beginner-tutorial/</a></p>
<p>&nbsp;</p>
<hr />
<h4>相關文章：</h4>
<p>apache AH00171: Graceful restart requested, doing restart<br />
<a href="https://stackoverflow.max-everyday.com/2017/02/apache-graceful-restart/">https://stackoverflow.max-everyday.com/2017/02/apache-graceful-restart/</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2017/02/handling-exceptions-tornado/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Integrating Google Sign-In into Your Android App</title>
		<link>https://stackoverflow.max-everyday.com/2016/08/google-sign-in/</link>
					<comments>https://stackoverflow.max-everyday.com/2016/08/google-sign-in/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Fri, 26 Aug 2016 16:52:26 +0000</pubDate>
				<category><![CDATA[Android筆記]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[Google]]></category>
		<guid isPermaLink="false">http://stackoverflow.max-everyday.com/?p=62</guid>

					<description><![CDATA[試了一下，在程式裡加入 Gogole Sign-...]]></description>
										<content:encoded><![CDATA[<p>試了一下，在程式裡加入 Gogole Sign-in，還滿簡單的，大約半小時～1小時就可以完成範例程式。試了一下 getID() 可以拿到一串好長好長的id, getEmail() 可以拿到user 的 email, 神奇的是程式不需要存取網路。</p>
<p>最佳的入門教學，應該是官方的這一篇：</p>
<blockquote><p>Start Integrating Google Sign-In into Your Android App<br />
<a href="https://developers.google.com/identity/sign-in/android/start-integrating">https://developers.google.com/identity/sign-in/android/start-integrating</a></p></blockquote>
<hr />
<p>先 git clone 別人寫的範例，用修改會比較快：</p>
<blockquote><p><a href="https://github.com/googlesamples/google-services">https://github.com/googlesamples/google-services</a></p></blockquote>
<p>github 的範例有一個問題，就是 apply plugin 是有問題的，改成這段就OK了。</p>
<blockquote>
<pre>dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.2.0'

    // Dependency for Google Sign-In （這個版號，會一直被修改）
    compile '<strong><span style="color: #339966;">com.google.android.gms:play-services-auth:9.8.0</span></strong>'
}</pre>
</blockquote>
<hr />
<p>在修改時，和Facebook Login 最大的差別在Google Sign-in 不需要在 AndroidManifest.xml 裡增加一個 activity 的宣告。</p>
<hr />
<p>Googe Sign-in 需要使用到一個「configuration file」，這是Facebook login 沒有的。</p>
<hr />
<p>重新下載Google API 憑證，可以到這裡下載：</p>
<p><a href="https://console.developers.google.com/">Google API Console</a><br />
<a href="https://console.developers.google.com/">https://console.developers.google.com/</a></p>
<hr />
<p>需要使用google token 來進行身分驗證，參考這一篇：</p>
<p>Authenticate with a backend server<br />
<a href="https://developers.google.com/identity/sign-in/android/backend-auth">https://developers.google.com/identity/sign-in/android/backend-auth</a></p>
<p>重點就是 android code 要多一行 requestIdToken：</p>
<pre class="prettyprint"><span class="typ">GoogleSignInOptions</span><span class="pln"> gso </span><span class="pun">=</span> <span class="kwd">new</span> <span class="typ">GoogleSignInOptions</span><span class="pun">.</span><span class="typ">Builder</span><span class="pun">(</span><span class="typ">GoogleSignInOptions</span><span class="pun">.</span><span class="pln">DEFAULT_SIGN_IN</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">requestIdToken</span><span class="pun">(</span><span class="pln">getString</span><span class="pun">(</span><span class="pln">R</span><span class="pun">.</span><span class="kwd">string</span><span class="pun">.</span><span class="pln">server_client_id</span><span class="pun">))</span><span class="pln">
        </span><span class="pun">.</span><span class="pln">build</span><span class="pun">();</span></pre>
<p>拿到token 之後，就可以連去google 問帳號資料：</p>
<pre>https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=XYZ123</pre>
<p>If the token is properly signed and the <code>iss</code> and <code>exp</code> claims have the expected values, you will get a HTTP 200 response, where the body contains the JSON-formatted ID token claims. Here&#8217;s an example response:</p>
<pre>{
 // These six fields are included in all Google ID Tokens.
 "iss": "https://accounts.google.com",
 "sub": "110169484474386276334",
 "azp": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "aud": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "iat": "1433978353",
 "exp": "1433981953",

 // These seven fields are only included when the user has granted the "profile" and
 // "email" OAuth scopes to the application.
 "email": "testuser@gmail.com",
 "email_verified": "true",
 "name" : "Test User",
 "picture": "https://lh4.googleusercontent.com/-kYgzyAWpZzJ/ABCDEFGHI/AAAJKLMNOP/tIXL9Ir44LE/s99-c/photo.jpg",
 "given_name": "Test",
 "family_name": "User",
 "locale": "en"
}</pre>
<hr />
<p>相關文章：</p>
<p>Integrating Google Sign-In into Your Android App<br />
<a href="https://developers.google.com/identity/sign-in/android/sign-in?configured=true">https://developers.google.com/identity/sign-in/android/sign-in?configured=true</a></p>
<p>Authenticating Your Client<br />
<a href="https://developers.google.com/android/guides/client-auth">https://developers.google.com/android/guides/client-auth</a></p>
<p>Add Google Sign-In to Your Android App<br />
<a href="https://developers.google.com/identity/sign-in/android/">https://developers.google.com/identity/sign-in/android/</a></p>
<h4>實作出來的範例：</h4>
<p><a href="http://max-everyday.com/wp-content/uploads/2016/08/Screenshot-2016-08-27-18.07.13.jpg"><img loading="lazy" decoding="async" class="alignnone wp-image-1197 size-medium" src="http://max-everyday.com/wp-content/uploads/2016/08/Screenshot-2016-08-27-18.07.13-303x500.jpg" alt="Screenshot 2016-08-27 18.07.13" width="303" height="500" /></a></p>
<hr />
<p>Google 好心地提醒我們，別把user id 當參數傳給自己的後端server API, 而是要使用被驗證過的token.</p>
<blockquote><p><strong>Warning:</strong> Do not accept plain user IDs, such as those you can get with the <code>GoogleSignInAccount.getId()method</code>, on your backend server. A modified client application can send arbitrary user IDs to your server to impersonate users, so you must instead use verifiable ID tokens to securely get the user IDs of signed-in users on the server side.</p></blockquote>
<p><a href="https://developers.google.com/identity/sign-in/android/backend-auth">https://developers.google.com/identity/sign-in/android/backend-auth</a></p>
<p>Verify the integrity of the ID token, 就把token 送 GET 到這個 URL:</p>
<blockquote>
<pre>https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=XYZ123</pre>
</blockquote>
<p>If the token is properly signed and the <code>iss</code> and <code>exp</code> claims have the expected values, you will get a HTTP 200 response, where the body contains the JSON-formatted ID token claims. Here&#8217;s an example response:</p>
<blockquote>
<pre>{
 // These six fields are included in all Google ID Tokens.
 "iss": "https://accounts.google.com",
 "sub": "110169484474386276334",
 "azp": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "aud": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
 "iat": "1433978353",
 "exp": "1433981953",

 // These seven fields are only included when the user has granted the "profile" and
 // "email" OAuth scopes to the application.
 "email": "testuser@gmail.com",
 "email_verified": "true",
 "name" : "Test User",
 "picture": "https://lh4.googleusercontent.com/-kYgzyAWpZzJ/ABCDEFGHI/AAAJKLMNOP/tIXL9Ir44LE/s99-c/photo.jpg",
 "given_name": "Test",
 "family_name": "User",
 "locale": "en"
}
</pre>
</blockquote>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2016/08/google-sign-in/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
