Saturday, March 15, 2025

Type Misalignment Mirage

Some time ago, I developed a small Windows tool to measure how long a thread took to test an external device. The project intrigued me due to a particularly tricky bug that took me over an hour to resolve. During that time, I consulted ChatGPT for assistance, but it failed to provide useful solutions.

Today, I decided to try DeepSeek instead. I copied the same question I had asked ChatGPT, and amusingly, DeepSeek delivered the correct answer after just 137 seconds of processing. From now on, I’ll be making DeepSeek my first go-to resource for troubleshooting.

In this article, I’ll share the problem I encountered, how I ultimately solved it, and—of course—the first time and correct answer provided by DeepSeek.

Question (to ChatGPT and DeepSeek):

This is Visual C++ Win32 MFC application, why following function got GUI update with "Multicast stopped: 57730ms, sent 0 frames":  

void threadFunction()

{

    // Record the start time using high-resolution clock

    auto startTime = std::chrono::high_resolution_clock::now();

    int nSentCount = 0;

    // Simulate some work by making the thread sleep for 2 seconds

    // thread running ...,  std::this_thread::sleep_for(std::chrono::seconds(2));

    // nSentCount++;  // Increment the sent count

    // Record the end time

    auto endTime = std::chrono::high_resolution_clock::now();

    // Calculate the duration in milliseconds

    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();

    CString info;

    if (nSentCount > 1) info.Format(_T("Multicast stopped: %ldms, sent %ld frames"), duration, nSentCount);

    else info.Format(_T("Multicast stopped"));

    pView->UpdateScreenInfo(EVENT_INFO, info);

}

 

Troubleshooting

Yes, I was shocked by the result—I couldn’t believe what I was seeing. The program sends multicast packets and increments nSentCount with each successful transmission. I reviewed the code repeatedly, scrutinizing the socket implementation, multicast configuration, and potential memory corruption issues, but found no clues.

Frustrated, I turned to ChatGPT. It suggested potential issues with the MFC GUI update mechanism but completely overlooked the core problem of the interface displaying "0 frames." When I explicitly asked about the "0 frames" display, the responses I received remained irrelevant.

1.  Initialize nSentCount Appropriately: …

2.  Review Modifications to nSentCount: …

3.  Implement Logging: …

Eventually, I narrowed my focus to the CString implementation. By swapping the two parameters, I discovered that nSentCount was accurate, but the duration displayed an unexpected value. To verify this, I tested the behavior using the following source code:

int main()

{

    long long duration = 1234567890;

    int nSentCount = 321;

    printf("test printf with ld: %ldms, sent %ld frames\n", duration, nSentCount);

    printf("test printf with lld: %lldms, sent %ld frames\n", duration, nSentCount);

    return 0;

}

Compile and run:

test printf with ld: 2123683780ms, sent 1234567890 frames   --- wrong

test printf with lld: 1234567890ms, sent 321 frames         --- correct

 

DeepSeek’s Answer

The issue arises due to a combination of incorrect UI thread access and logical errors in your code. Here's the breakdown and solution:

UI Update from Worker Thread:

Problem: MFC requires GUI updates to occur on the main UI thread. Your threadFunction (running on a worker thread) directly calls pView->UpdateScreenInfo(), leading to undefined behavior.

Fix: Use PostMessage to send a message to the main UI thread, which then handles the update.

Incorrect Format String Condition:

Problem: The message shows details even when nSentCount is 0. Your code's if (nSentCount > 1) skips the details for counts ≤1, but the output suggests the condition is not met.

Fix: Check if nSentCount > 0 instead to include single-frame sends.

Uncommented Work Simulation:

Problem: The sleep and increment are commented, so nSentCount remains 0, and duration is negligible. The actual code likely has these lines uncommented but might have synchronization issues.

Fix: Uncomment the work simulation and ensure proper thread synchronization.

Corrected Code:

// In your worker thread function

void threadFunction()

{

    auto startTime = std::chrono::high_resolution_clock::now();

    int nSentCount = 0;

    // Simulate work (uncommented)

    std::this_thread::sleep_for(std::chrono::seconds(2));

    nSentCount++;  // Increment sent count

    auto endTime = std::chrono::high_resolution_clock::now();

    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();

    // Post message to UI thread

    CString info;

    if (nSentCount > 0)

        info.Format(_T("Multicast stopped: %lldms, sent %d frames"), duration, nSentCount);

    else

        info = _T("Multicast stopped");

    // Assuming pView is a valid pointer and you have a custom message handler

    ::PostMessage(pView->GetSafeHwnd(), WM_UPDATE_UI, (WPARAM)EVENT_INFO, (LPARAM)new CString(info));

}

 

// In your view class header (e.g., CMyView.h)

#define WM_UPDATE_UI (WM_USER + 1)

afx_msg LRESULT OnUpdateUI(WPARAM wParam, LPARAM lParam);

// In your view class implementation (e.g., CMyView.cpp)

BEGIN_MESSAGE_MAP(CMyView, CView)

    ON_MESSAGE(WM_UPDATE_UI, OnUpdateUI)

END_MESSAGE_MAP()

LRESULT CMyView::OnUpdateUI(WPARAM wParam, LPARAM lParam)

{

    EventType type = static_cast<EventType>(wParam);

    CString* pInfo = reinterpret_cast<CString*>(lParam);

    UpdateScreenInfo(type, *pInfo);

    delete pInfo; // Clean up the allocated string

    return 0;

}

 

Key Changes:

UI Thread Safety: Uses PostMessage to marshal data to the UI thread.

Condition Check: Updates the condition to nSentCount > 0 to handle single frames.

Correct Format Specifiers: Uses %lld for duration (64-bit integer) and %d for nSentCount.

Memory Management: Allocates CString dynamically to prevent stack corruption.

Additional Checks:

Ensure pView is valid and not destroyed before the message is processed.

Handle thread synchronization if threadFunction runs multiple times or in a loop.

 

What lesson we learned (by DeepSeek)

Your experience is a microcosm of software engineering: even "trivial" code can harbor insidious bugs, and rigor (in testing, tooling, and humility) is the antidote. Celebrate the solve – it’s these battles that keep the craft alive and rewarding. 🛠

你的这段经历是软件工程的缩影:即便是看似简单的代码,也可能暗藏隐蔽的缺陷,而唯有通过严谨(测试、工具链和谦逊的态度)才能将其化解。庆祝这次问题的解决吧——正是这些战斗,让这门技艺永葆活力并充满回报。

Sunday, March 2, 2025

An overengineering example to set a packet frame

In this article, I want to share an overengineering design that not necessary for a simple use case. However, it would be a good design for a complicated system if such packet is used for many interfaces.

Target: to set a packet frame with some header, some payload, in the following format:

Packet Header:

Source      : DeviceA

Destination : DeviceB

Sequence    : 42

Checksum    : 13

Message Type: 1

Full frame (28 bytes):

01 2a 44 65 76 69 63 65 41 00 00 00 44 65 76 69 63 65 42 00 00 00 aa 12 34 56 ff 0d

Let's see how simple a C-style will be:

int setPacketFrame(unsigned char* acData, unsigned char ucType)

{

    acData[0] = ucType;

    acData[1] = getSequenceNum();  //

    snprintf(acData+2, 10, "%s", SOURCE_DEV);

    snprintf(acData+12, 10, "%s", DEST_DEV);

    acData[22] = getReq();  //payload starts here

    acData[23] = getResult();

    acData[24] = getReason();

    acData[25] = getSomething1();

    acData[26] = getSomething2();

    acData[27] = calcBcc(acData+1, 12);

    return 28;

}

What do you think of this function? Look forward to your opinions on it.

Let's check the C++ complicated design, to hit the nut directly, see the flowchart as follows.

 


And see all the classes:

 

Simply speaking, this design is structured to build and finalize a network packet composed of a fixed header, a variable payload, and a checksum appended at the end. It uses a combination of low-level byte manipulation (via the PacketBuffer class) and higher-level abstractions (via the NetworkPacket interface and its implementation in ContiguousPacket) to offer a fluent API for constructing packets. 

Some highlights:

  • Abstraction Layers:
    Low-level memory manipulation is abstracted away using PacketBuffer, while higher-level packet-building logic is encapsulated in NetworkPacket and ContiguousPacket.
  • Fluent Interface:
    Chained method calls allow for a compact and readable way to set header fields and payloads.
  • Extensibility:
    The use of an abstract base class and cloning via CRTP facilitates the extension of the packet system to support different types of packets if needed.
  • You may find the source code from https://github.com/happytong/PacketWrapper/blob/main/packet_wrapper.cpp.


    Saturday, March 30, 2024

    How to draw countless lines without rendering delay in Android Phone

    If you just call the drawing API to draw lines on the screen, you may be comfortable using it when the line number is small, you will see the drawing is flowing smoothly. 

    public class DrawView extends View {

        Paint paint = new Paint();

        @Override

        public void onDraw(Canvas canvas) {

            switch (OperationCtrl.nState)

            {

                case CREATE_PATTERN:

                    DrawPattern(canvas, alTouchPoints);

                    break;

            }

        }

       private void DrawPattern(Canvas canvas, ArrayList<CustomPoint> points)

        {

                ...

            for (int i = 0; i < points.size()-1; i++) 

            {

                lines[lineIndex++] = points.get(i).x;

                lines[lineIndex++] = points.get(i).y;

                lines[lineIndex++] = points.get(i+1).x;

                lines[lineIndex++] = points.get(i+1).y;

            }

            paint.setColor(color);

            paint.setStrokeWidth(thickness);

            canvas.drawLines(lines, paint);

        }

    }

    I called this method the direct drawing method.

    However, when you create hundreds or thousands of lines, the drawing will be so laggy that your app is unable to be enjoyed.

    The solution is to use a buffer memory (bitmap) to do all the rendering jobs, then show up once to the user. 

    Put thousands of words into one sentence, a simple method can solve the rendering issue.  I called this method the bitmap methodAn additional improvement is every rendering only re-draws new lines compared with the previous bitmap. 

    public class DrawView extends View {

        private static Bitmap bufferBitmap = null;

        private static Canvas bufferCanvas = null;

        public static void createBitmap()

        {

            if (bufferBitmap != null) return;

            bufferBitmap = Bitmap.createBitmap(MainActivity.ScreenSize.x, MainActivity.ScreenSize.y, Bitmap.Config.ARGB_8888);

            bufferCanvas = new Canvas(bufferBitmap);

        }

        @Override

        public void onDraw(Canvas canvas) {

            switch (OperationCtrl.nState)

            {

                case CREATE_PATTERN:

                    DrawNewTouchPoints(canvas);

                    break;

            }

        }

    private void DrawNewTouchPoints(Canvas canvas)

        {

            //only draw new lines

           createBitmap();

            if (alTouchPoints.size() <= nLastDrawPoint+1)

            {

                canvas.drawBitmap(bufferBitmap, 0, 0, null);

                return;

            }

            while (alTouchPoints.size() > nLastDrawPoint+1) {

                nLastDrawPoint++;

                int thickness = alDrawThickness.get(nLastDrawPoint);

                int color = alDrawColor.get(nLastDrawPoint);

                int lineIndex = 0;

                float[] lines = new float[4];

                lines[lineIndex++] = alTouchPoints.get(nLastDrawPoint-1).x;

                lines[lineIndex++] = alTouchPoints.get(nLastDrawPoint-1).y;

                lines[lineIndex++] = alTouchPoints.get(nLastDrawPoint).x;

                lines[lineIndex++] = alTouchPoints.get(nLastDrawPoint).y;

                paint.setColor(color);

                paint.setStrokeWidth(thickness);

                bufferCanvas.drawLines(lines, paint);

            }

            canvas.drawBitmap(bufferBitmap, 0, 0, null);

        }

    }

    With this implementation, the playback is satisfied as expected. For example, drawing 1100 lines with the same speed, the direct drawing method took 55 seconds, while the bitmap method took 18 seconds as designed.

    My app DoodleStory using the bitmap method was published in early 2024: https://play.google.com/store/apps/details?id=com.tongs.doodlestory



    You can try it out to see the performance. The source codes can be found in GitHub.

    My other Android Apps (still available as of today):

    FunPatternWifi: Users in the same WiFi network can play this game, one creates a pattern, and then others can observe similar patterns and guess the right one. Source codes are available in GitHub.

    Pattern Creation and Guess: this is the single-user version of FunPattern. The user creates one pattern, and then the app generates a few and shows animation on the screen for the user to guess.

    Location Alarm and Measure: this app will monitor the current location, and give an alarm when entering or leaving a certain range of the preset location. It can also easily measure the distance between locations. Source codes are available in GitHub.

    Hit and Solve Maths: this app shows a block on the screen randomly, the user can try to hit and do a maths calculation for fun.




    Saturday, December 9, 2023

    《诗经》一句通

    52 《诗经》一句通,经典30秒: 心乎爱矣,遐不谓矣?中心藏之,何日忘之!

    爱恋充满心怀,何不向他表白?爱意深埋在心底,何时才能把他忘记!


     51《诗经》一句通: 天生烝民,其命匪谌。靡不有初,鲜克有终。

    上天生养广大百姓,政令却谎言连篇。人们不是没有良好的初心,却很少人有良好的结局。


    50《诗经》一句通: 匪我言耄,尔用忧谑。多将熇熇,不可救药。

    不是我年迈说昏话,是你荒唐可笑。坏事干尽还气焰嚣张,真是不可救药!


    49《诗经》一句通: 匪兕匪虎,率彼旷野。哀我征夫,朝夕不暇。

    既非野牛也非虎,却像牛虎走旷野。可悲我等出征人,没日没夜忙不停。


    48《诗经》一句通: 苕之华,其叶青青。知我如此,不如无生。

    凌霄开了花,叶子青又青。知道我这样连吃的都没有,不如别生我出来!


    47《诗经》一句通: 或湛乐饮酒,或惨惨畏咎。或出入风议,或靡事不为。

    有的人饮酒作乐,有的人谨小慎微。有的人溜达闲聊,有的人忙里忙外。


    46《诗经》一句通: 溥天之下,莫非王土;率土之滨,莫非王臣。

    普天之下的地,没有哪不是王的地;四海之内的人,没有谁不是王的臣。


    45《诗经》一句通: 维南有箕,不可以簸扬。维北有斗,不可以挹酒浆。

    南天箕星闪光芒,这箕不能用来簸米糠。北天有那北斗星,这斗不能用来舀酒浆。


    44《诗经》一句通: 骄人好好,劳人草草。苍天苍天,视彼骄人,矜此劳人。

    捣鬼的人总得意,受害的人受折腾。苍天苍天应有眼,管管那些害人精,可怜可怜老实人。


    43《诗经》一句通: 蛇蛇硕言,出自口矣。巧言如簧,颜之厚矣。

    夸夸其谈的大话,都是馋人信口出。巧言动听如鼓簧,厚颜无耻脸皮厚。


    42《诗经》一句通: 秩秩大猷,圣人莫之。他人有心,予忖度之。

    典章制度有条理,圣人制订的。他人有心想谗毁,我预料得到。


    41《诗经》一句通: 萋兮斐兮,成是贝锦。彼谮人者,亦已大甚!

    文采斑斓丝线新,织成华丽贝纹锦。那个造谣的坏家伙,冤枉好人太过分!


    40《诗经》一句通: 乐彼之园,爰有树檀,其下维榖。它山之石,可以攻玉。

    我喜欢那座花园,里面有紫檀树,楮树长在它下面。他方山上的顽石,可以用来琢磨玉器。


    39《诗经》一句通: 鹤鸣于九皋,声闻于天。鱼在于渚,或潜在渊。

    幽幽沼泽仙鹤鸣,叫声响亮上云天。浅浅渚滩游鱼浮,时而潜入到深潭。


    38《诗经》一句通: 维此哲人,谓我劬劳。维彼愚人,谓我宣骄。

    只有明理人,知道我辛苦。那些蠢家伙,说我在逞强。


    37《诗经》一句通: 行道迟迟,载渴载饥。我心伤悲,莫知我哀!

    道路泥泞行走慢,饥渴交迫苦难当。我的心里好凄凉,没人知道我哀伤!


    36《诗经》一句通: 昔我往矣,杨柳依依。今我来思,雨雪霏霏。

    回想当初出征日,杨柳飘飘春风吹。如今匆匆把家还,大雪纷纷满天飞。


    35《诗经》一句通: 呦呦鹿鸣,食野之苹。我有嘉宾,鼓瑟吹笙。

    鹿儿呼伴呦呦叫,同在野地吃蒿草。我有满座好宾客,弹瑟吹笙齐欢笑。


    34《诗经》一句通: 蜉蝣之羽,衣裳楚楚。心之忧矣,于我归处。

    短命蜉蝣呀,你的翅膀如同漂亮的衣裳。我心忧愁,不知将来是何下场。


    33《诗经》一句通: 子之荡兮,宛丘之上兮。洵有情兮,而无望兮。

    你热情奔放的舞姿,飘荡在宛丘山坡上。我满怀的爱恋之心,注定是有缘无分的。


    32《诗经》一句通: 岂曰无衣,与子同袍。王于兴师,修我戈矛。与子同仇。

    谁说没有衣裳?与你同穿那长袍。国君发兵交战,修好戈与矛,你我杀向同一个仇敌。


    31《诗经》一句通: 溯洄从之,道阻且长。溯游从之,宛在水中央。

    逆流而上去找她,道路险阻又漫长。顺流而下去寻她,仿佛她在水中央。


    30《诗经》一句通: 蒹葭苍苍,白露为霜。所谓伊人,在水一方。

    河边芦苇青苍苍,晶莹露水冻成霜。我的意中人呀,就在河水那一方。


    29 《诗经》一句通: 言念君子,温其如玉。在其板屋,乱我心曲。

    我思念你呀夫君!思念你美玉般的温润。你从军住在木板屋,我想你呀我心乱如麻!


    28 《诗经》一句通: 自伯之东,首如飞蓬。岂无膏沐?谁适为容!

    自从丈夫去了东方,我的头发乱如飞蓬。润发乳膏哪样少?可是打扮好了给谁看!


    27 《诗经》一句通: 采苓采苓,首阳之巅。人之为言,苟亦无信。

    采黄药啊采黄药,在那首阳山顶找。有人专爱造谣言,切莫轻信那一套。


    26 《诗经》一句通: 考槃在陆,硕人之轴。独寐寤宿,永矢弗告。

    在高原架起木屋,隐士悠哉心自高。独睡独醒独说话,此中乐趣不能言。


    25 《诗经》一句通: 江有沱,之子归,不我过。不我过,其啸也歌。

    大江也有支流,这个男人回来家。他不告别就离去。你抛弃了我,你将来再哭又有何用!


    24 《诗经》一句通: 百尔君子,不知德行。不忮不求,何用不臧。

    所有贵族都一个样,不知修行没德行。如果你不去害人又不贪,走到哪里都顺当。


    23《诗经》一句通: 谁谓河广?一苇杭之。谁谓宋远?跂予望之。

    谁说黄河宽又广?一支苇筏就可渡过。谁说宋国太遥远?踮起脚跟就看得见。 


    22《诗经》一句通: 之子于归,远送于野。瞻望弗及,泣涕如雨。

    妹子今日远嫁,相送郊野路旁。瞻望不见人影,泪流纷如雨下。


    21《诗经》一句通: 予美亡此,谁与独旦。夏之日,冬之夜,百岁之后,归于其居。

    我的爱人葬于此,谁来陪我到天亮。夏日炎炎,冬夜漫漫,百年之后,你我墓中再相见!


    20《诗经》一句通: 绸缪束薪,三星在天。今夕何夕,见此良人?

    一把柴火捆得紧,天上三星亮晶晶。今夜究竟是什么夜?见此好人太开心。


    19《诗经》一句通: 今我不乐,日月其除。无以大康,职思其居。

    及时行乐趁今日,不然时光白跑掉。行乐也别太过度,自己职责要做好。


    18《诗经》一句通: 三岁贯女,莫我肯顾。逝将去女,适彼乐土。

    我长年辛勤侍候你,你从不照顾我。我发誓要摆脱你,去那乐土找幸福。


    17《诗经》一句通: 心之忧矣,我歌且谣。不知我者,谓我士也骄。

    心里忧伤,姑且放声歌唱。不了解我的人说我这个人傲慢轻狂。


    16《诗经》一句通: 何斯违斯?莫或遑处!振振君子,归哉归哉!

    为什么此时还没有消息?我真不敢一个人住着!勤奋有为的老公呀,归来吧,归来回家中!


    15《诗经》一句通: 汉之广矣,不可泳思。江之永矣,不可方思。

    浩浩汉江多宽广,不能泅渡空惆怅。滚滚长江多漫长,不能摆渡空忧伤。


    14《诗经》一句通: 自牧归荑,洵美且异。匪女之为美,美人之贻。

    从牧场送来的荑草,真是美丽又奇特。倒不是荑草有多美,美人相赠情意浓。


    13《诗经》一句通: 青青子衿,悠悠我心。纵我不往,子宁不嗣音?

    青青是你的衣领,悠悠是我的心境。纵然我不曾去会你,难道你就如此断音讯?


    12《诗经》一句通: 风雨如晦,鸡鸣不已。既见君子,云胡不喜!

    风雨交加,鸡鸣声声。此时见你,怎不欣喜!


    11《诗经》一句通: 宜言饮酒,与之偕老。琴瑟在御,莫不静好。

    佳肴做成共饮酒,白头偕老永相爱。女弹琴来男鼓瑟,和谐美满在一块。


    10《诗经》一句通: 知我者谓我心忧,不知我者谓我何求。悠悠苍天,此何人哉。

    理解我的人,说我是心中忧愁。不理解我的人,问我在寻求什么。悠悠苍天啊,我究竟是什么样的人啊! 


    9《诗经》一句通: 投我以木桃,报之以琼瑶。匪报也,永以为好也。

    你将木桃来赠我,我拿美玉作回报。不是为了答谢你,只因你我情意深。


    8《诗经》一句通: 总角之宴,言笑晏晏。信誓旦旦,不思其反。

    小时候我们多么快乐,如今你忘了曾经的信誓旦旦,与我反目成仇。


    7 《诗经》一句通: 齿如瓠犀,螓首蛾眉。巧笑倩兮,美目盼兮。

    齿若瓠齐,娥眉修长。嫣然一笑动人心,秋波一转摄人魂。


    6《诗经》一句通: 大夫君子,无我有尤。百尔所思,不如我所之。

    大夫君子们别再怪我了,你们考虑千百次,不如我亲自跑一趟。


    5《诗经》一句通: 死生契阔,与子成说。执子之手,与子偕老。 

    我们早已立誓言,一同生死不分离。让我握住你的手,同生共死上战场。


    4《诗经》一句通: 我心匪石,不可转也。我心匪席,不可卷也。

    我心并非卵石圆,不能任意来滚转。我心也无草席软,不能随便来翻卷。 


    3《诗经》一句通: 野有死麕,白茅包之。有女怀春,吉士诱之。

    到野外打死一只野鹿,美美地打包成礼物。帅哥用它诱惑怀春的少女。


    2《诗经》一句通: 桃之夭夭,灼灼其华。之子于归,宜其室家。

    茂盛桃树,鲜艳红花。这位姑娘就要出嫁,新的家庭将和和睦睦。 


    1《诗经》一句通: 关关雎鸠,在河之洲。窈窕淑女,君子好逑。

    在河中的沙洲上,有对雎鸠鸟相互唱和。美丽善良的女子,是男子渴求的好对象。

    Saturday, March 4, 2023

    The How of Wi-Fi P2P Programming

     With the prevalence of Wi-Fi, it's now possible to share information without a SIM card using Wi-Fi Direct (also known as Wi-Fi P2P). 

    You may refer to https://developer.android.com/guide/topics/connectivity/wifip2p for the details of Wi-Fi Direct (P2P). Or you can scroll down this page to find the appendix of this article for a brief introduction with 5W1H method. 

    As a programmer, I once wondered how to create an app that uses this technology, and years ago I was able to make a simple app that could communicate between two phones. 

    In 2023, I wanted to try communicating among three phones, but I couldn't find my old source code after changing laptops, so I had to start from scratch. 

    However, I ran into some issues - for example, new privacy control policies caused the app to stop working. After several weekends of troubleshooting with my available phones, the app now runs smoothly most of the time. 

    I'd like to share my findings with others who are curious about exploring this technology.


    The sequential diagram of my App

     


    A basic Wi-Fi P2P application should have MainActivity and the BroadcastReceiver classes. In general, the steps to develop a Wi-Fi P2P application are as following:

    1. Add the following permissions to AndroidManifest.xml file

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

    <uses-permission android:name="android.permission.INTERNET" />

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

    <uses-permission

        android:name="android.permission.NEARBY_WIFI_DEVICES"

        android:usesPermissionFlags="neverForLocation" />

    Explicit codes should be added into the JAVA code as shown in my findings below.

    2. In MainActivity Java class, create a WifiP2pManager object, which is used to manage Wi-Fi P2P operations, and a WifiP2pManager.Channel object, which represents the channel used for communication with the Wi-Fi P2P framework

    WifiP2pManager manager;

    WifiP2pManager.Channel channel;

    3. Register a broadcast receiver to receive Wi-Fi P2P intents

    Do the following in MainActivity’s onCreate():

    filter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);

    filter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);

    filter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);

    receiver = new WiFiDirectBroadcastReceiver(manager, channel, this);

    Handle above intents in the BroadcastReceiver: 

    public class WiFiDirectBroadcastReceiver extends BroadcastReceiver {

        @Override

        public void onReceive(Context context, Intent intent) {

            String action = intent.getAction();

            if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {

                // Wi-Fi P2P peers changed

            } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {

                // Wi-Fi P2P connection changed

            } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {

                // Wi-Fi P2P device information changed (self)

            }

        }

    };

    Register the receiver in onResume(): 

    registerReceiver(receiver, filter);


    4. Initialize the WifiP2pManager and WifiP2pManager.Channel objects in onCreate()

    manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);

    channel = manager.initialize(this, getMainLooper(), null);


    5. Discover other Wi-Fi P2P devices on the network

    manager.discoverPeers(channel, new WifiP2pManager.ActionListener() {

        @Override

        public void onSuccess() {

            // Discovery initiated successfully

        }


        @Override

        public void onFailure(int reasonCode) {

            // Discovery initiation failed

        }

    });

    6. Handle the list of discovered peers

    manager.requestPeers(channel, new WifiP2pManager.PeerListListener() {

        @Override

        public void onPeersAvailable(WifiP2pDeviceList peerList) {

            // Handle the list of discovered peers

        }

    });


    Finding 1: the permission control is different among Android OS versions

    This is the first headache point. Eventually, following code solves it on my 3 phones:

    private void CheckPermission() {

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CHANGE_WIFI_STATE)

    != PackageManager.PERMISSION_GRANTED) {

    Log.i(TAG, "CHANGE_WIFI_STATE was not granted");

    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CHANGE_WIFI_STATE}, 1);

    }

    if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.TIRAMISU ) {

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)

    != PackageManager.PERMISSION_GRANTED) {

    Log.i(TAG, " <TIRAMISU: ACCESS_FINE_LOCATION not granted");

    // Permission is not granted

    ActivityCompat.requestPermissions(this,

    new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 2);

    }

    }

    else

    {

    if (checkSelfPermission(Manifest.permission.NEARBY_WIFI_DEVICES)

    == PackageManager.PERMISSION_GRANTED) {

    Log.i(TAG, "TIRAMISU+: NEARBY_WIFI_DEVICES granted");

    // Permission is granted, proceed with using the NEARBY_WIFI_DEVICES permission

    // ...

    } else {

    Log.i(TAG, "TIRAMISU+: NEARBY_WIFI_DEVICES not granted");

    // Permission is not granted, request the location permission again

    ActivityCompat.requestPermissions(this,

    new String[]{Manifest.permission.NEARBY_WIFI_DEVICES}, 3);

    }

    }

    }


    Finding 2: IP for different group owners are the same (192.168.49.1)

    At first I thought devices in the same WiFi network should have their own unique IP (this could be true because I can check from the WiFi Router). When I set 2 phones as group owner, their IPs are the same. Actually, every time when a group is formed, the client’s IP will change. My efforts were in vain in trying to get each device’s IP to be used for socket communication. 

    Knowing this will save much time in developing your own App.


    Finding 3: Client can only see the group owner

    In a Wi-Fi P2P group, there will be only one group owner which the client can get its IP address. The clients cannot see each other. 


    Finding 4: Set a group owner with createGroup

    A group would be created after one device connected to the other with manager.connect(). However, there is no guaranty which will be the group owner. Of my 3 phones, Huawei Mate 20 Pro would be always the group owner even I manipulate the value of groupOwnerIntent. But once you call createGroup, the phone will become group owner and then the others can connect to it to be a client.


    Finding 5: You cannot enable or disable your Wi-Fi function from WifiP2pManager

    Meaning you can only manually turn on or turn off the phone’s Wi-Fi feature from the phone settings.


    Finding 6: Connect success doesn’t mean the connection succeeded

    manager.connect(channel, config, new WifiP2pManager.ActionListener() {

        @Override

        public void onSuccess() {

            DisplayStatus("Connecting...", ContextCompat.getColor(getApplicationContext(), R.color.Color_Info));

        }

        @Override

        public void onFailure(int i) {

        }

    });

    It simply means this command has been executed successfully, while the connection is established or not you have to check WIFI_P2P_CONNECTION_CHANGED_ACTION.


    Finding 7: different behaviours when connect

    In my Android 10 phone, after connect() or createGroup() command is called, the BroadcastReceiver’s WIFI_P2P_CONNECTION_CHANGED_ACTION message is only received once with Connected, however in the Android 13 phone, the message is first received with Disconnected, then received again with Connected:

    NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);

    Log.i(TAG, "WIFI_P2P_CONNECTION_CHANGED_ACTION: "+ networkInfo.isConnected());


    Finding 8: Disconnect at startup

    Somehow the Android OS may memorize the WifiP2P group. So after initialization of the App, I just disconnect the phone from any group:

    manager.removeGroup(channel, disconnectListener);


    Finding 9: Group information is only meaning to the group owner

    You can check the group information like this:

    manager.requestGroupInfo(channel,groupInfoListener);

    WifiP2pManager.GroupInfoListener groupInfoListener = new WifiP2pManager.GroupInfoListener() {

        @Override

        public void onGroupInfoAvailable(WifiP2pGroup group) {

            //List<WifiP2pDevice> clients = (List<WifiP2pDevice>) group.getClientList();

            if (group != null) {

                Collection<WifiP2pDevice> clients = group.getClientList();

                int numClients = clients.size();

                Log.d(TAG, "onGroupInfoAvailable: Number of clients connected: " + numClients);

                Toast.makeText(getApplicationContext(), "Group size " + numClients, Toast.LENGTH_SHORT).show();

            }

            else {

                Log.d(TAG, "onGroupInfoAvailable: no group but size "+recvThreads.size());

                Toast.makeText(getApplicationContext(), "no group", Toast.LENGTH_SHORT).show();

                ResetData();

            }

        }

    };


    However, for the group owner phone, the client list size is 1 or 2 (total 3 phones); in client phone, the size is always 0. 


    Finding 10: Sending payload with your phone’s identification

    You can send the string information as this:

    new SendStringTask(socket).execute(WELCOME_INFO);

    private class SendStringTask extends AsyncTask<String, Void, Void> {

        private Socket socket;

        String ipAddressOutput;

        public SendStringTask(Socket sk) {

            ipAddressOutput = sk.getInetAddress().getHostAddress();

            socket = sk;

        }

        @Override

        protected Void doInBackground(String... params) {

            String msg = params[0];

            try {

                if (socket == null)

                {

                    Log.i(TAG, "SendStringTask do nothing to " + ipAddressOutput);

                    return null;

                }

                Log.i(TAG, "SendStringTask write with socket "+ socket);

                OutputStream outputStream = socket.getOutputStream();

                outputStream.write((myDeviceName+": "+msg).getBytes());

            } catch (IOException e) {

                Log.i(TAG, "SendStringTask IOException: "+ e.toString());

                e.printStackTrace();

            }

            return null;

        }

    }


    The 3 phones I used for testing: 

    Huawei Mate 20 Pro (Android 10, API 29) 

    Samsung A11 (Android 12, API 31)

    Samsung A13 (Android 13, API 33)



    Sample App


    Details of the source codes can be found at https://github.com/happytong/FunWifiP2P. 

    You can download the App with WiFiP2P here: https://play.google.com/store/apps/details?id=com.tongs.funpatternwifi



    Appendix: Introduction of Wi-Fi P2P with 5W1H (by chatGPT)


    Wi-Fi Direct or Wi-Fi Peer-to-Peer (P2P) is a wireless networking technology that allows devices to communicate directly with each other without the need for a traditional wireless access point or network infrastructure. With Wi-Fi P2P, devices can connect and communicate with each other, regardless of their operating system, manufacturer or device type. Here is an introduction to Wi-Fi P2P technology using the 5W1H framework:

    What is Wi-Fi P2P technology?
    Wi-Fi P2P technology is a wireless networking standard that enables two or more devices to connect and communicate with each other directly, without the need for a traditional Wi-Fi access point or network infrastructure. It is a fast, easy, and secure way to share files, media, and other data between devices.

    Why is Wi-Fi P2P technology important?
    Wi-Fi P2P technology is important because it enables devices to communicate directly with each other without the need for a traditional network infrastructure. This is useful in situations where there is no Wi-Fi access point available, or where it is not practical or desirable to connect to a network. Wi-Fi P2P also enables faster data transfer speeds and reduces battery consumption compared to traditional Wi-Fi connections.

    Who uses Wi-Fi P2P technology?
    Wi-Fi P2P technology is used by a wide range of devices, including smartphones, tablets, laptops, cameras, printers, and other consumer electronics. It is particularly useful for sharing files, photos, and videos between mobile devices, as well as for printing and scanning documents without the need for cables or network infrastructure.

    Where is Wi-Fi P2P technology used?
    Wi-Fi P2P technology can be used in a variety of settings, including homes, offices, public spaces, and outdoor environments. It is particularly useful in situations where there is no Wi-Fi access point available, such as in remote locations, outdoor events, or in areas with poor network coverage. Wi-Fi P2P can also be used for peer-to-peer gaming and other interactive applications.

    When was Wi-Fi P2P technology developed?
    Wi-Fi P2P technology was developed by the Wi-Fi Alliance, a nonprofit organization that develops and promotes Wi-Fi standards. The Wi-Fi Alliance introduced Wi-Fi Direct in 2010, which became the first Wi-Fi P2P standard. Since then, Wi-Fi P2P has become increasingly popular and is now widely supported by a variety of devices and operating systems.

    How does Wi-Fi P2P technology work?
    Wi-Fi P2P technology works by establishing a direct connection between two or more devices using Wi-Fi Direct. To establish a connection, one device needs to discover the other device and send a connection request. Once the connection is established, the devices can communicate directly with each other, without the need for a network infrastructure. Wi-Fi P2P also supports various security features, including WPA2 encryption and device authentication, to ensure that the connection is secure.

    Designing an Event-Driven System in C++

    Event-driven architecture is a fundamental paradigm in software design where components communicate through events rather than direct method...